Remove Luna
This commit is contained in:
parent
e1d1531e0a
commit
148ceba239
@ -91,7 +91,6 @@ dependencies {
|
|||||||
implementation("ru.dbotthepony.kommons:kommons-guava:[$kommonsVersion,)") { setTransitive(false) }
|
implementation("ru.dbotthepony.kommons:kommons-guava:[$kommonsVersion,)") { setTransitive(false) }
|
||||||
|
|
||||||
implementation("com.github.ben-manes.caffeine:caffeine:$caffeineVersion")
|
implementation("com.github.ben-manes.caffeine:caffeine:$caffeineVersion")
|
||||||
implementation(project(":luna"))
|
|
||||||
|
|
||||||
implementation("info.picocli:picocli:$picocliVersion")
|
implementation("info.picocli:picocli:$picocliVersion")
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("java")
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "ru.dbotthepony.kstarbound"
|
|
||||||
version = "0.4.4"
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
java.toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
testImplementation(platform("org.junit:junit-bom:5.9.1"))
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
|
||||||
|
|
||||||
implementation("org.ow2.asm:asm:9.2")
|
|
||||||
implementation("org.ow2.asm:asm-tree:9.2")
|
|
||||||
implementation("org.ow2.asm:asm-util:9.2")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
@ -1,292 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A representation of one of the two arithmetic modes (integer or float).
|
|
||||||
*
|
|
||||||
* <p>This class serves as a bridge between the representation of Lua numbers as
|
|
||||||
* {@link java.lang.Number} and the appropriate dispatch of arithmetic operations
|
|
||||||
* via the methods of {@link org.classdump.luna.LuaMathOperators}.</p>
|
|
||||||
*
|
|
||||||
* <p>There are two concrete instances of this class, {@link #FLOAT} (the float
|
|
||||||
* arithmetic) and {@link #INTEGER} (the integer arithmetic). In order to obtain
|
|
||||||
* the correct instance for the given numbers, use the static methods
|
|
||||||
* {@link #of(Number, Number)} (for lookup based on two arguments, i.e. for binary
|
|
||||||
* operations) or {@link #of(Number)} (for lookup based on a single argument, i.e.
|
|
||||||
* for unary minus).</p>
|
|
||||||
*
|
|
||||||
* <p>The arithmetic methods of this class yield boxed results.</p>
|
|
||||||
*
|
|
||||||
* <p><b>Example:</b> Given two objects {@code a} and {@code b} of type {@code Number},
|
|
||||||
* use the {@code Arithmetic} class to compute the value of the Lua expression
|
|
||||||
* {@code (a // b)}:</p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* // Number a, b
|
|
||||||
* final Number result;
|
|
||||||
* Arithmetic m = Arithmetic.of(a, b);
|
|
||||||
* if (m != null) {
|
|
||||||
* result = m.idiv(a, b);
|
|
||||||
* }
|
|
||||||
* else {
|
|
||||||
* throw new IllegalStateException("a or b is nil");
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
public abstract class Arithmetic {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integer arithmetic.
|
|
||||||
*
|
|
||||||
* <p>Invokes arithmetic operations from {@link LuaMathOperators} with all arguments
|
|
||||||
* converted to Lua integers (i.e. {@code long}s).</p>
|
|
||||||
*/
|
|
||||||
public static final Arithmetic INTEGER = new IntegerArithmetic();
|
|
||||||
/**
|
|
||||||
* Float arithmetic.
|
|
||||||
*
|
|
||||||
* <p>Invokes arithmetic operations from {@link LuaMathOperators} with all arguments
|
|
||||||
* converted to Lua floats (i.e. {@code double}s).</p>
|
|
||||||
*/
|
|
||||||
public static final Arithmetic FLOAT = new FloatArithmetic();
|
|
||||||
|
|
||||||
private Arithmetic() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an arithmetic based on the concrete types of the arguments {@code a}
|
|
||||||
* and {@code b}.
|
|
||||||
*
|
|
||||||
* <p>If either of {@code a} or {@code b} is {@code null}, returns {@code null}.
|
|
||||||
* Otherwise, if either of {@code a} or {@code b} a Lua float, returns {@link #FLOAT}.
|
|
||||||
* If {@code a} and {@code b} are both Lua integers, returns {@link #INTEGER}.</p>
|
|
||||||
*
|
|
||||||
* @param a first argument, may be {@code null}
|
|
||||||
* @param b second argument, may be {@code null}
|
|
||||||
* @return {@code null} if {@code a} or {@code b} is {@code null}; {@link #FLOAT} if {@code a} or
|
|
||||||
* {@code b} is a Lua float; {@link #INTEGER} if {@code a} and {@code b} are Lua integers
|
|
||||||
*/
|
|
||||||
public static Arithmetic of(Number a, Number b) {
|
|
||||||
if (a == null || b == null) {
|
|
||||||
return null;
|
|
||||||
} else if ((a instanceof Double || a instanceof Float)
|
|
||||||
|| (b instanceof Double || b instanceof Float)) {
|
|
||||||
return FLOAT;
|
|
||||||
} else {
|
|
||||||
return INTEGER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an arithmetic based on the concrete type of the argument {@code n}.
|
|
||||||
*
|
|
||||||
* If {@code n} is {@code null}, returns {@code null}; otherwise, returns
|
|
||||||
* {@link #FLOAT} if {@code n} is a Lua float, or {@link #INTEGER} if {@code n}
|
|
||||||
* is a Lua integer.
|
|
||||||
*
|
|
||||||
* @param n the argument, may be {@code null}
|
|
||||||
* @return {@code null} if {@code n} is {@code null}; {@link #FLOAT} if {@code n} is a Lua float;
|
|
||||||
* {@link #INTEGER} if {@code n} is a Lua integer
|
|
||||||
*/
|
|
||||||
public static Arithmetic of(Number n) {
|
|
||||||
if (n == null) {
|
|
||||||
return null;
|
|
||||||
} else if (n instanceof Double || n instanceof Float) {
|
|
||||||
return FLOAT;
|
|
||||||
} else {
|
|
||||||
return INTEGER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua addition of the two numbers
|
|
||||||
* {@code a} and {@code b}.
|
|
||||||
*
|
|
||||||
* @param a first addend, must not be {@code null}
|
|
||||||
* @param b second addend, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (a + b)}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Number add(Number a, Number b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua subtraction of the two numbers
|
|
||||||
* {@code a} and {@code b}.
|
|
||||||
*
|
|
||||||
* @param a the minuend, must not be {@code null}
|
|
||||||
* @param b the subtrahend, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (a - b)}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Number sub(Number a, Number b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua multiplication of the two numbers
|
|
||||||
* {@code a} and {@code b}.
|
|
||||||
*
|
|
||||||
* @param a first factor, must not be {@code null}
|
|
||||||
* @param b second factor, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (a * b)}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Number mul(Number a, Number b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua float division of the two numbers
|
|
||||||
* {@code a} and {@code b}.
|
|
||||||
*
|
|
||||||
* @param a the dividend, must not be {@code null}
|
|
||||||
* @param b the divisor, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (a / b)}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Double div(Number a, Number b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua modulo of the two numbers
|
|
||||||
* {@code a} and {@code b}.
|
|
||||||
*
|
|
||||||
* @param a the dividend, must not be {@code null}
|
|
||||||
* @param b the divisor, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (a % b)}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Number mod(Number a, Number b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua floor division of the two numbers
|
|
||||||
* {@code a} and {@code b}.
|
|
||||||
*
|
|
||||||
* @param a the dividend, must not be {@code null}
|
|
||||||
* @param b the divisor, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (a // b)}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Number idiv(Number a, Number b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua exponentiation of the two numbers
|
|
||||||
* {@code a} and {@code b}.
|
|
||||||
*
|
|
||||||
* @param a the base, must not be {@code null}
|
|
||||||
* @param b the exponent, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (a ^ b)}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Double pow(Number a, Number b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boxed result of the Lua arithmetic negation of the number
|
|
||||||
* {@code n}.
|
|
||||||
*
|
|
||||||
* @param n the operand, must not be {@code null}
|
|
||||||
* @return the (boxed) value of the Lua expression {@code (-n)}
|
|
||||||
* @throws NullPointerException if {@code n} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Number unm(Number n);
|
|
||||||
|
|
||||||
private static final class IntegerArithmetic extends Arithmetic {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long add(Number a, Number b) {
|
|
||||||
return LuaMathOperators.add(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long sub(Number a, Number b) {
|
|
||||||
return LuaMathOperators.sub(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long mul(Number a, Number b) {
|
|
||||||
return LuaMathOperators.mul(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double div(Number a, Number b) {
|
|
||||||
return LuaMathOperators.div(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long mod(Number a, Number b) {
|
|
||||||
return LuaMathOperators.mod(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long idiv(Number a, Number b) {
|
|
||||||
return LuaMathOperators.idiv(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double pow(Number a, Number b) {
|
|
||||||
return LuaMathOperators.pow(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long unm(Number n) {
|
|
||||||
return LuaMathOperators.unm(n.longValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class FloatArithmetic extends Arithmetic {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double add(Number a, Number b) {
|
|
||||||
return LuaMathOperators.add(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double sub(Number a, Number b) {
|
|
||||||
return LuaMathOperators.sub(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double mul(Number a, Number b) {
|
|
||||||
return LuaMathOperators.mul(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double div(Number a, Number b) {
|
|
||||||
return LuaMathOperators.div(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double mod(Number a, Number b) {
|
|
||||||
return LuaMathOperators.mod(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double idiv(Number a, Number b) {
|
|
||||||
return LuaMathOperators.idiv(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double pow(Number a, Number b) {
|
|
||||||
return LuaMathOperators.pow(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Double unm(Number n) {
|
|
||||||
return LuaMathOperators.unm(n.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.util.ArrayByteIterator;
|
|
||||||
import org.classdump.luna.util.ByteIterator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A byte string backed by a byte array.
|
|
||||||
*/
|
|
||||||
class ArrayByteString extends ByteString {
|
|
||||||
|
|
||||||
static final ArrayByteString EMPTY_INSTANCE = new ArrayByteString(new byte[0]);
|
|
||||||
|
|
||||||
private final byte[] bytes;
|
|
||||||
private int hashCode;
|
|
||||||
|
|
||||||
ArrayByteString(byte[] bytes) {
|
|
||||||
this.bytes = Objects.requireNonNull(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkSubstringBounds(int start, int end, int len) {
|
|
||||||
if (start > end) {
|
|
||||||
throw new IndexOutOfBoundsException("start > end (" + start + " > " + end + ")");
|
|
||||||
} else if (start < 0) {
|
|
||||||
throw new IndexOutOfBoundsException("start < 0 (" + start + " < 0)");
|
|
||||||
} else if (end < 0) {
|
|
||||||
throw new IndexOutOfBoundsException("end < 0 (" + end + " < 0)");
|
|
||||||
} else if (end > len) {
|
|
||||||
throw new IndexOutOfBoundsException("end > length (" + start + " > " + len + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean equalsByteString(ByteString that) {
|
|
||||||
if (this.length() != that.length()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (that instanceof ArrayByteString bstring) {
|
|
||||||
return Arrays.equals(this.bytes, bstring.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = this.length();
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
if (this.byteAt(i) != that.byteAt(i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int hc = hashCode;
|
|
||||||
if (hc == 0) {
|
|
||||||
if (bytes.length > 0) {
|
|
||||||
for (byte b : bytes) {
|
|
||||||
hc = (hc * 31) + (b & 0xff);
|
|
||||||
}
|
|
||||||
hashCode = hc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(ByteString that) {
|
|
||||||
if (that instanceof ArrayByteString bstring)
|
|
||||||
return Arrays.compare(bytes, bstring.bytes);
|
|
||||||
|
|
||||||
return super.compareTo(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int maybeHashCode() {
|
|
||||||
return hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return decode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toRawString() {
|
|
||||||
char[] chars = new char[bytes.length];
|
|
||||||
for (int i = 0; i < chars.length; i++) {
|
|
||||||
chars[i] = (char) (bytes[i] & 0xff);
|
|
||||||
}
|
|
||||||
return String.valueOf(chars);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length() {
|
|
||||||
return bytes.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int maybeLength() {
|
|
||||||
return bytes.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return bytes.length == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte byteAt(int index) {
|
|
||||||
return bytes[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteIterator byteIterator() {
|
|
||||||
return new ArrayByteIterator(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream asInputStream() {
|
|
||||||
// no need to go via the iterator
|
|
||||||
return new ByteArrayInputStream(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteString substring(int start, int end) {
|
|
||||||
checkSubstringBounds(start, end, bytes.length);
|
|
||||||
return new ArrayByteString(Arrays.copyOfRange(bytes, start, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getBytes() {
|
|
||||||
return Arrays.copyOf(bytes, bytes.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putTo(ByteBuffer buffer) {
|
|
||||||
buffer.put(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream stream) throws IOException {
|
|
||||||
// must make a defensive copy to avoid leaking the contents
|
|
||||||
stream.write(getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean startsWith(byte b) {
|
|
||||||
return bytes.length > 0 && bytes[0] == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,481 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.util.ByteIterator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Byte string, an immutable sequence of bytes.
|
|
||||||
*
|
|
||||||
* <p>The purpose of this class is to serve as a bridge between Java strings (with their
|
|
||||||
* characters corresponding to 16-bit code units in the Basic Multilingual Plane (BMP))
|
|
||||||
* and Lua strings (raw 8-bit sequences).</p>
|
|
||||||
*
|
|
||||||
* <p>Byte strings come in two flavours:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>one is a wrapper of {@link String java.lang.String} with a given {@link Charset}
|
|
||||||
* — constructed using {@link #of(String)};</li>
|
|
||||||
* <li>the other is a wrapper of a {@code byte} arrays
|
|
||||||
* — constructed using {@link #wrap(byte[])}.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>The {@code ByteString} class provides the functionality for treating both cases
|
|
||||||
* as sequences of bytes when they take part in Lua operations, and as Java strings when
|
|
||||||
* used by an outer Java application. However, these perspectives are as <i>lazy</i>
|
|
||||||
* as possible, in order to avoid doing unnecessary work.</p>
|
|
||||||
*
|
|
||||||
* <p>This class provides a natural lexicographical ordering that is consistent with equals.</p>
|
|
||||||
*/
|
|
||||||
public abstract class ByteString implements Comparable<ByteString> {
|
|
||||||
|
|
||||||
ByteString() {
|
|
||||||
// no-op: package-private to restrict access
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new byte string corresponding to the bytes in {@code s} as encoded
|
|
||||||
* by the specified {@code charset}.
|
|
||||||
*
|
|
||||||
* @param s the string to take the byte view of, must not be {@code null}
|
|
||||||
* @param charset the charset to use for decoding {@code s} into bytes, must not be {@code null}
|
|
||||||
* @return a byte string perspective of {@code s} using {@code charset}
|
|
||||||
* @throws NullPointerException if {@code s} or {@code charset} is {@code null}
|
|
||||||
* @throws IllegalArgumentException if {@code charset} does not provide encoding capability (see
|
|
||||||
* {@link Charset#canEncode()})
|
|
||||||
*/
|
|
||||||
public static ByteString of(String s, Charset charset) {
|
|
||||||
return new StringByteString(s, charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new byte string corresponding to the bytes in {@code s} as encoded
|
|
||||||
* by the default charset ({@link Charset#defaultCharset()}).
|
|
||||||
*
|
|
||||||
* @param s the string to take the perspective of, must not be {@code null}
|
|
||||||
* @return a byte string perspective of {@code s}
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
*/
|
|
||||||
public static ByteString of(String s) {
|
|
||||||
return of(s, Charset.defaultCharset());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new byte string corresponding to bytes in {@code s} by taking the
|
|
||||||
* least significant byte of each character.
|
|
||||||
*
|
|
||||||
* @param s the string to get bytes from, must not be {@code null}
|
|
||||||
* @return a byte string based on {@code s} by taking the least significant byte of each char
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
*/
|
|
||||||
public static ByteString fromRaw(String s) {
|
|
||||||
byte[] bytes = new byte[s.length()];
|
|
||||||
for (int i = 0; i < bytes.length; i++) {
|
|
||||||
bytes[i] = (byte) ((int) s.charAt(i) & 0xff);
|
|
||||||
}
|
|
||||||
return wrap(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte string corresponding to the bytes in {@code s} as encoded by the default
|
|
||||||
* charset in a form suitable for use as a string constant.
|
|
||||||
*
|
|
||||||
* <p>This method differs from {@link #of(String)} in that it may force the computation
|
|
||||||
* of lazily-evaluated properties of the resulting string at instantiation time and
|
|
||||||
* cache them for use at runtime.</p>
|
|
||||||
*
|
|
||||||
* @param s the string to get bytes from, must not be {@code null}
|
|
||||||
* @return a byte string based on a byte perspective of {@code s}
|
|
||||||
*/
|
|
||||||
public static ByteString constOf(String s) {
|
|
||||||
return of(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ByteString wrap(byte[] bytes) {
|
|
||||||
return new ArrayByteString(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte string containing a copy of the byte array {@code bytes}.
|
|
||||||
*
|
|
||||||
* @param bytes the byte array to use as the byte string, must not be {@code null}
|
|
||||||
* @return a byte string containing a copy of {@code bytes}
|
|
||||||
* @throws NullPointerException if {@code bytes} is {@code null}
|
|
||||||
*/
|
|
||||||
public static ByteString copyOf(byte[] bytes) {
|
|
||||||
return copyOf(bytes, 0, bytes.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte string containing a copy of a slice of the byte array {@code bytes}
|
|
||||||
* starting at the offset {@code offset} and consisting of {@code length} bytes.
|
|
||||||
*
|
|
||||||
* @param bytes the byte array to use as the byte string, must not be {@code null}
|
|
||||||
* @param offset offset in {@code bytes} to start reading from
|
|
||||||
* @param length the number of bytes to copy from {@code bytes}
|
|
||||||
* @return a byte string containing a copy of {@code bytes}
|
|
||||||
* @throws NullPointerException if {@code bytes} is {@code null}
|
|
||||||
* @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if
|
|
||||||
* {@code (offset + length)} is greater than {@code bytes.length}
|
|
||||||
*/
|
|
||||||
public static ByteString copyOf(byte[] bytes, int offset, int length) {
|
|
||||||
if (offset < 0 || length < 0 || (offset + length) > bytes.length) {
|
|
||||||
throw new IndexOutOfBoundsException("offset=" + offset + ", length=" + length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrap(Arrays.copyOfRange(bytes, offset, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an empty byte string.
|
|
||||||
*
|
|
||||||
* @return an empty byte string
|
|
||||||
*/
|
|
||||||
public static ByteString empty() {
|
|
||||||
return ArrayByteString.EMPTY_INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if {@code o} is a byte string containing the same bytes as
|
|
||||||
* this byte string.
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> this method uses the strict interpretation of byte strings as byte
|
|
||||||
* sequences. It is therefore <b>not</b> necessarily true that for two byte strings {@code a}
|
|
||||||
* and {@code b}, the result of their comparison is the same as the result of comparing
|
|
||||||
* their images provided by {@link #toString()}:</p>
|
|
||||||
* <pre>
|
|
||||||
* boolean byteEq = a.equals(b);
|
|
||||||
* boolean stringEq = a.toString().equals(b.toString());
|
|
||||||
*
|
|
||||||
* // may fail!
|
|
||||||
* assert (byteEq == stringEq);
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param o object to evaluate for equality with, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a byte string with equal contents to {@code this}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final boolean equals(Object o) {
|
|
||||||
return (this == o) || ((o instanceof ByteString) && this.equalsByteString((ByteString) o));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the hash code of this byte string. The hash code is computed using the same
|
|
||||||
* function as used by {@link String#hashCode()}, interpreting the byte string's bytes
|
|
||||||
* as unsigned integers.
|
|
||||||
*
|
|
||||||
* @return the hash code of this byte string
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public abstract int hashCode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an integer <i>i</i> that corresponds to the hash code of this byte string
|
|
||||||
* if <i>i</i> is non-zero. When <i>i</i> is zero, it <b>may or may not</b> be the hash code
|
|
||||||
* of this string.
|
|
||||||
*
|
|
||||||
* @return the hash code of this byte string if non-zero
|
|
||||||
*/
|
|
||||||
abstract int maybeHashCode();
|
|
||||||
|
|
||||||
abstract boolean equalsByteString(ByteString that);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new byte array containing the bytes of this byte string.
|
|
||||||
*
|
|
||||||
* @return a new byte array
|
|
||||||
*/
|
|
||||||
public abstract byte[] getBytes();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the byte at position {@code index}.
|
|
||||||
*
|
|
||||||
* @param index the position in the string
|
|
||||||
* @return the byte at position {@code index}
|
|
||||||
* @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= length()}
|
|
||||||
*/
|
|
||||||
public abstract byte byteAt(int index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterator over the bytes in this byte string.
|
|
||||||
*
|
|
||||||
* @return an iterator over the bytes in this byte string
|
|
||||||
*/
|
|
||||||
public abstract ByteIterator byteIterator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an input stream that reads the contents of this string.
|
|
||||||
*
|
|
||||||
* @return an input stream that reads the contents of this string
|
|
||||||
*/
|
|
||||||
public InputStream asInputStream() {
|
|
||||||
return new ByteStringInputStream(byteIterator());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of this byte string, i.e., the number of bytes it contains.
|
|
||||||
*
|
|
||||||
* @return the length of this byte string
|
|
||||||
*/
|
|
||||||
public abstract int length();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an integer <i>i</i> that is equal to the length of this byte string if
|
|
||||||
* <i>i</i> is non-negative. When <i>i</i> is negative, the length of this byte string
|
|
||||||
* is not yet known.
|
|
||||||
*
|
|
||||||
* @return the length of this byte string if non-negative
|
|
||||||
*/
|
|
||||||
abstract int maybeLength();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff this byte string is empty, i.e., if the number of bytes it
|
|
||||||
* contains is 0.
|
|
||||||
*
|
|
||||||
* @return {@code true} iff this byte string is empty
|
|
||||||
*/
|
|
||||||
public abstract boolean isEmpty();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a substring of this byte string starting at position {@code start} (inclusive),
|
|
||||||
* ending at position {@code end} (exclusive).
|
|
||||||
*
|
|
||||||
* <p>The indices refer to the <i>byte</i> position in the byte string.</p>
|
|
||||||
*
|
|
||||||
* @param start the first index to include in the new substring (inclusive)
|
|
||||||
* @param end the smallest index immediately following the new substring in this byte string
|
|
||||||
* @return a substring of this byte string ranging from {@code start} (inclusive) to {@code end}
|
|
||||||
* (exclusive)
|
|
||||||
* @throws IndexOutOfBoundsException if {@code start < 0}, {@code end > length()} or {@code start
|
|
||||||
* > end}
|
|
||||||
*/
|
|
||||||
public abstract ByteString substring(int start, int end);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the contents of this byte string to the specified {@code buffer}.
|
|
||||||
*
|
|
||||||
* @param buffer the buffer to use, must not be {@code null}
|
|
||||||
* @throws NullPointerException if {@code buffer} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract void putTo(ByteBuffer buffer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the contents of this byte string to the specified {@code stream}.
|
|
||||||
*
|
|
||||||
* @param stream the stream to use, must not be {@code null}
|
|
||||||
* @throws IOException when I/O error happens during the write
|
|
||||||
* @throws NullPointerException if {@code stream} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract void writeTo(OutputStream stream) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the interpretation of this byte string as a Java string.
|
|
||||||
*
|
|
||||||
* @return the string represented by this byte string
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public abstract String toString();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of this byte string that uses the specified
|
|
||||||
* charset {@code charset} to decode characters from bytes.
|
|
||||||
*
|
|
||||||
* @param charset the charset to use, must not be {@code null}
|
|
||||||
* @return this byte string decoded into a string using {@code charset}
|
|
||||||
* @throws NullPointerException if {@code charset} is {@code null}
|
|
||||||
*/
|
|
||||||
public String decode(Charset charset) {
|
|
||||||
if (isEmpty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer byteBuffer = ByteBuffer.allocate(length());
|
|
||||||
putTo(byteBuffer);
|
|
||||||
byteBuffer.flip();
|
|
||||||
return charset.decode(byteBuffer).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string represented by this byte string decoded using the default charset
|
|
||||||
* of the virtual machine.
|
|
||||||
*
|
|
||||||
* <p>This is effectively equivalent to {@link #decode(Charset)}
|
|
||||||
* called with {@link Charset#defaultCharset()}.</p>
|
|
||||||
*
|
|
||||||
* @return a string decoded from this byte string using the platform's default charset
|
|
||||||
*/
|
|
||||||
public String decode() {
|
|
||||||
return decode(Charset.defaultCharset());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string in which all characters are directly mapped to the bytes in this
|
|
||||||
* byte string by treating them as unsigned integers.
|
|
||||||
*
|
|
||||||
* <p>This method is the complement of {@link #fromRaw(String)}.</p>
|
|
||||||
*
|
|
||||||
* @return a raw string based on this byte string
|
|
||||||
*/
|
|
||||||
public abstract String toRawString();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares this byte string lexicographically with {@code that}. Returns a negative
|
|
||||||
* integer, zero, or a positive integer if {@code this} is lesser than, equal to or greater
|
|
||||||
* than {@code that} in this ordering.
|
|
||||||
*
|
|
||||||
* <p>For the purposes of this ordering, bytes are interpreted as <i>unsigned</i>
|
|
||||||
* integers.</p>
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> this method uses the strict interpretation of byte strings as byte
|
|
||||||
* sequences. It is therefore <b>not</b> necessarily true that for two byte strings {@code a}
|
|
||||||
* and {@code b}, the result of their comparison is the same as the result of comparing
|
|
||||||
* their images provided by {@link #toString()}:</p>
|
|
||||||
* <pre>
|
|
||||||
* int byteCmp = a.compareTo(b);
|
|
||||||
* int stringCmp = a.toString().compareTo(b.toString());
|
|
||||||
*
|
|
||||||
* // may fail!
|
|
||||||
* assert(Integer.signum(byteCmp) == Integer.signum(stringCmp));
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>This is done in order to ensure that the natural ordering provided by this
|
|
||||||
* {@code compareTo()} is consistent with equals.</p>
|
|
||||||
*
|
|
||||||
* @param that byte string to compare to, must not be {@code null}
|
|
||||||
* @return a negative, zero, or positive integer if {@code this} is lexicographically lesser than,
|
|
||||||
* equal to or greater than {@code that}
|
|
||||||
* @throws NullPointerException if {@code that} is {@code null}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compareTo(ByteString that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
ByteIterator thisIterator = this.byteIterator();
|
|
||||||
ByteIterator thatIterator = that.byteIterator();
|
|
||||||
|
|
||||||
while (thisIterator.hasNext() && thatIterator.hasNext()) {
|
|
||||||
int thisByte = thisIterator.nextByte() & 0xff;
|
|
||||||
int thatByte = thatIterator.nextByte() & 0xff;
|
|
||||||
int diff = thisByte - thatByte;
|
|
||||||
if (diff != 0) {
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return thisIterator.hasNext()
|
|
||||||
? 1 // !thatIterator.hasNext() => that is shorter
|
|
||||||
: thatIterator.hasNext()
|
|
||||||
? -1 // this is shorter
|
|
||||||
: 0; // equal length
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte string formed by a concatenating this byte string with the byte string
|
|
||||||
* {@code other}.
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> this method uses the non-strict interpretation and therefore
|
|
||||||
* may (<i>but might not necessarily</i>) preserve unmappable and malformed characters
|
|
||||||
* occurring in the two strings.</p>
|
|
||||||
*
|
|
||||||
* @param other the byte string to concatenate this byte string with, must not be {@code null}
|
|
||||||
* @return this byte string concatenated with {@code other}
|
|
||||||
* @throws NullPointerException if {@code other} is {@code null}
|
|
||||||
*/
|
|
||||||
public ByteString concat(ByteString other) {
|
|
||||||
if (other.isEmpty()) {
|
|
||||||
return this;
|
|
||||||
} else if (this.isEmpty()) {
|
|
||||||
return other;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] thisBytes = this.getBytes();
|
|
||||||
byte[] otherBytes = other.getBytes();
|
|
||||||
|
|
||||||
byte[] result = new byte[thisBytes.length + otherBytes.length];
|
|
||||||
System.arraycopy(thisBytes, 0, result, 0, thisBytes.length);
|
|
||||||
System.arraycopy(otherBytes, 0, result, thisBytes.length, otherBytes.length);
|
|
||||||
return ByteString.wrap(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte string formed by concatenating this byte string with the string
|
|
||||||
* {@code other}.
|
|
||||||
*
|
|
||||||
* <p>This is a convenience method equivalent to</p>
|
|
||||||
* <pre>
|
|
||||||
* concat(ByteString.of(other))
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param other the string to concatenate with, must not be {@code null}
|
|
||||||
* @return this byte string concatenated with {@code other}
|
|
||||||
* @throws NullPointerException if {@code other} is {@code null}
|
|
||||||
*/
|
|
||||||
public ByteString concat(String other) {
|
|
||||||
return this.concat(ByteString.of(other));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: add startsWith(ByteString)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if the first byte of this byte string is {@code b}.
|
|
||||||
*
|
|
||||||
* @param b the byte to compare the first byte of this byte string to
|
|
||||||
* @return {@code true} if this byte string starts with {@code b}
|
|
||||||
*/
|
|
||||||
public abstract boolean startsWith(byte b);
|
|
||||||
|
|
||||||
// TODO: add contains(ByteString)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if the byte string contains the byte {@code b}.
|
|
||||||
*
|
|
||||||
* @param b the byte to search for in the byte string
|
|
||||||
* @return {@code true} if this byte string contains {@code b}
|
|
||||||
*/
|
|
||||||
public boolean contains(byte b) {
|
|
||||||
ByteIterator it = byteIterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
if (b == it.nextByte()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces all occurrences of the byte string {@code target} in this byte string
|
|
||||||
* with the replacement {@code replacement}.
|
|
||||||
*
|
|
||||||
* @param target the substring to replace, must not be {@code null}
|
|
||||||
* @param replacement the replacement, must not be {@code null}
|
|
||||||
* @return this byte string with all occurrences of {@code target} replaced by {@code replacement}
|
|
||||||
* @throws NullPointerException if {@code target} or {@code replacement} is {@code null}
|
|
||||||
*/
|
|
||||||
public ByteString replace(ByteString target, ByteString replacement) {
|
|
||||||
// FIXME: don't go via raw strings
|
|
||||||
return ByteString.fromRaw(this.toRawString().replace(
|
|
||||||
target.toRawString(),
|
|
||||||
replacement.toRawString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,279 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import org.classdump.luna.util.Check;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A builder for byte strings, similar in interface to {@link StringBuilder}.
|
|
||||||
*
|
|
||||||
* <p>This class is not thread-safe.</p>
|
|
||||||
*/
|
|
||||||
public class ByteStringBuilder {
|
|
||||||
|
|
||||||
private static final int DEFAULT_CAPACITY = 32;
|
|
||||||
// don't go any smaller than this
|
|
||||||
private static final int MIN_CAPACITY = DEFAULT_CAPACITY;
|
|
||||||
private byte[] buffer;
|
|
||||||
private int length;
|
|
||||||
|
|
||||||
private ByteStringBuilder(byte[] buffer, int length) {
|
|
||||||
this.buffer = buffer;
|
|
||||||
this.length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new empty {@code ByteStringBuilder} that can hold at least {@code capacity}
|
|
||||||
* bytes.
|
|
||||||
*
|
|
||||||
* @param capacity the initial required capacity, must not be negative
|
|
||||||
* @throws IllegalArgumentException if {@code capacity} is negative
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder(int capacity) {
|
|
||||||
this(new byte[idealCapacity(Check.nonNegative(capacity))], 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new empty {@code ByteStringBuilder}.
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder() {
|
|
||||||
this(new byte[DEFAULT_CAPACITY], 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the smallest positive integer i >= x such that i is a power of 2
|
|
||||||
private static int binaryCeil(int x) {
|
|
||||||
if (x < 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// from "Hacker's Delight" by Henry S. Warren, Jr., section 3-2
|
|
||||||
x -= 1;
|
|
||||||
x |= (x >> 1);
|
|
||||||
x |= (x >> 2);
|
|
||||||
x |= (x >> 4);
|
|
||||||
x |= (x >> 8);
|
|
||||||
x |= (x >> 16);
|
|
||||||
return x + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int idealCapacity(int desired) {
|
|
||||||
int ceil = binaryCeil(desired);
|
|
||||||
return Math.max(ceil, MIN_CAPACITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] resize(byte[] buf, int newSize) {
|
|
||||||
assert (newSize >= buf.length);
|
|
||||||
byte[] newBuf = new byte[newSize];
|
|
||||||
System.arraycopy(buf, 0, newBuf, 0, buf.length);
|
|
||||||
return newBuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureCapacity(int cap) {
|
|
||||||
if (cap > buffer.length) {
|
|
||||||
buffer = resize(buffer, idealCapacity(cap));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current capacity of the builder.
|
|
||||||
*
|
|
||||||
* @return the current capacity of the builder
|
|
||||||
*/
|
|
||||||
public int capacity() {
|
|
||||||
return buffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of bytes in this builder.
|
|
||||||
*
|
|
||||||
* @return the number of bytes in this builder
|
|
||||||
*/
|
|
||||||
public int length() {
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the number of bytes in this builder to {@code newLength}.
|
|
||||||
*
|
|
||||||
* <p>When {@code newLength} is lesser than the current length {@code len},
|
|
||||||
* drops the last {@code (len - newLength)} bytes from the constructed sequence.
|
|
||||||
* If {@code newLength} is greater than {@code len}, appends {@code newLength - len}
|
|
||||||
* zero bytes.</p>
|
|
||||||
*
|
|
||||||
* <p>No memory is freed when reducing the length of the sequence.</p>
|
|
||||||
*
|
|
||||||
* @param newLength the new length, must not be negative
|
|
||||||
* @throws IndexOutOfBoundsException if {@code newLength} is negative
|
|
||||||
*/
|
|
||||||
public void setLength(int newLength) {
|
|
||||||
if (newLength < 0) {
|
|
||||||
throw new IndexOutOfBoundsException(Integer.toString(newLength));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newLength < length) {
|
|
||||||
length = newLength;
|
|
||||||
} else if (newLength > length) {
|
|
||||||
ensureCapacity(newLength);
|
|
||||||
Arrays.fill(buffer, length, newLength, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to reduce the memory consumption of this builder by reducing its capacity
|
|
||||||
* to a smaller, yet still sufficient value.
|
|
||||||
*/
|
|
||||||
public void trimToSize() {
|
|
||||||
int cap = idealCapacity(length);
|
|
||||||
if (cap < capacity()) {
|
|
||||||
buffer = resize(buffer, cap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the byte at position {@code index} to {@code value}.
|
|
||||||
*
|
|
||||||
* @param index the index of the byte to set
|
|
||||||
* @param value the new value of the byte at position {@code index}
|
|
||||||
* @throws IndexOutOfBoundsException if {@code index} is negative or greater than or equal to the
|
|
||||||
* current buffer length
|
|
||||||
*/
|
|
||||||
public void setByteAt(int index, byte value) {
|
|
||||||
if (index < 0 || index > length) {
|
|
||||||
throw new IndexOutOfBoundsException(String.valueOf(index));
|
|
||||||
}
|
|
||||||
buffer[index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the byte {@code b}.
|
|
||||||
*
|
|
||||||
* @param b the byte to append
|
|
||||||
* @return this builder
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder append(byte b) {
|
|
||||||
ensureCapacity(length + 1);
|
|
||||||
buffer[length] = b;
|
|
||||||
length += 1;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the contents of the byte array {@code array}. {@code off}
|
|
||||||
* is the offset in {@code array} to start the write from, and {@code len} is the
|
|
||||||
* number of bytes that should be written.
|
|
||||||
*
|
|
||||||
* <p>Throws an {@code IndexOutOfBoundsException} if {@code off} or {@code len}
|
|
||||||
* is negative, or if {@code (off + len)} is greater than {@code array.length}.</p>
|
|
||||||
*
|
|
||||||
* @param array the byte array, must not be {@code null}
|
|
||||||
* @param off offset in {@code array} to start from
|
|
||||||
* @param len number of bytes to write
|
|
||||||
* @return this builder
|
|
||||||
* @throws NullPointerException if {@code array} is {@code null}
|
|
||||||
* @throws IndexOutOfBoundsException if {@code off} or {@code len} is negative, or if {@code (off
|
|
||||||
* + len)} is greater than {@code array.length}
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder append(byte[] array, int off, int len) {
|
|
||||||
if (off < 0 || len < 0 || (off + len) > array.length) {
|
|
||||||
throw new IndexOutOfBoundsException("off=" + off + ", len=" + len);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > 0) {
|
|
||||||
ensureCapacity(length + len);
|
|
||||||
System.arraycopy(array, off, buffer, length, len);
|
|
||||||
length += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the contents of the byte array {@code array}.
|
|
||||||
*
|
|
||||||
* @param array the byte array to append, must not be {@code null}
|
|
||||||
* @return this builder
|
|
||||||
* @throws NullPointerException if {@code array} is {@code null}
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder append(byte[] array) {
|
|
||||||
return append(array, 0, array.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the contents of the byte string {@code string}.
|
|
||||||
*
|
|
||||||
* @param string the byte string to append, must not be {@code null}
|
|
||||||
* @return this builder
|
|
||||||
* @throws NullPointerException if {@code string} is {@code null}
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder append(ByteString string) {
|
|
||||||
return append(string.getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends a char sequence {@code charSequence} interpreted as a sequence
|
|
||||||
* of bytes using the specified {@code Charset}.
|
|
||||||
*
|
|
||||||
* @param charSequence the char sequence to append, must not be {@code null}
|
|
||||||
* @param charset the charset to use for encoding, must not be {@code null}
|
|
||||||
* @return this builder
|
|
||||||
* @throws NullPointerException if {@code string} is {@code null}
|
|
||||||
* @throws IllegalArgumentException if {@code charset} cannot does not provide encoding capability
|
|
||||||
* (see {@link Charset#canEncode()})
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder append(CharSequence charSequence, Charset charset) {
|
|
||||||
if (!charset.canEncode()) {
|
|
||||||
throw new IllegalArgumentException("Charset cannot encode: " + charset.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: inefficient, could be done more directly
|
|
||||||
append(ByteString.of(charSequence.toString(), charset));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends the char sequence {@code charSequence} interpreted as a sequence
|
|
||||||
* of bytes using the virtual machine's default charset (see {@link Charset#defaultCharset()}).
|
|
||||||
*
|
|
||||||
* @param charSequence the char sequence to append, must not be {@code null}
|
|
||||||
* @return this builder
|
|
||||||
* @throws NullPointerException if {@code charSequence} is {@code null}
|
|
||||||
*/
|
|
||||||
public ByteStringBuilder append(CharSequence charSequence) {
|
|
||||||
return append(charSequence, Charset.defaultCharset());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a byte string consisting of the bytes in this builder.
|
|
||||||
*
|
|
||||||
* @return a byte string with this builder's contents
|
|
||||||
*/
|
|
||||||
public ByteString toByteString() {
|
|
||||||
return ByteString.copyOf(buffer, 0, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the interpretation of this builder's bytes as a {@code java.lang.String}.
|
|
||||||
*
|
|
||||||
* @return a {@code java.lang.String} interpretation of the bytes in this builder
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toByteString().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import org.classdump.luna.util.ByteIterator;
|
|
||||||
|
|
||||||
class ByteStringInputStream extends InputStream {
|
|
||||||
|
|
||||||
private final ByteIterator iterator;
|
|
||||||
|
|
||||||
public ByteStringInputStream(ByteIterator iterator) {
|
|
||||||
this.iterator = iterator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
return !iterator.hasNext() ? -1 : iterator.nextByte() & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO implement more efficient version
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
return super.read(b, off, len);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception thrown to indicate an unsuccessful value conversion.
|
|
||||||
*/
|
|
||||||
public class ConversionException extends LuaRuntimeException {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance with the given error message.
|
|
||||||
*
|
|
||||||
* @param message error message
|
|
||||||
*/
|
|
||||||
public ConversionException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,590 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static methods implementing Lua value conversions.
|
|
||||||
*/
|
|
||||||
public final class Conversions {
|
|
||||||
|
|
||||||
private static final ByteString NULL_ERROR_MESSAGE = ByteString.constOf("(null)");
|
|
||||||
|
|
||||||
private Conversions() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the numerical value of the string {@code s}, or {@code null} if
|
|
||||||
* {@code s} does not have a numerical value.
|
|
||||||
*
|
|
||||||
* <p>If {@code s} is a valid Lua integer literal with optional sign, the numerical
|
|
||||||
* value is the corresponding integer; if {@code s} is a valid Lua float literal with
|
|
||||||
* optional sign, the numerical value is the corresponding float. Otherwise, the {@code s}
|
|
||||||
* does not have a numerical value.</p>
|
|
||||||
*
|
|
||||||
* <p>Leading and trailing whitespace in {@code s} is ignored by this method.</p>
|
|
||||||
*
|
|
||||||
* <p>Numbers returned by this method are in the canonical representation.</p>
|
|
||||||
*
|
|
||||||
* @param s string to convert to numerical value, may be {@code null}
|
|
||||||
* @return a number representing the numerical value of {@code s} (in the canonical
|
|
||||||
* representation), or {@code null} if {@code s} does not have a numerical value
|
|
||||||
*/
|
|
||||||
public static Number numericalValueOf(ByteString s) {
|
|
||||||
String trimmed = s.toString().trim();
|
|
||||||
try {
|
|
||||||
return Long.valueOf(LuaFormat.parseInteger(trimmed));
|
|
||||||
} catch (NumberFormatException ei) {
|
|
||||||
try {
|
|
||||||
return Double.valueOf(LuaFormat.parseFloat(trimmed));
|
|
||||||
} catch (NumberFormatException ef) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the numerical value of the object {@code o}, or {@code null} if {@code o}
|
|
||||||
* does not have a numerical value.
|
|
||||||
*
|
|
||||||
* <p>If {@code o} is already a number, returns {@code o} cast to number. If {@code o}
|
|
||||||
* is a string, returns its numerical value (see {@link #numericalValueOf(ByteString)}).
|
|
||||||
* Otherwise, returns {@code null}.</p>
|
|
||||||
*
|
|
||||||
* <p>This method differs from {@link #arithmeticValueOf(Object)} in that it
|
|
||||||
* preserves the numerical value representation of coerced strings. For use in arithmetic
|
|
||||||
* operations following Lua's argument conversion rules, use that method instead.</p>
|
|
||||||
*
|
|
||||||
* <p>Numbers returned by this method are not necessarily in the canonical representation.</p>
|
|
||||||
*
|
|
||||||
* @param o object to convert to numerical value, may be {@code null}
|
|
||||||
* @return number representing the numerical value of {@code o} (not necessarily in the canonical
|
|
||||||
* representation), of {@code null} if {@code o} does not have a numerical value
|
|
||||||
* @see #arithmeticValueOf(Object)
|
|
||||||
*/
|
|
||||||
public static Number numericalValueOf(Object o) {
|
|
||||||
if (o instanceof Number) {
|
|
||||||
return (Number) o;
|
|
||||||
} else if (o instanceof ByteString) {
|
|
||||||
return numericalValueOf((ByteString) o);
|
|
||||||
} else if (o instanceof String) {
|
|
||||||
return numericalValueOf(ByteString.of((String) o));
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the numerical value of {@code o}, throwing a {@link ConversionException}
|
|
||||||
* if {@code o} does not have a numerical value.
|
|
||||||
*
|
|
||||||
* <p>The conversion rules are those of {@link #numericalValueOf(Object)}; the only difference
|
|
||||||
* is that this method throws an exception rather than returning {@code null} to signal
|
|
||||||
* errors.</p>
|
|
||||||
*
|
|
||||||
* <p>Numbers returned by this method are not necessarily in the canonical representation.</p>
|
|
||||||
*
|
|
||||||
* @param o object to convert to numerical value, may be {@code null}
|
|
||||||
* @param name value name for error reporting, may be {@code null}
|
|
||||||
* @return number representing the numerical value of {@code o} (not necessarily in the canonical
|
|
||||||
* representation), guaranteed to be non-{@code null}
|
|
||||||
* @throws ConversionException if {@code o} is not a number or string convertible to number.
|
|
||||||
* @see #numericalValueOf(Object)
|
|
||||||
*/
|
|
||||||
public static Number toNumericalValue(Object o, String name) {
|
|
||||||
Number n = numericalValueOf(o);
|
|
||||||
if (n == null) {
|
|
||||||
throw new ConversionException((name != null ? name : "value") + " must be a number");
|
|
||||||
} else {
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number {@code n} in its canonical representation,
|
|
||||||
* i.e. a {@link java.lang.Long} if {@code n} is a Lua integer, or a {@link java.lang.Double}
|
|
||||||
* if {@code n} is a Lua float.
|
|
||||||
*
|
|
||||||
* @param n number to convert to canonical representation, must not be {@code null}
|
|
||||||
* @return an instance of {@code Long} if {@code n} is an integer, or an instance of {@code
|
|
||||||
* Double} if {@code n} is a float
|
|
||||||
* @throws NullPointerException if {@code n} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Number toCanonicalNumber(Number n) {
|
|
||||||
if (n instanceof Long || n instanceof Double) {
|
|
||||||
// already in canonical representation
|
|
||||||
return n;
|
|
||||||
} else if (n instanceof Float) {
|
|
||||||
// re-box
|
|
||||||
return Double.valueOf(n.doubleValue());
|
|
||||||
} else {
|
|
||||||
// re-box
|
|
||||||
return Long.valueOf(n.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value {@code o} to its canonical representation.
|
|
||||||
*
|
|
||||||
* <p>For numbers, this method is equivalent to {@link #toCanonicalNumber(Number)}.
|
|
||||||
* If {@code o} is a {@link String java.lang.String}, it is wrapped into a byte
|
|
||||||
* string by {@link ByteString#of(String)}. Otherwise, {@code o} is in canonical
|
|
||||||
* representation.</p>
|
|
||||||
*
|
|
||||||
* <p>This method is intended for use at the Java → Lua boundary, and whenever
|
|
||||||
* it is not certain that {@code o} is in a canonical representation when a canonical
|
|
||||||
* representation is required.</p>
|
|
||||||
*
|
|
||||||
* @param o value to convert to canonical representation, may be {@code null}
|
|
||||||
* @return {@code o} converted to canonical representation
|
|
||||||
*/
|
|
||||||
public static Object canonicalRepresentationOf(Object o) {
|
|
||||||
if (o instanceof Number) {
|
|
||||||
return toCanonicalNumber((Number) o);
|
|
||||||
} else if (o instanceof String) {
|
|
||||||
return ByteString.of((String) o);
|
|
||||||
} else {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value {@code o} in its Java representation.
|
|
||||||
*
|
|
||||||
* <p>If {@code o} is a {@link ByteString}, returns {@code o} as a {@code java.lang.String}
|
|
||||||
* (using {@link ByteString#toString()}. Otherwise, returns {@code o}.</p>
|
|
||||||
*
|
|
||||||
* <p>This method is intended for use at the Lua → Java boundary for interoperating
|
|
||||||
* with Java code unaware of (or not concerned with) the interpretation of Lua
|
|
||||||
* strings as sequences of bytes.</p>
|
|
||||||
*
|
|
||||||
* @param o value to convert to Java representation, may be {@code null}
|
|
||||||
* @return {@code o} converted to a {@code java.lang.String} if {@code o} is a byte string, {@code
|
|
||||||
* o} otherwise
|
|
||||||
*/
|
|
||||||
public static Object javaRepresentationOf(Object o) {
|
|
||||||
if (o instanceof ByteString) {
|
|
||||||
return o.toString();
|
|
||||||
} else {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the contents of the array {@code values} by converting all values to
|
|
||||||
* their canonical representations.
|
|
||||||
*
|
|
||||||
* @param values values to convert to their canonical representations, must not be {@code null}
|
|
||||||
* @throws NullPointerException if {@code values} is {@code null}
|
|
||||||
* @see #canonicalRepresentationOf(Object)
|
|
||||||
*/
|
|
||||||
public static void toCanonicalValues(Object[] values) {
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
Object v = values[i];
|
|
||||||
values[i] = canonicalRepresentationOf(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the contents of the array {@code values} by converting all values to
|
|
||||||
* their Java representations.
|
|
||||||
*
|
|
||||||
* <p>This method is intended for use at the Lua → Java boundary for interoperating
|
|
||||||
* with Java code unaware of (or not concerned with) the interpretation of Lua
|
|
||||||
* strings as sequences of bytes.</p>
|
|
||||||
*
|
|
||||||
* @param values values to convert to their Java representations, must not be {@code null}
|
|
||||||
* @throws NullPointerException if {@code values} is {@code null}
|
|
||||||
* @see #javaRepresentationOf(Object)
|
|
||||||
*/
|
|
||||||
public static void toJavaValues(Object[] values) {
|
|
||||||
for (int i = 0; i < values.length; i++) {
|
|
||||||
Object v = values[i];
|
|
||||||
values[i] = javaRepresentationOf(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a copy of the array {@code values} with all values converted to their
|
|
||||||
* canonical representation.
|
|
||||||
*
|
|
||||||
* @param values values to convert to their canonical representation, must not be {@code null}
|
|
||||||
* @return a copy of {@code values} with all elements converted to canonical representation
|
|
||||||
* @see #canonicalRepresentationOf(Object)
|
|
||||||
*/
|
|
||||||
public static Object[] copyAsCanonicalValues(Object[] values) {
|
|
||||||
values = Arrays.copyOf(values, values.length);
|
|
||||||
toCanonicalValues(values);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a copy of the array {@code values} with all values converted to their
|
|
||||||
* Java representation.
|
|
||||||
*
|
|
||||||
* <p>This method is intended for use at the Lua → Java boundary for interoperating
|
|
||||||
* with Java code unaware of (or not concerned with) the interpretation of Lua
|
|
||||||
* strings as sequences of bytes.</p>
|
|
||||||
*
|
|
||||||
* @param values values to convert to their Java representation, must not be {@code null}
|
|
||||||
* @return a copy of {@code values} with all elements converted to their Java representation
|
|
||||||
* @see #javaRepresentationOf(Object)
|
|
||||||
*/
|
|
||||||
public static Object[] copyAsJavaValues(Object[] values) {
|
|
||||||
values = Arrays.copyOf(values, values.length);
|
|
||||||
toJavaValues(values);
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalises the number {@code n} so that it may be used as a key in a Lua
|
|
||||||
* table.
|
|
||||||
*
|
|
||||||
* <p>If {@code n} has an integer value <i>i</i>, returns the canonical representation
|
|
||||||
* of <i>i</i>; otherwise, returns the canonical representation of {@code n}
|
|
||||||
* (see {@link #toCanonicalNumber(Number)}).</p>
|
|
||||||
*
|
|
||||||
* @param n number to normalise, must not be {@code null}
|
|
||||||
* @return an canonical integer if {@code n} has an integer value, the canonical representation of
|
|
||||||
* {@code n} otherwise
|
|
||||||
* @throws NullPointerException if {@code n} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Number normaliseKey(Number n) {
|
|
||||||
Long i = integerValueOf(n);
|
|
||||||
return i != null ? i : toCanonicalNumber(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalises the argument {@code o} so that it may be used safely as a key
|
|
||||||
* in a Lua table.
|
|
||||||
*
|
|
||||||
* <p>If {@code o} is a number, returns the number normalised (see {@link #normaliseKey(Number)}.
|
|
||||||
* If {@code o} is a {@code java.lang.String}, returns {@code o} as a byte string using
|
|
||||||
* {@link ByteString#of(String)}. Otherwise, returns {@code o}.</p>
|
|
||||||
*
|
|
||||||
* @param o object to normalise, may be {@code null}
|
|
||||||
* @return normalised number if {@code o} is a number, {@code o} as byte string if {@code o} is a
|
|
||||||
* {@code java.lang.String}, {@code o} otherwise
|
|
||||||
*/
|
|
||||||
public static Object normaliseKey(Object o) {
|
|
||||||
if (o instanceof Number) {
|
|
||||||
return normaliseKey((Number) o);
|
|
||||||
} else if (o instanceof String) {
|
|
||||||
return ByteString.of((String) o);
|
|
||||||
} else {
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the arithmetic value of the object {@code o}, or {@code null} if {@code o}
|
|
||||||
* does not have an arithmetic value.
|
|
||||||
*
|
|
||||||
* <p>If {@code o} is a number, then that number is its arithmetic value. If {@code o}
|
|
||||||
* is a string that has a numerical value (see {@link #numericalValueOf(ByteString)}),
|
|
||||||
* its arithmetic value is the numerical value converted to a float. Otherwise,
|
|
||||||
* {@code o} does not have an arithmetic value.</p>
|
|
||||||
*
|
|
||||||
* <p>Note that this method differs from {@link #numericalValueOf(Object)} in that it
|
|
||||||
* coerces strings convertible to numbers into into floats rather than preserving
|
|
||||||
* their numerical value representation, and also note that this conversion happens
|
|
||||||
* <i>after</i> the numerical value has been determined. Most significantly,</p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* Conversions.arithmeticValueOf("-0")
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>yields {@code 0.0} rather than {@code 0} (as would be the case with
|
|
||||||
* {@code numericalValueOf("-0")}), or {@code -0.0} (it would in the case if the string
|
|
||||||
* was parsed directly as a float).</p>
|
|
||||||
*
|
|
||||||
* <p>Numbers returned by this method are not necessarily in the canonical representation.</p>
|
|
||||||
*
|
|
||||||
* @param o object to convert to arithmetic value, may be {@code null}
|
|
||||||
* @return number representing the arithmetic value of {@code o} (not necessarily in the canonical
|
|
||||||
* representation), or {@code null} if {@code o} does not have an arithmetic value
|
|
||||||
* @see #numericalValueOf(Object)
|
|
||||||
*/
|
|
||||||
public static Number arithmeticValueOf(Object o) {
|
|
||||||
if (o instanceof Number) {
|
|
||||||
return (Number) o;
|
|
||||||
} else {
|
|
||||||
Number n = numericalValueOf(o);
|
|
||||||
return n != null ? floatValueOf(n) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the number {@code n}, or {@code null} if {@code n}
|
|
||||||
* does not have an integer value.
|
|
||||||
*
|
|
||||||
* <p>{@code n} has an integer value if and only if the number it denotes can be represented
|
|
||||||
* as a signed 64-bit integer. That integer is then the integer value of {@code n}.
|
|
||||||
* In other words, if {@code n} is a float, it has an integer value if and only if
|
|
||||||
* it can be converted to a {@code long} without loss of precision.</p>
|
|
||||||
*
|
|
||||||
* @param n number to convert to integer, must not be {@code null}
|
|
||||||
* @return a {@code Long} representing the integer value of {@code n}, or {@code null} if {@code
|
|
||||||
* n} does not have an integer value
|
|
||||||
* @throws NullPointerException if {@code n} is {@code null}
|
|
||||||
* @see LuaMathOperators#hasExactIntegerRepresentation(double)
|
|
||||||
*/
|
|
||||||
public static Long integerValueOf(Number n) {
|
|
||||||
if (n instanceof Double || n instanceof Float) {
|
|
||||||
double d = n.doubleValue();
|
|
||||||
return LuaMathOperators.hasExactIntegerRepresentation(d) ? Long.valueOf((long) d) : null;
|
|
||||||
} else if (n instanceof Long) {
|
|
||||||
return (Long) n;
|
|
||||||
} else {
|
|
||||||
return Long.valueOf(n.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the number {@code n}, throwing
|
|
||||||
* a {@link NoIntegerRepresentationException} if {@code n} does not have an integer value.
|
|
||||||
*
|
|
||||||
* <p>This is a variant of {@link #integerValueOf(Number)}; the difference is that
|
|
||||||
* this method throws an exception rather than returning {@code null} to signal that
|
|
||||||
* {@code n} does not have an integer value, and that this method returns the unboxed
|
|
||||||
* integer value of {@code n} (as a {@code long}).</p>
|
|
||||||
*
|
|
||||||
* @param n object to be converted to integer, must not be {@code null}
|
|
||||||
* @return integer value of {@code n}
|
|
||||||
* @throws NoIntegerRepresentationException if {@code n} does not have an integer value
|
|
||||||
* @throws NullPointerException if {@code n} is {@code null}
|
|
||||||
*/
|
|
||||||
public static long toIntegerValue(Number n) {
|
|
||||||
Long l = integerValueOf(n);
|
|
||||||
if (l != null) {
|
|
||||||
return l.longValue();
|
|
||||||
} else {
|
|
||||||
throw new NoIntegerRepresentationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the object {@code o}, or {@code null} if {@code o}
|
|
||||||
* does not have an integer value.
|
|
||||||
*
|
|
||||||
* <p>The integer value of {@code o} is the integer value of its numerical value
|
|
||||||
* (see {@link #numericalValueOf(Object)}), when it exists.</p>
|
|
||||||
*
|
|
||||||
* @param o object to be converted to integer, may be {@code null}
|
|
||||||
* @return a {@code Long} representing the integer value of {@code o}, or {@code null} if {@code
|
|
||||||
* o} does not have a integer value
|
|
||||||
* @see #integerValueOf(Number)
|
|
||||||
*/
|
|
||||||
public static Long integerValueOf(Object o) {
|
|
||||||
Number n = numericalValueOf(o);
|
|
||||||
return n != null ? integerValueOf(n) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer value of the object {@code o}, throwing
|
|
||||||
* a {@link NoIntegerRepresentationException} if {@code o} does not have an integer value.
|
|
||||||
*
|
|
||||||
* <p>This is a variant of {@link #integerValueOf(Object)}; the difference is that
|
|
||||||
* this method throws an exception rather than returning {@code null} to signal that
|
|
||||||
* {@code o} does not have an integer value, and that this method returns the unboxed
|
|
||||||
* integer value of {@code o} (as a {@code long}).</p>
|
|
||||||
*
|
|
||||||
* @param o object to be converted to integer, may be {@code null}
|
|
||||||
* @return integer value of {@code n}
|
|
||||||
* @throws NoIntegerRepresentationException if {@code o} does not have an integer value
|
|
||||||
*/
|
|
||||||
public static long toIntegerValue(Object o) {
|
|
||||||
Long l = integerValueOf(o);
|
|
||||||
if (l != null) {
|
|
||||||
return l.longValue();
|
|
||||||
} else {
|
|
||||||
throw new NoIntegerRepresentationException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the float value of the number {@code n}.
|
|
||||||
*
|
|
||||||
* <p>The float value of {@code n} is its numerical value converted to a Lua float.</p>
|
|
||||||
*
|
|
||||||
* @param n the number to convert to float, must not be {@code null}
|
|
||||||
* @return the float value of {@code n}, guaranteed to be non-{@code null}
|
|
||||||
* @throws NullPointerException if {@code n} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Double floatValueOf(Number n) {
|
|
||||||
return n instanceof Double
|
|
||||||
? (Double) n
|
|
||||||
: Double.valueOf(n.doubleValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the float value of the object {@code o}, or {@code null} if {@code o}
|
|
||||||
* does not have a float value.
|
|
||||||
*
|
|
||||||
* <p>The float value of {@code o} is the {@linkplain #floatValueOf(Number) float value}
|
|
||||||
* of its numerical value (see {@link #numericalValueOf(Object)}), when {@code o}
|
|
||||||
* has a numerical value.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to be converted to float, may be {@code null}
|
|
||||||
* @return a {@code Double} representing the float value of {@code o}, or {@code null} if {@code
|
|
||||||
* o} does not have a float value
|
|
||||||
*/
|
|
||||||
public static Double floatValueOf(Object o) {
|
|
||||||
Number n = numericalValueOf(o);
|
|
||||||
return n != null ? floatValueOf(n) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the boolean value of the object {@code o}.
|
|
||||||
*
|
|
||||||
* <p>The boolean value of {@code o} is {@code false} if and only if {@code o} is <b>nil</b>
|
|
||||||
* (i.e., {@code null}) or <b>false</b> (i.e., a {@link Boolean} {@code b} such
|
|
||||||
* that {@code b.booleanValue() == false}).
|
|
||||||
*
|
|
||||||
* @param o object to convert to boolean, may be {@code null}
|
|
||||||
* @return {@code false} if {@code o} is <b>nil</b> or <b>false</b>, {@code true} otherwise
|
|
||||||
*/
|
|
||||||
public static boolean booleanValueOf(Object o) {
|
|
||||||
return !(o == null || (o instanceof Boolean && !((Boolean) o).booleanValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the string value of the number {@code n}.
|
|
||||||
*
|
|
||||||
* <p>The string value of integers is the result of {@link LuaFormat#toByteString(long)}
|
|
||||||
* on their numerical value; similarly the string value of floats is the result
|
|
||||||
* of {@link LuaFormat#toByteString(double)} on their numerical value.
|
|
||||||
*
|
|
||||||
* @param n number to be converted to string, must not be {@code null}
|
|
||||||
* @return string value of {@code n}, guaranteed to be non-{@code null}
|
|
||||||
* @throws NullPointerException if {@code n} is {@code null}
|
|
||||||
*/
|
|
||||||
public static ByteString stringValueOf(Number n) {
|
|
||||||
if (n instanceof Double || n instanceof Float) {
|
|
||||||
return LuaFormat.toByteString(n.doubleValue());
|
|
||||||
} else {
|
|
||||||
return LuaFormat.toByteString(n.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the string value of the object {@code o}, or {@code null} if {@code o} does
|
|
||||||
* not have a string value.
|
|
||||||
*
|
|
||||||
* <p>If {@code o} is a string, that is the string value. If {@code o} is a number,
|
|
||||||
* returns the string value of that number (see {@link #stringValueOf(Number)}).
|
|
||||||
* Otherwise, {@code o} does not have a string value.
|
|
||||||
*
|
|
||||||
* @param o object to be converted to string, may be {@code null}
|
|
||||||
* @return string value of {@code o}, or {@code null} if {@code o} does not have a string value
|
|
||||||
*/
|
|
||||||
public static ByteString stringValueOf(Object o) {
|
|
||||||
if (o instanceof ByteString) {
|
|
||||||
return (ByteString) o;
|
|
||||||
} else if (o instanceof Number) {
|
|
||||||
return stringValueOf((Number) o);
|
|
||||||
} else if (o instanceof String) {
|
|
||||||
return ByteString.of((String) o);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the object {@code o} to a human-readable string format.
|
|
||||||
*
|
|
||||||
* <p>The conversion rules are the following:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>If {@code o} is a {@code string} or {@code number}, returns the string value
|
|
||||||
* of {@code o};</li>
|
|
||||||
* <li>if {@code o} is <b>nil</b> (i.e., {@code null}), returns {@code "nil"};</li>
|
|
||||||
* <li>if {@code o} is a {@code boolean}, returns {@code "true"} if {@code o} is <b>true</b>
|
|
||||||
* or {@code "false"} if {@code o} is <b>false</b>;</li>
|
|
||||||
* <li>otherwise, returns the string of the form {@code "TYPE: 0xHASH"}, where
|
|
||||||
* TYPE is the Lua type of {@code o}, and HASH
|
|
||||||
* is the {@link Object#hashCode()} of {@code o}
|
|
||||||
* in hexadecimal format.
|
|
||||||
* </li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Note that this method ignores the object's {@code toString()} method
|
|
||||||
* and its {@code __tostring} metamethod.
|
|
||||||
*
|
|
||||||
* @param o the object to be converted to string, may be {@code null}
|
|
||||||
* @return human-readable string representation of {@code o}
|
|
||||||
* @see #stringValueOf(Object)
|
|
||||||
*/
|
|
||||||
public static ByteString toHumanReadableString(Object o) {
|
|
||||||
if (o == null) {
|
|
||||||
return LuaFormat.NIL;
|
|
||||||
} else if (o instanceof ByteString) {
|
|
||||||
return (ByteString) o;
|
|
||||||
} else if (o instanceof Number) {
|
|
||||||
return stringValueOf((Number) o);
|
|
||||||
} else if (o instanceof Boolean) {
|
|
||||||
return LuaFormat.toByteString(((Boolean) o).booleanValue());
|
|
||||||
} else if (o instanceof String) {
|
|
||||||
return ByteString.of((String) o);
|
|
||||||
} else {
|
|
||||||
return ByteString.of(String.format("%s: %#010x",
|
|
||||||
PlainValueTypeNamer.INSTANCE.typeNameOf(o),
|
|
||||||
o.hashCode()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@code Throwable} {@code t} to an error object.
|
|
||||||
*
|
|
||||||
* <p>If {@code t} is a {@link LuaRuntimeException}, the result of this operation
|
|
||||||
* is the result of its {@link LuaRuntimeException#getErrorObject()}. Otherwise,
|
|
||||||
* the result is {@link Throwable#getMessage()}.
|
|
||||||
*
|
|
||||||
* @param t throwable to convert to error object, must not be {@code null}
|
|
||||||
* @return error object represented by {@code t}
|
|
||||||
* @throws NullPointerException if {@code t} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Object toErrorObject(Throwable t) {
|
|
||||||
if (t instanceof LuaRuntimeException) {
|
|
||||||
return ((LuaRuntimeException) t).getErrorObject();
|
|
||||||
} else {
|
|
||||||
return t.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@code Throwable} {@code t} to an error message (a byte string).
|
|
||||||
*
|
|
||||||
* <p>This is equivalent to converting the error object retrieved from {@code t} by
|
|
||||||
* {@link #toErrorObject(Throwable)} to a byte string using
|
|
||||||
* {@link #stringValueOf(Object)}.</p>
|
|
||||||
*
|
|
||||||
* <p>If the error object does not have a string value, returns {@code "(null)"}.</p>
|
|
||||||
*
|
|
||||||
* @param t throwable to convert to error message, must not be {@code null}
|
|
||||||
* @return error message of {@code t}, or {@code "(null)"} if {@code t} does not have a string
|
|
||||||
* error message
|
|
||||||
* @throws NullPointerException if {@code t} is {@code null}
|
|
||||||
*/
|
|
||||||
public static ByteString toErrorMessage(Throwable t) {
|
|
||||||
ByteString m = Conversions.stringValueOf(toErrorObject(t));
|
|
||||||
return m != null ? m : NULL_ERROR_MESSAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,485 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static methods for parsing and generating lexical strings following the Lua lexical
|
|
||||||
* conventions.
|
|
||||||
*/
|
|
||||||
public final class LuaFormat {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The byte string representation of <b>nil</b>.
|
|
||||||
*/
|
|
||||||
public static final ByteString NIL = ByteString.constOf("nil");
|
|
||||||
/**
|
|
||||||
* The byte string representation of <b>true</b>.
|
|
||||||
*/
|
|
||||||
public static final ByteString TRUE = ByteString.constOf("true");
|
|
||||||
/**
|
|
||||||
* The byte string representation of <b>false</b>.
|
|
||||||
*/
|
|
||||||
public static final ByteString FALSE = ByteString.constOf("false");
|
|
||||||
/**
|
|
||||||
* The byte string representation of infinity.
|
|
||||||
*/
|
|
||||||
public static final ByteString INF = ByteString.constOf("inf");
|
|
||||||
/**
|
|
||||||
* The byte string representation of <i>NaN</i>.
|
|
||||||
*/
|
|
||||||
public static final ByteString NAN = ByteString.constOf("nan");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code nil} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_NIL = NIL;
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code boolean} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_BOOLEAN = ByteString.constOf("boolean");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code number} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_NUMBER = ByteString.constOf("number");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code string} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_STRING = ByteString.constOf("string");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code table} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_TABLE = ByteString.constOf("table");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code function} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_FUNCTION = ByteString.constOf("function");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code userdata} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_USERDATA = ByteString.constOf("userdata");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code thread} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_THREAD = ByteString.constOf("thread");
|
|
||||||
/**
|
|
||||||
* Byte string representation of the Lua {@code lua object} type.
|
|
||||||
*/
|
|
||||||
public static final ByteString TYPENAME_LUAOBJECT = ByteString.constOf("luaobject");
|
|
||||||
/**
|
|
||||||
* The '\a' character.
|
|
||||||
*/
|
|
||||||
public static final char CHAR_BELL = 0x07;
|
|
||||||
/**
|
|
||||||
* The '\v' character.
|
|
||||||
*/
|
|
||||||
public static final char CHAR_VERTICAL_TAB = 0x0b;
|
|
||||||
private static final ByteString NEG_INF = ByteString.constOf("-" + INF);
|
|
||||||
private static final Set<String> keywords;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Set<String> ks = new HashSet<>();
|
|
||||||
Collections.addAll(ks,
|
|
||||||
"and", "break", "do", "else", "elseif", "end", "false", "for",
|
|
||||||
"function", "goto", "if", "in", "local", "nil", "not", "or",
|
|
||||||
"repeat", "return", "then", "true", "until", "while");
|
|
||||||
keywords = Collections.unmodifiableSet(ks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LuaFormat() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Lua format string representation of the boolean value {@code b}.
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> this method returns a {@code java.lang.String}. In order to
|
|
||||||
* obtain a <i>byte</i> string, use {@link #toByteString(boolean)} rather than
|
|
||||||
* wrapping the result of this method using {@link ByteString#of(String)}.</p>
|
|
||||||
*
|
|
||||||
* @param b the boolean value
|
|
||||||
* @return string representation of {@code b}
|
|
||||||
*/
|
|
||||||
public static String toString(boolean b) {
|
|
||||||
return toByteString(b).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Lua format byte string representation of the boolean value {@code b}
|
|
||||||
* as a byte string.
|
|
||||||
*
|
|
||||||
* @param b the boolean value
|
|
||||||
* @return byte string representation of {@code b}
|
|
||||||
*/
|
|
||||||
public static ByteString toByteString(boolean b) {
|
|
||||||
return b ? TRUE : FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Lua format string representation of the integer value {@code l}.
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> this method returns a {@code java.lang.String}. In order to
|
|
||||||
* obtain a <i>byte</i> string, use {@link #toByteString(long)} rather than
|
|
||||||
* wrapping the result of this method using {@link ByteString#of(String)}.</p>
|
|
||||||
*
|
|
||||||
* @param l the integer value
|
|
||||||
* @return string representation of {@code l}
|
|
||||||
*/
|
|
||||||
public static String toString(long l) {
|
|
||||||
return Long.toString(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Lua format byte string representation of the integer value {@code l}.
|
|
||||||
*
|
|
||||||
* @param l the integer value
|
|
||||||
* @return byte string representation of {@code l}
|
|
||||||
*/
|
|
||||||
public static ByteString toByteString(long l) {
|
|
||||||
return ByteString.of(toString(l));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Lua format string representation of the float value {@code f}.
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> this method returns a {@code java.lang.String}. In order to
|
|
||||||
* obtain a <i>byte</i> string, use {@link #toByteString(long)} rather than
|
|
||||||
* wrapping the result of this method using {@link ByteString#of(String)}.</p>
|
|
||||||
*
|
|
||||||
* @param f the float value
|
|
||||||
* @return string representation of {@code f}
|
|
||||||
*/
|
|
||||||
public static String toString(double f) {
|
|
||||||
return toByteString(f).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ByteString finiteDoubleToByteString(double f) {
|
|
||||||
// f assumed not to be NaN or infinite
|
|
||||||
|
|
||||||
// Lua 5.2 compat
|
|
||||||
if (Math.abs(f) % 1.0 <= 0.000000001) {
|
|
||||||
return ByteString.of(Long.toString((long) f));
|
|
||||||
}
|
|
||||||
|
|
||||||
String s = Double.toString(f).toLowerCase();
|
|
||||||
return ByteString.of(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Lua format byte string representation of the float value {@code f}.
|
|
||||||
*
|
|
||||||
* @param f the float value
|
|
||||||
* @return byte string representation of {@code f}
|
|
||||||
*/
|
|
||||||
public static ByteString toByteString(double f) {
|
|
||||||
if (Double.isNaN(f)) {
|
|
||||||
return NAN;
|
|
||||||
} else if (Double.isInfinite(f)) {
|
|
||||||
return f > 0 ? INF : NEG_INF;
|
|
||||||
} else {
|
|
||||||
return finiteDoubleToByteString(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int hexValue(int c) {
|
|
||||||
if (c >= '0' && c <= '9') {
|
|
||||||
return c - (int) '0';
|
|
||||||
} else if (c >= 'a' && c <= 'f') {
|
|
||||||
return 10 + c - (int) 'a';
|
|
||||||
} else if (c >= 'A' && c <= 'F') {
|
|
||||||
return 10 + c - (int) 'A';
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the string {@code s} as an integer according to the Lua lexer rules.
|
|
||||||
* When {@code s} is not an integer numeral, throws a {@link NumberFormatException}.
|
|
||||||
*
|
|
||||||
* <p>This method ignores leading and trailing whitespace in {@code s}.</p>
|
|
||||||
*
|
|
||||||
* @param s string to be parsed, must not be {@code null}
|
|
||||||
* @return the integer value represented by {@code s}
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
* @throws NumberFormatException if {@code s} is not a valid Lua format string representing an
|
|
||||||
* integer value
|
|
||||||
*/
|
|
||||||
public static long parseInteger(String s) throws NumberFormatException {
|
|
||||||
s = s.trim();
|
|
||||||
if (s.startsWith("0x") || s.startsWith("0X")) {
|
|
||||||
long l = 0;
|
|
||||||
int from = Math.max(2, s.length() - 16);
|
|
||||||
|
|
||||||
for (int idx = 2; idx < from; idx++) {
|
|
||||||
if (hexValue(s.charAt(idx)) < 0) {
|
|
||||||
throw new NumberFormatException("Illegal character #" + idx + " in \"" + s + "\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only take the last 16 characters of the string for the value
|
|
||||||
for (int idx = Math.max(2, s.length() - 16); idx < s.length(); idx++) {
|
|
||||||
int hex = hexValue(s.charAt(idx));
|
|
||||||
if (hex < 0) {
|
|
||||||
throw new NumberFormatException("Illegal character #" + idx + " in \"" + s + "\"");
|
|
||||||
}
|
|
||||||
l = l << 4 | hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
return l;
|
|
||||||
} else {
|
|
||||||
return Long.parseLong(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the string {@code s} as a float according to the Lua lexer rules.
|
|
||||||
* When {@code s} is not a float numeral, throws a {@link NumberFormatException}.
|
|
||||||
*
|
|
||||||
* <p>This method ignores leading and trailing whitespace in {@code s}.</p>
|
|
||||||
*
|
|
||||||
* @param s the string to be parsed, must not be {@code null}
|
|
||||||
* @return the float value represented by {@code s}
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
* @throws NumberFormatException if {@code s} is not a valid Lua format string representing a
|
|
||||||
* float value
|
|
||||||
*/
|
|
||||||
public static double parseFloat(String s) throws NumberFormatException {
|
|
||||||
try {
|
|
||||||
return Double.parseDouble(s);
|
|
||||||
} catch (NumberFormatException e0) {
|
|
||||||
// might be missing the trailing exponent for hex floating point constants
|
|
||||||
try {
|
|
||||||
return Double.parseDouble(s.trim() + "p0");
|
|
||||||
} catch (NumberFormatException e1) {
|
|
||||||
throw new NumberFormatException("Not a number: " + s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses {@code s} as an integer following the Lua lexer rules. When {@code s} is
|
|
||||||
* an integer numeral, returns its value boxed as a {@link Long}. Otherwise, returns
|
|
||||||
* {@code null}.
|
|
||||||
*
|
|
||||||
* <p>This is a variant of {@link #parseInteger(String)} that signals invalid input
|
|
||||||
* by returning {@code null} rather than throwing a {@code NumberFormatException}.</p>
|
|
||||||
*
|
|
||||||
* @param s the string to be parsed, must not be {@code null}
|
|
||||||
* @return the (boxed) integer value represented by {@code s} if {@code s} is an integer numeral;
|
|
||||||
* {@code null} otherwise
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Long tryParseInteger(String s) {
|
|
||||||
try {
|
|
||||||
return parseInteger(s);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses {@code s} as a float following the Lua lexer rules. When {@code s} is
|
|
||||||
* a float numeral, returns its value boxed as a {@link Double}. Otherwise, returns
|
|
||||||
* {@code null}.
|
|
||||||
*
|
|
||||||
* <p>This is a variant of {@link #parseFloat(String)} that signals invalid input
|
|
||||||
* by returning {@code null} rather than throwing a {@code NumberFormatException}.</p>
|
|
||||||
*
|
|
||||||
* @param s the string to be parsed, must not be {@code null}
|
|
||||||
* @return the (boxed) float value represented by {@code s} if {@code s} is an float numeral;
|
|
||||||
* {@code null} otherwise
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Double tryParseFloat(String s) {
|
|
||||||
try {
|
|
||||||
return parseFloat(s);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses {@code s} as a number following the Lua lexer rules. When {@code s} is
|
|
||||||
* a numeral, returns its value boxed either as a {@link Long} (for integer numerals)
|
|
||||||
* or a {@link Double} (for float numerals). Otherwise, returns {@code null}.
|
|
||||||
*
|
|
||||||
* <p>Note an integer numeral is also a float numeral, but not all float numerals are
|
|
||||||
* integer numerals. This method returns the "most canonical" representation of the numeric
|
|
||||||
* value represented by {@code s}: it first tries to parse {@code s} as an integer,
|
|
||||||
* attempting to parse {@code s} as a float only when {@code s} is not an integer numeral.</p>
|
|
||||||
*
|
|
||||||
* @param s the string to be parsed, must not be {@code null}
|
|
||||||
* @return the numeric value represented by {@code s}, or {@code null} if {@code s} is not a
|
|
||||||
* numeral
|
|
||||||
*/
|
|
||||||
public static Number tryParseNumeral(String s) {
|
|
||||||
Long l = tryParseInteger(s);
|
|
||||||
return l != null ? l : (Number) tryParseFloat(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isASCIIPrintable(char c) {
|
|
||||||
// ASCII printable character range
|
|
||||||
return c >= 32 && c < 127;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int shortEscape(char c) {
|
|
||||||
switch (c) {
|
|
||||||
case CHAR_BELL:
|
|
||||||
return 'a';
|
|
||||||
case '\b':
|
|
||||||
return 'b';
|
|
||||||
case '\f':
|
|
||||||
return 'f';
|
|
||||||
case '\n':
|
|
||||||
return 'n';
|
|
||||||
case '\r':
|
|
||||||
return 'r';
|
|
||||||
case '\t':
|
|
||||||
return 't';
|
|
||||||
case CHAR_VERTICAL_TAB:
|
|
||||||
return 'v';
|
|
||||||
case '"':
|
|
||||||
return '"';
|
|
||||||
default:
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char toHex(int i) {
|
|
||||||
// i must be between 0x0 and 0xf
|
|
||||||
return i < 0xa ? (char) ((int) '0' + i) : (char) ((int) 'a' + i - 0xa);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string {@code esc} formed from the character sequence {@code s} such that
|
|
||||||
* when {@code esc} is read by a Lua lexer as a string literal, it evaluates to a string equal
|
|
||||||
* to {@code s}. The resulting string is enclosed in double quotes ({@code "}).
|
|
||||||
*
|
|
||||||
* @param s the character sequence to escape, must not be {@code null}
|
|
||||||
* @return a Lua string literal representing {@code s}
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
*/
|
|
||||||
public static String escape(CharSequence s) {
|
|
||||||
Objects.requireNonNull(s);
|
|
||||||
|
|
||||||
StringBuilder bld = new StringBuilder();
|
|
||||||
bld.append('"');
|
|
||||||
|
|
||||||
for (int i = 0; i < s.length(); i++) {
|
|
||||||
char c = s.charAt(i);
|
|
||||||
|
|
||||||
if (c != '\\' && c != '"' && isASCIIPrintable(c)) {
|
|
||||||
bld.append(c);
|
|
||||||
} else {
|
|
||||||
// escaping
|
|
||||||
bld.append('\\');
|
|
||||||
|
|
||||||
int esc = shortEscape(c);
|
|
||||||
|
|
||||||
if (esc != -1) {
|
|
||||||
bld.append((char) esc);
|
|
||||||
} else {
|
|
||||||
if ((int) c <= 0xff) {
|
|
||||||
bld.append('x');
|
|
||||||
bld.append(toHex(((int) c >>> 8) & 0xf));
|
|
||||||
bld.append(toHex((int) c & 0xf));
|
|
||||||
} else {
|
|
||||||
bld.append(Integer.toString((int) c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bld.append('"');
|
|
||||||
return bld.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string {@code esc} formed from the byte string {@code s} such that
|
|
||||||
* when {@code esc} is read by a Lua lexer as a string literal, it evaluates to
|
|
||||||
* a byte string equal to {@code s}. The resulting string is enclosed in double quotes
|
|
||||||
* ({@code "}).
|
|
||||||
*
|
|
||||||
* @param byteString the byte sequence sequence to escape, must not be {@code null}
|
|
||||||
* @return a Lua string literal representing {@code s}
|
|
||||||
* @throws NullPointerException if {@code s} is {@code null}
|
|
||||||
*/
|
|
||||||
public static String escape(ByteString byteString) {
|
|
||||||
return escape(byteString.toRawString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the string {@code s} is a keyword in Lua.
|
|
||||||
*
|
|
||||||
* <p>A keyword in Lua is one of the following strings:
|
|
||||||
* {@code "and"}, {@code "break"}, {@code "do"}, {@code "else"}, {@code "elseif"},
|
|
||||||
* {@code "end"}, {@code "false"}, {@code "for"}, {@code "function"}, {@code "goto"},
|
|
||||||
* {@code "if"}, {@code "in"}, {@code "local"}, {@code "nil"}, {@code "not"},
|
|
||||||
* {@code "or"}, {@code "repeat"}, {@code "return"}, {@code "then"}, {@code "true"},
|
|
||||||
* {@code "until"}, {@code "while"}.</p>
|
|
||||||
*
|
|
||||||
* @param s the string to be examined, may be {@code null}
|
|
||||||
* @return {@code true} if {@code s} is a Lua keyword; {@code false} otherwise
|
|
||||||
*/
|
|
||||||
public static boolean isKeyword(String s) {
|
|
||||||
return s != null && keywords.contains(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the string {@code s} is a valid Lua name.
|
|
||||||
*
|
|
||||||
* <p>According to §3.1 of the Lua Reference Manual,</p>
|
|
||||||
*
|
|
||||||
* <blockquote>
|
|
||||||
* Names (also called identifiers) in Lua can be any string of letters, digits,
|
|
||||||
* and underscores, not beginning with a digit and not being a reserved word.
|
|
||||||
* </blockquote>
|
|
||||||
*
|
|
||||||
* <p>This implementation treats letters as characters in the ranges
|
|
||||||
* {@code 'a'}...{@code 'z'} and {@code 'A'}...{@code 'Z'}, and numbers as characters in
|
|
||||||
* the range {@code '0'}...{@code '9'}.</p>
|
|
||||||
*
|
|
||||||
* @param s the string to be checked for being a valid name, may be {@code null}
|
|
||||||
* @return {@code true} if {@code s} is a valid name in Lua; {@code false} otherwise
|
|
||||||
*/
|
|
||||||
public static boolean isValidName(String s) {
|
|
||||||
if (s == null || s.isEmpty() || isKeyword(s)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char c = s.charAt(0);
|
|
||||||
|
|
||||||
if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i < s.length(); i++) {
|
|
||||||
c = s.charAt(i);
|
|
||||||
if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_') && (c < '0' || c > '9')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,600 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of static methods for performing the equivalents of Lua's arithmetic,
|
|
||||||
* bitwise and numerical comparison operations.
|
|
||||||
*
|
|
||||||
* <p>This class and its methods exploits the isomorphism between Lua integers and
|
|
||||||
* Java {@code long} on the one hand, and between Lua floats and Java {@code double}
|
|
||||||
* on the other.</p>
|
|
||||||
*
|
|
||||||
* <p>For each operation, there are as many variants in the form of a static method
|
|
||||||
* as there are valid type combinations. While all arithmetic operations
|
|
||||||
* are defined in two variants (for two {@code long}s and two {@code double}s, e.g.
|
|
||||||
* {@link #idiv(long, long)} and {@link #idiv(double, double)}), bitwise operations
|
|
||||||
* have single variants (taking two {@code long}s, e.g. {@link #band(long, long)}),
|
|
||||||
* and numerical comparison operations have four variants (for argument type combination).</p>
|
|
||||||
*
|
|
||||||
* <p>It is the task of a Lua implementation to select the appropriate method and
|
|
||||||
* supply it with the required values; however, note that the method selection is well-behaved
|
|
||||||
* under the type conversion rules of the Java programming language.</p>
|
|
||||||
*/
|
|
||||||
public final class LuaMathOperators {
|
|
||||||
|
|
||||||
private static final double MAX_LONG_AS_DOUBLE = (double) Long.MAX_VALUE;
|
|
||||||
|
|
||||||
// Arithmetic operators
|
|
||||||
private static final double MIN_LONG_AS_DOUBLE = (double) Long.MIN_VALUE;
|
|
||||||
|
|
||||||
private LuaMathOperators() {
|
|
||||||
// not to be instantiated or extended
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the addition of two {@code long}s, equivalent to the addition
|
|
||||||
* of two integers in Lua.
|
|
||||||
*
|
|
||||||
* @param a first addend
|
|
||||||
* @param b second addend
|
|
||||||
* @return the value of the Lua expression {@code (a + b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long add(long a, long b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the addition of two {@code double}s, equivalent to the addition
|
|
||||||
* of two floats in Lua.
|
|
||||||
*
|
|
||||||
* @param a first addend
|
|
||||||
* @param b second addend
|
|
||||||
* @return the value of the Lua expression {@code (a + b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* floats
|
|
||||||
*/
|
|
||||||
public static double add(double a, double b) {
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the subtraction of two {@code long}s, equivalent to the subtraction
|
|
||||||
* of two integers in Lua.
|
|
||||||
*
|
|
||||||
* @param a the minuend
|
|
||||||
* @param b the subtrahend
|
|
||||||
* @return the value of the Lua expression {@code (a - b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long sub(long a, long b) {
|
|
||||||
return a - b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the subtraction of two {@code double}s, equivalent to the subtraction
|
|
||||||
* of two floats in Lua.
|
|
||||||
*
|
|
||||||
* @param a the minuend
|
|
||||||
* @param b the subtrahend
|
|
||||||
* @return the value of the Lua expression {@code (a - b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* floats
|
|
||||||
*/
|
|
||||||
public static double sub(double a, double b) {
|
|
||||||
return a - b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the multiplication of two {@code long}s, equivalent to
|
|
||||||
* the multiplication of two integers in Lua.
|
|
||||||
*
|
|
||||||
* @param a first factor
|
|
||||||
* @param b second factor
|
|
||||||
* @return the value of the Lua expression {@code (a * b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long mul(long a, long b) {
|
|
||||||
return a * b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the multiplication of two {@code double}s, equivalent to
|
|
||||||
* the multiplication of two floats in Lua.
|
|
||||||
*
|
|
||||||
* @param a first factor
|
|
||||||
* @param b second factor
|
|
||||||
* @return the value of the Lua expression {@code (a * b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* floats
|
|
||||||
*/
|
|
||||||
public static double mul(double a, double b) {
|
|
||||||
return a * b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the division of two {@code long}s, equivalent to the float
|
|
||||||
* division of two integers in Lua. The result is always a {@code double}; when {@code b}
|
|
||||||
* is zero, the result is <i>NaN</i>.
|
|
||||||
*
|
|
||||||
* <p>Note that this behaviour differs from the standard Java integer division.</p>
|
|
||||||
*
|
|
||||||
* @param a the dividend
|
|
||||||
* @param b the divisor
|
|
||||||
* @return the value of the Lua expression {@code (a / b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static double div(long a, long b) {
|
|
||||||
return ((double) a) / ((double) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the division of two {@code double}s, equivalent to the float
|
|
||||||
* division of two floats in Lua.
|
|
||||||
*
|
|
||||||
* @param a the dividend
|
|
||||||
* @param b the divisor
|
|
||||||
* @return the value of the Lua expression {@code (a / b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* floats
|
|
||||||
*/
|
|
||||||
public static double div(double a, double b) {
|
|
||||||
return a / b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the floor modulus of two {@code long}s, equivalent to the modulus
|
|
||||||
* of two integers in Lua.
|
|
||||||
*
|
|
||||||
* <p>Note that in Lua,</p>
|
|
||||||
*
|
|
||||||
* <blockquote>
|
|
||||||
* Modulo is defined as the remainder of a division that rounds the quotient
|
|
||||||
* towards minus infinity (floor division).
|
|
||||||
* </blockquote>
|
|
||||||
*
|
|
||||||
* <p>This definition is <i>not</i> equivalent to the standard Java definition of modulo
|
|
||||||
* (which is the remainder of a division rounding toward <i>zero</i>).</p>
|
|
||||||
*
|
|
||||||
* @param a the dividend
|
|
||||||
* @param b the divisor
|
|
||||||
* @return the value of the Lua expression {@code (a % b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
* @throws ArithmeticException if {@code b} is zero
|
|
||||||
*/
|
|
||||||
public static long mod(long a, long b) {
|
|
||||||
// Note: in JDK 8+, Math.floorMod could be used
|
|
||||||
if (b == 0) {
|
|
||||||
throw new ArithmeticException("attempt to perform 'n%0'");
|
|
||||||
} else {
|
|
||||||
return a - b * (long) Math.floor((double) a / (double) b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the floor modulus of two {@code double}s, equivalent to the modulus
|
|
||||||
* of two floats in Lua.
|
|
||||||
*
|
|
||||||
* <p>Note that in Lua,</p>
|
|
||||||
*
|
|
||||||
* <blockquote>
|
|
||||||
* Modulo is defined as the remainder of a division that rounds the quotient
|
|
||||||
* towards minus infinity (floor division).
|
|
||||||
* </blockquote>
|
|
||||||
*
|
|
||||||
* <p>This definition is <i>not</i> equivalent to the standard Java definition of modulo
|
|
||||||
* (which is the remainder of a division rounding toward <i>zero</i>).</p>
|
|
||||||
*
|
|
||||||
* @param a the dividend
|
|
||||||
* @param b the divisor
|
|
||||||
* @return the value of the Lua expression {@code (a % b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* floats
|
|
||||||
*/
|
|
||||||
public static double mod(double a, double b) {
|
|
||||||
return b != 0 ? a - b * Math.floor(a / b) : Double.NaN;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of floor division of two {@code long}s, equivalent to the Lua
|
|
||||||
* floor division of two integers.
|
|
||||||
*
|
|
||||||
* <p>In Lua,</p>
|
|
||||||
*
|
|
||||||
* <blockquote>
|
|
||||||
* Floor division (//) is a division that rounds the quotient towards minus infinity,
|
|
||||||
* that is, the floor of the division of its operands.
|
|
||||||
* </blockquote>
|
|
||||||
*
|
|
||||||
* @param a the dividend
|
|
||||||
* @param b the divisor
|
|
||||||
* @return the value of the Lua expression {@code (a // b)} where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
* @throws ArithmeticException if {@code b} is zero
|
|
||||||
*/
|
|
||||||
public static long idiv(long a, long b) {
|
|
||||||
if (b == 0) {
|
|
||||||
throw new ArithmeticException("attempt to divide by zero");
|
|
||||||
} else {
|
|
||||||
long q = a / b;
|
|
||||||
return q * b == a || (a ^ b) >= 0 ? q : q - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of floor division of two {@code double}s, equivalent to the Lua
|
|
||||||
* floor division of two floats:
|
|
||||||
*
|
|
||||||
* <p>In Lua,</p>
|
|
||||||
*
|
|
||||||
* <blockquote>
|
|
||||||
* Floor division (//) is a division that rounds the quotient towards minus infinity,
|
|
||||||
* that is, the floor of the division of its operands.
|
|
||||||
* </blockquote>
|
|
||||||
*
|
|
||||||
* @param a the dividend
|
|
||||||
* @param b the divisor
|
|
||||||
* @return the value of the Lua expression {@code (a // b)} where {@code a} and {@code b} are Lua
|
|
||||||
* floats
|
|
||||||
*/
|
|
||||||
public static double idiv(double a, double b) {
|
|
||||||
return Math.floor(a / b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the exponentiation of two {@code long}s, equivalent to the Lua
|
|
||||||
* exponentiation of two integers. Note that the resulting value is a {@code double}.
|
|
||||||
*
|
|
||||||
* @param a the base
|
|
||||||
* @param b the exponent
|
|
||||||
* @return the value of the Lua expression {@code (a ^ b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static double pow(long a, long b) {
|
|
||||||
return Math.pow((double) a, (double) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the exponentiation of two {@code double}s, equivalent to Lua
|
|
||||||
* exponentiation of two floats.
|
|
||||||
*
|
|
||||||
* @param a the base
|
|
||||||
* @param b the exponent
|
|
||||||
* @return the value of the Lua expression {@code (a ^ b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* floats
|
|
||||||
*/
|
|
||||||
public static double pow(double a, double b) {
|
|
||||||
return Math.pow(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bitwise operators
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the (arithmetic) negation of a {@code long}, equivalent to
|
|
||||||
* the Lua unary minus on an integer.
|
|
||||||
*
|
|
||||||
* @param n the operand
|
|
||||||
* @return the value of the Lua expression {@code (-n)}, where {@code n} is a Lua integer
|
|
||||||
*/
|
|
||||||
public static long unm(long n) {
|
|
||||||
return -n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the (arithmetic) negation of a {@code long}, equivalent to
|
|
||||||
* the Lua unary minus on a float.
|
|
||||||
*
|
|
||||||
* @param n the operand
|
|
||||||
* @return the value of the Lua expression {@code (-n)}, where {@code n} is a Lua float
|
|
||||||
*/
|
|
||||||
public static double unm(double n) {
|
|
||||||
return -n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the bitwise AND of two {@code long}s, equivalent to the Lua
|
|
||||||
* bitwise AND of two integers.
|
|
||||||
*
|
|
||||||
* @param a the first operand
|
|
||||||
* @param b the second operand
|
|
||||||
* @return the value of the Lua expression {@code (a & b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long band(long a, long b) {
|
|
||||||
return a & b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the bitwise OR of two {@code long}s, equivalent to the Lua
|
|
||||||
* bitwise OR of two integers.
|
|
||||||
*
|
|
||||||
* @param a the first operand
|
|
||||||
* @param b the second operand
|
|
||||||
* @return the value of the Lua expression {@code (a | b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long bor(long a, long b) {
|
|
||||||
return a | b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the bitwise exclusive OR of two {@code long}s,
|
|
||||||
* equivalent to the Lua bitwise exclusive OR of two integers.
|
|
||||||
*
|
|
||||||
* @param a the first operand
|
|
||||||
* @param b the second operand
|
|
||||||
* @return the value of the Lua expression {@code (a ~ b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long bxor(long a, long b) {
|
|
||||||
return a ^ b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the (bitwise) logical left shift, equivalent to the Lua bitwise
|
|
||||||
* left shift on two integers. Vacant bits are filled with zeros. When {@code b} is negative,
|
|
||||||
* the result is equal to the result of a right shift by {@code -b}.
|
|
||||||
*
|
|
||||||
* <p>Note that Lua's behaviour differs from Java's {@code <<} operator in that if
|
|
||||||
* {@code b} is greater than 64, the result is zero, as all bits have been shifted out.</p>
|
|
||||||
*
|
|
||||||
* @param a the left-hand side operand
|
|
||||||
* @param b the right-hand side operand (shift distance)
|
|
||||||
* @return the value of the Lua expression {@code (a << b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long shl(long a, long b) {
|
|
||||||
return b < 0 ? shr(a, -b) : (b < 64 ? a << b : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numerical comparison operators
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the (bitwise) logical right shift, equivalent to the Lua bitwise
|
|
||||||
* right shift on two integers. Vacant bits are filled with zeros. When {@code b} is negative,
|
|
||||||
* the result is equal to the result of a left shift by {@code -b}.
|
|
||||||
*
|
|
||||||
* <p>Note that Lua's behaviour differs from Java's {@code >>>} operator in that if
|
|
||||||
* {@code b} is greater than 64, the result is zero, as all bits have been shifted out.</p>
|
|
||||||
*
|
|
||||||
* @param a the left-hand side operand
|
|
||||||
* @param b the right-hand side operand (shift distance)
|
|
||||||
* @return the value of the Lua expression {@code (a << b)}, where {@code a} and {@code b} are Lua
|
|
||||||
* integers
|
|
||||||
*/
|
|
||||||
public static long shr(long a, long b) {
|
|
||||||
return b < 0 ? shl(a, -b) : (b < 64 ? a >>> b : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the bitwise unary NOT of a {@code long}, equivalent to the Lua
|
|
||||||
* bitwise unary NOT on an integer.
|
|
||||||
*
|
|
||||||
* @param n the operand
|
|
||||||
* @return the value of the Lua expression {@code (~b)}, where {@code n} is a Lua integer
|
|
||||||
*/
|
|
||||||
public static long bnot(long n) {
|
|
||||||
return ~n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the {@code double} {@code d} can be represented by
|
|
||||||
* a {@code long} without the loss of precision, i.e. if {@code ((long) d)}
|
|
||||||
* and {@code d} denote the same mathematical value.
|
|
||||||
*
|
|
||||||
* @param d the {@code double} in question
|
|
||||||
* @return {@code true} iff {@code d} can be represented by a {@code long} without the loss of
|
|
||||||
* precision
|
|
||||||
*/
|
|
||||||
public static boolean hasExactIntegerRepresentation(double d) {
|
|
||||||
long l = (long) d;
|
|
||||||
return (double) l == d && l != Long.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the {@code long} {@code l} can be represented by
|
|
||||||
* a {@code double} without the loss of precision, i.e. if {@code ((double) l)}
|
|
||||||
* and {@code l} denote the same mathematical value.
|
|
||||||
*
|
|
||||||
* @param l the {@code long} in question
|
|
||||||
* @return {@code true} iff {@code l} can be represented by a {@code double} without the loss of
|
|
||||||
* precision
|
|
||||||
*/
|
|
||||||
public static boolean hasExactFloatRepresentation(long l) {
|
|
||||||
double d = (double) l;
|
|
||||||
return (long) d == l && l != Long.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the {@code long}s {@code a} and {@code b} denote
|
|
||||||
* the same mathematical value. This is equivalent to the Lua numerical equality
|
|
||||||
* comparison of two integers.
|
|
||||||
*
|
|
||||||
* @param a a {@code long}
|
|
||||||
* @param b a {@code long} to be compared with {@code a} for mathematical equality
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} and {@code b} are
|
|
||||||
* Lua integers, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean eq(long a, long b) {
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the {@code long} {@code a} denotes the same mathematical
|
|
||||||
* value as the {@code double} {@code b}. This is equivalent to the Lua numerical
|
|
||||||
* equality comparison of an integer and a float.
|
|
||||||
*
|
|
||||||
* @param a a {@code long}
|
|
||||||
* @param b a {@code double} to be compared with {@code a} for mathematical equality
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} is a Lua integer
|
|
||||||
* and {@code b} is a Lua float, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean eq(long a, double b) {
|
|
||||||
return hasExactFloatRepresentation(a) && (double) a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the {@code double} {@code a} denotes the same mathematical
|
|
||||||
* value as the {@code long} {@code b}. This is equivalent to the Lua numerical equality
|
|
||||||
* comparison of a float and an integer.
|
|
||||||
*
|
|
||||||
* @param a a {@code double}
|
|
||||||
* @param b a {@code long} to be compared with {@code a} for mathematical equality
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} is a Lua float
|
|
||||||
* and {@code b} is a Lua integer, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean eq(double a, long b) {
|
|
||||||
return hasExactFloatRepresentation(b) && a == (double) b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the {@code double}s {@code a} and {@code b} denote
|
|
||||||
* the same mathematical value. This is equivalent to the Lua numerical equality
|
|
||||||
* comparison of two doubles.
|
|
||||||
*
|
|
||||||
* @param a a {@code double}
|
|
||||||
* @param b a {@code double} to be compared with {@code a} for mathematical equality
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a == b)}, where {@code a} and {@code b} are
|
|
||||||
* Lua floats, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean eq(double a, double b) {
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code long} {@code a}
|
|
||||||
* is lesser than the mathematical value of the {@code long} {@code b}. This is equivalent
|
|
||||||
* to the Lua numerical lesser-than comparison of two integers.
|
|
||||||
*
|
|
||||||
* @param a a {@code long}
|
|
||||||
* @param b a {@code long} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} and {@code b} are
|
|
||||||
* Lua integers, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean lt(long a, long b) {
|
|
||||||
return a < b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code long} {@code a}
|
|
||||||
* is lesser than the mathematical value of the {@code double} {@code b}. This
|
|
||||||
* is equivalent to the Lua numerical lesser-than comparison of an integer and a float.
|
|
||||||
*
|
|
||||||
* @param a a {@code long}
|
|
||||||
* @param b a {@code double} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} is a Lua integer
|
|
||||||
* and {@code b} is a Lua float, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean lt(long a, double b) {
|
|
||||||
if (hasExactFloatRepresentation(a)) {
|
|
||||||
return (double) a < b;
|
|
||||||
} else {
|
|
||||||
return !Double.isNaN(b) && b > MIN_LONG_AS_DOUBLE && (b >= MAX_LONG_AS_DOUBLE
|
|
||||||
|| a < (long) b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code double} {@code a}
|
|
||||||
* is lesser than the mathematical value of the {@code long} {@code b}. This
|
|
||||||
* is equivalent to the Lua numerical lesser-than comparison of a float and an integer.
|
|
||||||
*
|
|
||||||
* @param a a {@code double}
|
|
||||||
* @param b a {@code long} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} is a Lua float and
|
|
||||||
* {@code b} is a Lua integer, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean lt(double a, long b) {
|
|
||||||
return !Double.isNaN(a) && !le(b, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code double} {@code a}
|
|
||||||
* is lesser than the mathematical value of the {@code double} {@code b}. This
|
|
||||||
* is equivalent to the Lua numerical lesser-than comparison of two floats.
|
|
||||||
*
|
|
||||||
* @param a a {@code double}
|
|
||||||
* @param b a {@code double} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a < b)}, where {@code a} and {@code b} are
|
|
||||||
* Lua floats, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean lt(double a, double b) {
|
|
||||||
return a < b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code long} {@code a}
|
|
||||||
* is lesser than or equal to the mathematical value of the {@code long} {@code b}.
|
|
||||||
* This is equivalent to the Lua numerical lesser-than-or-equal comparison of
|
|
||||||
* two integers.
|
|
||||||
*
|
|
||||||
* @param a a {@code long}
|
|
||||||
* @param b a {@code long} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} and {@code b} are
|
|
||||||
* Lua integers, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean le(long a, long b) {
|
|
||||||
return a <= b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code long} {@code a}
|
|
||||||
* is lesser than or equal to the mathematical value of the {@code double} {@code b}.
|
|
||||||
* This is equivalent to the Lua numerical lesser-than-or-equal comparison of
|
|
||||||
* an integer and a float.
|
|
||||||
*
|
|
||||||
* @param a a {@code long}
|
|
||||||
* @param b a {@code double} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} is a Lua integer
|
|
||||||
* and {@code b} is a Lua float, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean le(long a, double b) {
|
|
||||||
if (hasExactFloatRepresentation(a)) {
|
|
||||||
return (double) a <= b;
|
|
||||||
} else {
|
|
||||||
return !Double.isNaN(b) && b > MIN_LONG_AS_DOUBLE && (b >= MAX_LONG_AS_DOUBLE
|
|
||||||
|| a <= (long) b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code double} {@code a}
|
|
||||||
* is lesser than or equal to the mathematical value of the {@code long} {@code b}.
|
|
||||||
* This is equivalent to the Lua numerical lesser-than-or-equal comparison of
|
|
||||||
* a float and an integer.
|
|
||||||
*
|
|
||||||
* @param a a {@code double}
|
|
||||||
* @param b a {@code long} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} is a Lua float
|
|
||||||
* and {@code b} is a Lua integer, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean le(double a, long b) {
|
|
||||||
return !Double.isNaN(a) && !lt(b, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value of the {@code double} {@code a}
|
|
||||||
* is lesser than or equal to the mathematical value of the {@code double} {@code b}.
|
|
||||||
* This is equivalent to the Lua numerical lesser-than-or-equal comparison of
|
|
||||||
* two floats.
|
|
||||||
*
|
|
||||||
* @param a a {@code double}
|
|
||||||
* @param b a {@code double} to be compared with {@code a}
|
|
||||||
* @return {@code true} iff the Lua expression {@code (a <= b)}, where {@code a} and {@code b} are
|
|
||||||
* Lua floats, would evaluate to (Lua) <b>true</b>
|
|
||||||
*/
|
|
||||||
public static boolean le(double a, double b) {
|
|
||||||
return a <= b;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class of objects that have a metatable attached to them on a per-instance basis.
|
|
||||||
*/
|
|
||||||
public abstract class LuaObject {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable of this object, or {@code null} if this object does not have
|
|
||||||
* a metatable.
|
|
||||||
*
|
|
||||||
* @return this object's metatable, or {@code null} if this object does not have a metatable
|
|
||||||
*/
|
|
||||||
public abstract Table getMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable of this object to {@code mt}. {@code mt} may be {@code null}:
|
|
||||||
* in that case, removes the metatable from this object.
|
|
||||||
*
|
|
||||||
* <p>Returns the metatable previously associated with this object (i.e., the metatable
|
|
||||||
* before the call of this method; possibly {@code null}).</p>
|
|
||||||
*
|
|
||||||
* @param mt new metatable to attach to this object, may be {@code null}
|
|
||||||
* @return previous metatable associated with this object
|
|
||||||
*/
|
|
||||||
public abstract Table setMetatable(Table mt);
|
|
||||||
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for runtime exceptions that carry arbitrary <i>error objects</i>
|
|
||||||
* attached to them.
|
|
||||||
*
|
|
||||||
* <p>To retrieve the error object, use {@link #getErrorObject()}.</p>
|
|
||||||
*/
|
|
||||||
public class LuaRuntimeException extends RuntimeException {
|
|
||||||
|
|
||||||
private final Object errorObject;
|
|
||||||
|
|
||||||
private LuaRuntimeException(Throwable cause, Object errorObject) {
|
|
||||||
super(cause);
|
|
||||||
this.errorObject = errorObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@code LuaRuntimeException} with {@code errorObject} as its
|
|
||||||
* error object. {@code errorObject} may be {@code null}.
|
|
||||||
*
|
|
||||||
* @param errorObject the error object, may be {@code null}
|
|
||||||
*/
|
|
||||||
public LuaRuntimeException(Object errorObject) {
|
|
||||||
this(null, errorObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@code LuaRuntimeException} with {@code cause} as its cause.
|
|
||||||
*
|
|
||||||
* <p>When queried for the error object, invokes {@link Conversions#toErrorObject(Throwable)}
|
|
||||||
* on {@code cause}; when {@code cause} is {@code null}, then the error object
|
|
||||||
* is {@code null}.</p>
|
|
||||||
*
|
|
||||||
* @param cause the cause of this error, may be {@code null}
|
|
||||||
*/
|
|
||||||
public LuaRuntimeException(Throwable cause) {
|
|
||||||
this(cause, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the error object attached to this exception converted to a string.
|
|
||||||
*
|
|
||||||
* @return error object converted to a string
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return getErrorLocation() + Conversions.toHumanReadableString(getErrorObject()).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the error object attached to this exception. The error object may be {@code null}.
|
|
||||||
*
|
|
||||||
* @return the error object attached to this exception (possibly {@code null})
|
|
||||||
*/
|
|
||||||
public Object getErrorObject() {
|
|
||||||
Throwable cause = getCause();
|
|
||||||
if (cause != null) {
|
|
||||||
return Conversions.toErrorObject(cause);
|
|
||||||
} else {
|
|
||||||
return errorObject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the closest location in the Lua code when this exception was triggered.
|
|
||||||
*
|
|
||||||
* @return the location of this error in the Lua code, @{code file:line} or @{code unknown:-1} if
|
|
||||||
* it could not be determined
|
|
||||||
*/
|
|
||||||
public String getErrorLocation() {
|
|
||||||
for (StackTraceElement stackTraceElement : getStackTrace()) {
|
|
||||||
if (stackTraceElement.getClassName().startsWith("luna_dynamic")) {
|
|
||||||
return stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber() + ": ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,294 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import org.classdump.luna.runtime.Coroutine;
|
|
||||||
import org.classdump.luna.runtime.LuaFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An enum representing a Lua type.
|
|
||||||
*
|
|
||||||
* <p>There are eight types in Lua ({@code nil}, {@code boolean}, {@code number}, {@code string},
|
|
||||||
* {@code function}, {@code userdata}, {@code thread} and {@code table}). In Luna,
|
|
||||||
* all Java object references are mapped to a Lua type according to the following list:</p>
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #NIL} ... no explicit {@code nil} type; any (Java) {@code null} value is
|
|
||||||
* considered <b>nil</b></li>
|
|
||||||
* <li>{@link #BOOLEAN} ... instances of {@link Boolean java.lang.Boolean}</li>
|
|
||||||
* <li>{@link #NUMBER} ... instances of {@link Number java.lang.Number}:
|
|
||||||
* <ul>
|
|
||||||
* <li><i>float</i> ... {@link Double java.lang.Double} (canonical)
|
|
||||||
* or {@link Float java.lang.Float}</li>
|
|
||||||
* <li><i>integer</i> ... any other subclass of {@code java.lang.Number},
|
|
||||||
* with {@link Long java.lang.Long} being the canonical
|
|
||||||
* representation</li>
|
|
||||||
* </ul>
|
|
||||||
* </li>
|
|
||||||
* <li>{@link #STRING} ... instances of {@link String java.lang.String}</li>
|
|
||||||
* <li>{@link #FUNCTION} ... {@link LuaFunction}</li>
|
|
||||||
* <li>{@link #USERDATA}:
|
|
||||||
* <ul>
|
|
||||||
* <li><i>full userdata</i> ... {@link Userdata}</li>
|
|
||||||
* <li><i>light userdata</i> ... instances of any class other than those mentioned
|
|
||||||
* in this list</li>
|
|
||||||
* </ul>
|
|
||||||
* </li>
|
|
||||||
* <li>{@link #THREAD} ... {@link Coroutine}</li>
|
|
||||||
* <li>{@link #TABLE} ... {@link Table}</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>For numeric values, the <i>canonical representation</i> is the default, full-precision
|
|
||||||
* representation of the number as either a float or integer. To convert a number to its
|
|
||||||
* canonical value, use {@link Conversions#toCanonicalNumber(Number)}.</p>
|
|
||||||
*
|
|
||||||
* <p>To retrieve the name of the type of a Lua value, use the {@link ValueTypeNamer}
|
|
||||||
* interface (for names based on the type only, without taking into account the {@code __name}
|
|
||||||
* metamethod, use {@link PlainValueTypeNamer}).</p>
|
|
||||||
*/
|
|
||||||
public enum LuaType {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code nil} type, corresponding to {@code null} references.
|
|
||||||
*/
|
|
||||||
NIL,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code boolean} type, corresponding to instances
|
|
||||||
* of {@link Boolean java.lang.Boolean}.
|
|
||||||
*/
|
|
||||||
BOOLEAN,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code number} type, corresponding to instances of {@link Number java.lang.Number}.
|
|
||||||
*
|
|
||||||
* <p>Instances of {@link Double java.lang.Double} and {@link Float java.lang.Float}
|
|
||||||
* are mapped to Lua floats ({@code Double}s being the canonical representation). All other
|
|
||||||
* subclasses of {@code Number} are mapped to Lua integers, with {@link Long java.lang.Long}
|
|
||||||
* being the canonical representation.</p>
|
|
||||||
*/
|
|
||||||
NUMBER,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code string} type, corresponding to instances of {@link ByteString}
|
|
||||||
* and {@link String java.lang.String}.
|
|
||||||
*/
|
|
||||||
STRING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code function} type, corresponding to instances of {@link LuaFunction}.
|
|
||||||
*/
|
|
||||||
FUNCTION,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code userdata} type, corresponding to instances of {@link Userdata} (for full
|
|
||||||
* userdata), or any other subclasses of {@link java.lang.Object} not mapped to a Lua
|
|
||||||
* type (for light userdata).
|
|
||||||
*/
|
|
||||||
USERDATA,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code thread} type, corresponding to instances of the {@link Coroutine} class.
|
|
||||||
*/
|
|
||||||
THREAD,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Lua {@code table} type, corresponding to instances of {@link Table}.
|
|
||||||
*/
|
|
||||||
TABLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Lua type of the object {@code o}.
|
|
||||||
*
|
|
||||||
* @param o the object to determine the type of, may be {@code null}
|
|
||||||
* @return the Lua type of {@code o}
|
|
||||||
*/
|
|
||||||
public static LuaType typeOf(Object o) {
|
|
||||||
if (o == null) {
|
|
||||||
return LuaType.NIL;
|
|
||||||
} else if (o instanceof Boolean) {
|
|
||||||
return LuaType.BOOLEAN;
|
|
||||||
} else if (o instanceof Number) {
|
|
||||||
return LuaType.NUMBER;
|
|
||||||
} else if (o instanceof ByteString || o instanceof String) {
|
|
||||||
return LuaType.STRING;
|
|
||||||
} else if (o instanceof Table) {
|
|
||||||
return LuaType.TABLE;
|
|
||||||
} else if (o instanceof LuaFunction) {
|
|
||||||
return LuaType.FUNCTION;
|
|
||||||
} else if (o instanceof Coroutine) {
|
|
||||||
return LuaType.THREAD;
|
|
||||||
} else {
|
|
||||||
return LuaType.USERDATA;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is <b>nil</b>.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is <b>nil</b> if and only if {@code o} is {@code null}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being <b>nil</b>, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is <b>nil</b>
|
|
||||||
*/
|
|
||||||
public static boolean isNil(Object o) {
|
|
||||||
return o == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is a Lua boolean.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua boolean if and only if {@code o} is an instance of
|
|
||||||
* {@link Boolean java.lang.Boolean}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a boolean, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua boolean
|
|
||||||
*/
|
|
||||||
public static boolean isBoolean(Object o) {
|
|
||||||
return o instanceof Boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is a Lua number.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua number if and only if {@code o} is an instance of
|
|
||||||
* {@link Number java.lang.Number}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a number, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua number
|
|
||||||
*/
|
|
||||||
public static boolean isNumber(Object o) {
|
|
||||||
return o instanceof Number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is a Lua float.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua float if and only if {@code o} is an instance of
|
|
||||||
* {@link Double java.lang.Double} or {@link Float java.lang.Float}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a float, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua float
|
|
||||||
*/
|
|
||||||
public static boolean isFloat(Object o) {
|
|
||||||
return o instanceof Double || o instanceof Float;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is a Lua integer.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua number if and only if {@code o} is a Lua number and is not
|
|
||||||
* a Lua float.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being an integer, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua integer
|
|
||||||
*/
|
|
||||||
public static boolean isInteger(Object o) {
|
|
||||||
return isNumber(o) && !isFloat(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is a Lua string.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua string if and only if {@code o} is an instance of
|
|
||||||
* {@link ByteString} or {@link String java.lang.String}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a string, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua string
|
|
||||||
*/
|
|
||||||
public static boolean isString(Object o) {
|
|
||||||
return o instanceof ByteString || o instanceof String;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is a Lua function.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua function if and only if {@code o} is an instance of
|
|
||||||
* {@link LuaFunction}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a function, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua function
|
|
||||||
*/
|
|
||||||
public static boolean isFunction(Object o) {
|
|
||||||
return o instanceof LuaFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is a Lua userdata.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua userdata if it is not {@code nil}, {@code boolean}, {@code number},
|
|
||||||
* {@code string}, {@code function}, {@code thread} or {@code table}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a userdata, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua userdata
|
|
||||||
*/
|
|
||||||
public static boolean isUserdata(Object o) {
|
|
||||||
return typeOf(o) == USERDATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code o} is full userdata.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is full userdata if and only if {@code o} is an instance of
|
|
||||||
* {@link Userdata}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being full userdata, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is full userdata
|
|
||||||
*/
|
|
||||||
public static boolean isFullUserdata(Object o) {
|
|
||||||
return o instanceof Userdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the object {@code o} is light userdata.
|
|
||||||
*
|
|
||||||
* <p>An object is light userdata when its Lua type is {@link #USERDATA} and it
|
|
||||||
* is not an instance of the {@link Userdata} class. In other words, it is not an
|
|
||||||
* instance of any class mapped to a Lua type.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being light userdata, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is light userdata
|
|
||||||
*/
|
|
||||||
public static boolean isLightUserdata(Object o) {
|
|
||||||
return !isFullUserdata(o) && isUserdata(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the object {@code o} is a Lua thread.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua thread if and only if {@code o} is an instance of {@link Coroutine}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a Lua thread, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua thread
|
|
||||||
*/
|
|
||||||
public static boolean isThread(Object o) {
|
|
||||||
return o instanceof Coroutine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the object {@code o} is a Lua table.
|
|
||||||
*
|
|
||||||
* <p>{@code o} is a Lua table if and only if {@code o} is an instance of {@link Table}.</p>
|
|
||||||
*
|
|
||||||
* @param o the object to test for being a Lua table, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code o} is a Lua table
|
|
||||||
*/
|
|
||||||
public static boolean isTable(Object o) {
|
|
||||||
return o instanceof Table;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A metatable accessor, an interface for getting and setting object metatables.
|
|
||||||
*
|
|
||||||
* <p>In Lua, only tables and (full) userdata carry their own metatables; for all other
|
|
||||||
* types of values <i>T</i>, all values of type <i>T</i> share a metatable. This interface
|
|
||||||
* provides a uniform setter for metatables of all types.</p>
|
|
||||||
*/
|
|
||||||
public interface MetatableAccessor extends MetatableProvider {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable for <b>nil</b> (i.e., the {@code nil} type) to {@code table}.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears the metatable. Returns
|
|
||||||
* the previous metatable.
|
|
||||||
*
|
|
||||||
* @param table new metatable for the {@code nil} type, may be {@code null}
|
|
||||||
* @return the previous metatable for the {@code nil} type
|
|
||||||
*/
|
|
||||||
Table setNilMetatable(Table table);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable for the {@code boolean} type.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears the metatable. Returns
|
|
||||||
* the previous metatable.
|
|
||||||
*
|
|
||||||
* @param table new metatable for the {@code boolean} type, may be {@code null}
|
|
||||||
* @return the previous metatable for the {@code boolean} type
|
|
||||||
*/
|
|
||||||
Table setBooleanMetatable(Table table);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable for the {@code number} type.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears the metatable. Returns
|
|
||||||
* the previous metatable.
|
|
||||||
*
|
|
||||||
* @param table new metatable for the {@code number} type, may be {@code null}
|
|
||||||
* @return the previous metatable for the {@code number} type
|
|
||||||
*/
|
|
||||||
Table setNumberMetatable(Table table);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable for the {@code string} type.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears the metatable. Returns
|
|
||||||
* the previous metatable.
|
|
||||||
*
|
|
||||||
* @param table new metatable for the {@code string} type, may be {@code null}
|
|
||||||
* @return the previous metatable for the {@code string} type
|
|
||||||
*/
|
|
||||||
Table setStringMetatable(Table table);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable for the {@code function} type.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears the metatable. Returns
|
|
||||||
* the previous metatable.
|
|
||||||
*
|
|
||||||
* @param table new metatable for the {@code function} type, may be {@code null}
|
|
||||||
* @return the previous metatable for the {@code function} type
|
|
||||||
*/
|
|
||||||
Table setFunctionMetatable(Table table);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable for the {@code thread} type.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears the metatable. Returns
|
|
||||||
* the previous metatable.
|
|
||||||
*
|
|
||||||
* @param table new metatable for the {@code thread} type, may be {@code null}
|
|
||||||
* @return the previous metatable for the {@code thread} type
|
|
||||||
*/
|
|
||||||
Table setThreadMetatable(Table table);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable for light userdata.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears the metatable. Returns
|
|
||||||
* the previous metatable.
|
|
||||||
*
|
|
||||||
* @param table new metatable for light userdata, may be {@code null}
|
|
||||||
* @return the previous metatable for light userdata
|
|
||||||
*/
|
|
||||||
Table setLightUserdataMetatable(Table table);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable of the object {@code instance} to {@code table}.
|
|
||||||
* {@code table} may be {@code null}: in that case, clears {@code instance}'s metatable.
|
|
||||||
* Returns the previous metatable.
|
|
||||||
*
|
|
||||||
* <p>Note that {@code instance} may share the metatable with other instances of the same
|
|
||||||
* (Lua) type. This method provides a uniform interface for setting the metatables
|
|
||||||
* of all types.</p>
|
|
||||||
*
|
|
||||||
* @param instance object to set the metatable of, may be {@code null}
|
|
||||||
* @param table new metatable of {@code instance}, may be {@code null}
|
|
||||||
* @return the previous metatable of {@code instance}
|
|
||||||
*/
|
|
||||||
Table setMetatable(Object instance, Table table);
|
|
||||||
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for obtaining value metatables.
|
|
||||||
*
|
|
||||||
* <p>In Lua, only tables and (full) userdata carry their own metatables; for all other
|
|
||||||
* types of values <i>T</i>, all values of type <i>T</i> share a metatable. This interface
|
|
||||||
* provides uniform access to metatables of all types.</p>
|
|
||||||
*/
|
|
||||||
public interface MetatableProvider {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for <b>nil</b> (the {@code nil} type), or {@code null} if this
|
|
||||||
* provider does not assign a metatable to the {@code nil} type.
|
|
||||||
*
|
|
||||||
* @return the metatable for the {@code nil} type
|
|
||||||
*/
|
|
||||||
Table getNilMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for {@code boolean} values, or {@code null} if this provider does
|
|
||||||
* not assign a metatable to the {@code boolean} type.
|
|
||||||
*
|
|
||||||
* @return the metatable for the {@code boolean} type
|
|
||||||
*/
|
|
||||||
Table getBooleanMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for {@code number} values, or {@code null} if this provider does
|
|
||||||
* not assign a metatable to the {@code number} type.
|
|
||||||
*
|
|
||||||
* @return the metatable for the {@code number} type
|
|
||||||
*/
|
|
||||||
Table getNumberMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for {@code string} values, or {@code null} if this provider does
|
|
||||||
* not assign a metatable to the {@code string} type.
|
|
||||||
*
|
|
||||||
* @return the metatable for the {@code string} type
|
|
||||||
*/
|
|
||||||
Table getStringMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for {@code function} values, or {@code null} if this provider does
|
|
||||||
* not assign a metatable to the {@code function} type.
|
|
||||||
*
|
|
||||||
* @return the metatable for the {@code function} type
|
|
||||||
*/
|
|
||||||
Table getFunctionMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for {@code thread} values, or {@code null} if this provider does
|
|
||||||
* not assign a metatable to the {@code thread} type.
|
|
||||||
*
|
|
||||||
* @return the metatable for the {@code thread} type
|
|
||||||
*/
|
|
||||||
Table getThreadMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for light userdata, or {@code null} if this provider does
|
|
||||||
* not assign a metatable to light userdata..
|
|
||||||
*
|
|
||||||
* @return the metatable for light userdata
|
|
||||||
*/
|
|
||||||
Table getLightUserdataMetatable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable for the object {@code instance}, or {@code null} if this
|
|
||||||
* metatable provider does not assign any metatable to {@code instance}.
|
|
||||||
*
|
|
||||||
* @param instance the object to obtain a metatable for, may be {@code null}
|
|
||||||
* @return the metatable of {@code instance}, or {@code null} if there is no metatable assigned to
|
|
||||||
* {@code instance} in this provider
|
|
||||||
*/
|
|
||||||
Table getMetatable(Object instance);
|
|
||||||
|
|
||||||
}
|
|
@ -1,226 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metatable keys and utilities.
|
|
||||||
*/
|
|
||||||
public final class Metatables {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__add"}. When defined, customises the behaviour of
|
|
||||||
* the Lua addition operator ({@code +}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_ADD = ByteString.constOf("__add");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__sub"}. When defined, customises the behaviour of
|
|
||||||
* the Lua subtraction operator (binary {@code -}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_SUB = ByteString.constOf("__sub");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__mul"}. When defined, customises the behaviour of
|
|
||||||
* the Lua multiplication operator ({@code *}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_MUL = ByteString.constOf("__mul");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__div"}. When defined, customises the behaviour of
|
|
||||||
* the Lua division operator ({@code /}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_DIV = ByteString.constOf("__div");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__mod"}. When defined, customises the behaviour of
|
|
||||||
* the Lua modulo operator ({@code %}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_MOD = ByteString.constOf("__mod");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__pow"}. When defined, customises the behaviour of
|
|
||||||
* the Lua exponentiation operator ({@code ^}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_POW = ByteString.constOf("__pow");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__unm"}. When defined, customises the behaviour of
|
|
||||||
* the Lua unary minus operator (unary {@code -}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_UNM = ByteString.constOf("__unm");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__idiv"}. When defined, customises the behaviour of
|
|
||||||
* the Lua floor division ({@code //}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_IDIV = ByteString.constOf("__idiv");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__band"}. When defined, customises the behaviour of
|
|
||||||
* the Lua bitwise AND operator ({@code &}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_BAND = ByteString.constOf("__band");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__bor"}. When defined, customises the behaviour of
|
|
||||||
* the Lua bitwise OR operator ({@code |}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_BOR = ByteString.constOf("__bor");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__bxor"}. When defined, customises the behaviour of
|
|
||||||
* the Lua bitwise XOR operator (binary {@code ~}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_BXOR = ByteString.constOf("__bxor");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__bnot"}. When defined, customises the behaviour of
|
|
||||||
* the Lua bitwise NOT operator (unary {@code ~}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_BNOT = ByteString.constOf("__bnot");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__shl"}. When defined, customises the behaviour of
|
|
||||||
* the Lua bitwise left shift operator ({@code <<}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_SHL = ByteString.constOf("__shl");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__shr"}. When defined, customises the behaviour of
|
|
||||||
* the Lua bitwise right shift operator ({@code >>}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_SHR = ByteString.constOf("__shr");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__concat"}. When defined, customises the behaviour of
|
|
||||||
* the Lua concatenation operator ({@code ..}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_CONCAT = ByteString.constOf("__concat");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__len"}. When defined, customises the behaviour of
|
|
||||||
* the Lua length operator ({@code #}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_LEN = ByteString.constOf("__len");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__eq"}. When defined, customises the behaviour of
|
|
||||||
* the Lua equality operator ({@code ==}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_EQ = ByteString.constOf("__eq");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__lt"}. When defined, customises the behaviour of
|
|
||||||
* the Lua lesser-than operator ({@code <}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_LT = ByteString.constOf("__lt");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__le"}. When defined, customises the behaviour of
|
|
||||||
* the Lua lesser-than-or-equal-to operator ({@code <=}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_LE = ByteString.constOf("__le");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__index"}. When defined, customises the behaviour of
|
|
||||||
* the (non-assignment) Lua table access operator ({@code t[k]}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_INDEX = ByteString.constOf("__index");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__newindex"}. When defined, customises the behaviour of
|
|
||||||
* Lua table assignment ({@code t[k] = v}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_NEWINDEX = ByteString.constOf("__newindex");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__call"}. When defined, customises the behaviour of
|
|
||||||
* the Lua call operator ({@code f(args)}).
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_CALL = ByteString.constOf("__call");
|
|
||||||
/**
|
|
||||||
* The metatable key {@code "__mode"}. Used to control the weakness of table keys
|
|
||||||
* and values.
|
|
||||||
*/
|
|
||||||
public static final ByteString MT_MODE = ByteString.constOf("__mode");
|
|
||||||
|
|
||||||
private Metatables() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the entry with the key {@code event} of the metatable of the {@link LuaObject}
|
|
||||||
* {@code o}. If {@code o} does not have a metatable or {@code event} does not exist in it as
|
|
||||||
* a key, returns {@code null}.
|
|
||||||
*
|
|
||||||
* <p>The access of the metatable is raw (i.e. uses {@link Table#rawget(Object)}).</p>
|
|
||||||
*
|
|
||||||
* <p>This method differs from {@link #getMetamethod(MetatableProvider, ByteString, Object)}
|
|
||||||
* in that it does not require a metatable provider as the object in question is known
|
|
||||||
* to have metatables attached on a per-instance basis.</p>
|
|
||||||
*
|
|
||||||
* @param event the key to look up in the metatable, must not be {@code null}
|
|
||||||
* @param o the object in question, must not be {@code null}
|
|
||||||
* @return a non-{@code null} value if {@code event} is a key in {@code o}'s metatable; {@code
|
|
||||||
* null} otherwise
|
|
||||||
* @throws NullPointerException if {@code o} or {@code event} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Object getMetamethod(ByteString event, LuaObject o) {
|
|
||||||
Objects.requireNonNull(event);
|
|
||||||
Objects.requireNonNull(o);
|
|
||||||
|
|
||||||
Table mt = o.getMetatable();
|
|
||||||
if (mt != null) {
|
|
||||||
return mt.rawget(event);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the entry with the key {@code event} of the metatable of the object {@code o}.
|
|
||||||
* If {@code o} does not have a metatable or {@code event} does not exist in it as
|
|
||||||
* a key, returns {@code null}.
|
|
||||||
*
|
|
||||||
* <p>The access of the metatable is raw (i.e. uses {@link Table#rawget(Object)}).</p>
|
|
||||||
*
|
|
||||||
* @param metatableProvider the metatable provider, must not be {@code null}
|
|
||||||
* @param event the key to look up in the metatable, must not be {@code null}
|
|
||||||
* @param o the object in question, may be {@code null}
|
|
||||||
* @return a non-{@code null} value if {@code event} is a key in {@code o}'s metatable; {@code
|
|
||||||
* null} otherwise
|
|
||||||
* @throws NullPointerException if {@code metatableProvider} or {@code event} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Object getMetamethod(MetatableProvider metatableProvider, ByteString event,
|
|
||||||
Object o) {
|
|
||||||
Objects.requireNonNull(event);
|
|
||||||
// o can be null
|
|
||||||
|
|
||||||
Table mt = metatableProvider.getMetatable(o);
|
|
||||||
if (mt != null) {
|
|
||||||
return mt.rawget(event);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the metatable entry {@code event} for {@code a} or in {@code b}, or {@code null}
|
|
||||||
* if neither {@code a} nor {@code b} has such an entry in their metatable.
|
|
||||||
*
|
|
||||||
* <p>This method is similar to {@link #getMetamethod(MetatableProvider, ByteString, Object)},
|
|
||||||
* but first looks up the entry {@code event} in {@code a}, and if this fails (by
|
|
||||||
* returning {@code null}), tries to look {@code event} up in {@code b}.
|
|
||||||
*
|
|
||||||
* @param metatableProvider the metatable provider, must not be {@code null}
|
|
||||||
* @param event the key to look up in the metatable, must not be {@code null}
|
|
||||||
* @param a the first object to try, may be {@code null}
|
|
||||||
* @param b the second object to try, may be {@code null}
|
|
||||||
* @return a non-{@code null} value if {@code event} is a key in {@code a}'s or {@code b}'s
|
|
||||||
* metatable (in this order); {@code null} otherwise
|
|
||||||
* @throws NullPointerException if {@code metatableProvider} or {@code event} is {@code null}
|
|
||||||
*/
|
|
||||||
public static Object binaryHandlerFor(MetatableProvider metatableProvider, ByteString event,
|
|
||||||
Object a, Object b) {
|
|
||||||
Objects.requireNonNull(metatableProvider);
|
|
||||||
Objects.requireNonNull(event);
|
|
||||||
Object ma = Metatables.getMetamethod(metatableProvider, event, a);
|
|
||||||
return ma != null ? ma : Metatables.getMetamethod(metatableProvider, event, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception thrown to indicate an unsuccessful conversion of a number to an integer,
|
|
||||||
* i.e., when a number has no integer representation.
|
|
||||||
*/
|
|
||||||
public class NoIntegerRepresentationException extends ConversionException {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance of {@code NoIntegerRepresentationException}.
|
|
||||||
*/
|
|
||||||
public NoIntegerRepresentationException() {
|
|
||||||
super("number has no integer representation");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,441 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A representation of an ordering on values, allowing the comparison of values
|
|
||||||
* of the same type (in the type parameter {@code T}).
|
|
||||||
*
|
|
||||||
* <p>In Lua, only strings and numbers have such an ordering. This class serves the
|
|
||||||
* purpose of a bridge between the concrete representation of Lua numbers
|
|
||||||
* (as {@link java.lang.Number}) and the raw comparison operations provided by
|
|
||||||
* {@link LuaMathOperators}, and the concrete representation of Lua strings
|
|
||||||
* (as {@link java.lang.String}) and the comparison operations defined on them.</p>
|
|
||||||
*
|
|
||||||
* <p>Consequently, there are two concrete implementations of this class:
|
|
||||||
* {@link #NUMERIC} for the numeric ordering and {@link #STRING} for the string
|
|
||||||
* ordering. These instances may be used directly for comparing objects of known,
|
|
||||||
* conforming types; for unknown objects, the method {@link #of(Object, Object)}
|
|
||||||
* returns an ordering that accepts {@link java.lang.Object}, and uses one of
|
|
||||||
* the two ordering instances, or {@code null} if the arguments
|
|
||||||
* are not directly comparable in Lua.</p>
|
|
||||||
*
|
|
||||||
* <p>The comparison methods of this class return unboxed booleans.</p>
|
|
||||||
*
|
|
||||||
* <p>This class implements the {@link Comparator} interface by imposing a total
|
|
||||||
* order on the accepted values. For numbers, this total ordering is <i>different</i>
|
|
||||||
* from the one imposed by this class. See the documentation of {@link NumericOrdering}
|
|
||||||
* for more details.</p>
|
|
||||||
*
|
|
||||||
* <p><b>Example:</b> Given two objects {@code a}, and {@code b}, attempt to
|
|
||||||
* evaluate the Lua expression {@code (a <= b)}:</p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* // Object a, b
|
|
||||||
* final boolean result;
|
|
||||||
* Ordering<Object> cmp = Ordering.of(a, b);
|
|
||||||
* if (cmp != null) {
|
|
||||||
* // a and b are comparable in cmp
|
|
||||||
* result = cmp.le(a, b);
|
|
||||||
* }
|
|
||||||
* else {
|
|
||||||
* throw new RuntimeException("a and b not comparable");
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param <T> the type of values comparable in the ordering
|
|
||||||
*/
|
|
||||||
public abstract class Ordering<T> implements Comparator<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A static instance of the numeric ordering.
|
|
||||||
*/
|
|
||||||
public static final NumericOrdering NUMERIC = new NumericOrdering();
|
|
||||||
/**
|
|
||||||
* A static instance of the string ordering.
|
|
||||||
*/
|
|
||||||
public static final StringOrdering STRING = new StringOrdering();
|
|
||||||
private static final NumericObjectOrdering NUMERIC_OBJECT = new NumericObjectOrdering();
|
|
||||||
private static final StringObjectOrdering STRING_OBJECT = new StringObjectOrdering();
|
|
||||||
|
|
||||||
private Ordering() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the object {@code a} is raw-equal to {@code b} following
|
|
||||||
* the Lua equality rules.
|
|
||||||
*
|
|
||||||
* <p>Excerpt from the Lua Reference Manual (§3.4.4):</p>
|
|
||||||
*
|
|
||||||
* <blockquote>
|
|
||||||
* <p>Equality (==) first compares the type of its operands. If the types are different,
|
|
||||||
* then the result is false. Otherwise, the values of the operands are compared.
|
|
||||||
* Strings are compared in the obvious way. Numbers are equal if they denote the
|
|
||||||
* same mathematical value.</p>
|
|
||||||
*
|
|
||||||
* <p>Tables, userdata, and threads are compared by reference: two objects are considered
|
|
||||||
* equal only if they are the same object. Every time you create a new object (a table,
|
|
||||||
* userdata, or thread), this new object is different from any previously existing
|
|
||||||
* object. Closures with the same reference are always equal. Closures with any
|
|
||||||
* detectable difference (different behavior, different definition) are always
|
|
||||||
* different.</p>
|
|
||||||
* </blockquote>
|
|
||||||
*
|
|
||||||
* <p><b>Note:</b> Luna uses {@link Object#equals(Object)} to compare all non-nil,
|
|
||||||
* non-string, and non-numeric values for equality, effectively shifting the
|
|
||||||
* responsibility of adhering to the rules of Lua raw-equality for tables, userdata
|
|
||||||
* and threads to their implementations.</p>
|
|
||||||
*
|
|
||||||
* @param a an object, may be {@code null}
|
|
||||||
* @param b another object, may be {@code null}
|
|
||||||
* @return {@code true} iff {@code a} is raw-equal to {@code b}
|
|
||||||
*/
|
|
||||||
public static boolean isRawEqual(Object a, Object b) {
|
|
||||||
if (a == null && b == null) {
|
|
||||||
// two nils
|
|
||||||
return true;
|
|
||||||
} else if (a == null) {
|
|
||||||
// b is definitely not nil; also ensures that neither a nor b is null in the tests below
|
|
||||||
return false;
|
|
||||||
} else if (a instanceof Number && b instanceof Number) {
|
|
||||||
return Ordering.NUMERIC.eq((Number) a, (Number) b);
|
|
||||||
} else if (LuaType.isString(a) && LuaType.isString(b)) {
|
|
||||||
return Ordering.STRING.eq(toByteString(a), toByteString(b));
|
|
||||||
} else {
|
|
||||||
return a.equals(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ByteString toByteString(Object o) throws ClassCastException {
|
|
||||||
if (o instanceof ByteString) {
|
|
||||||
return (ByteString) o;
|
|
||||||
} else {
|
|
||||||
return ByteString.of((String) o); // may throw a ClassCastException
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Based on the actual types of the arguments {@code a} and {@code b}, returns
|
|
||||||
* the ordering in which {@code a} and {@code b} can be compared, or {@code null}
|
|
||||||
* if they are not comparable.
|
|
||||||
*
|
|
||||||
* <p>More specifically, if {@code a} and {@code b} are both numbers, returns
|
|
||||||
* an ordering that uses (but is distinct from) {@link #NUMERIC}; if {@code a} and
|
|
||||||
* {@code b} are both strings, returns an ordering that uses (but is distinct from)
|
|
||||||
* {@link #STRING}; otherwise, returns {@code null}.</p>
|
|
||||||
*
|
|
||||||
* <p>Note that when the result is non-{@code null}, it is guaranteed that
|
|
||||||
* 1) neither {@code a} nor {@code b} is {@code null}; and 2)
|
|
||||||
* both {@code a} and {@code b} are of types accepted by the underlying ordering.
|
|
||||||
* Caution must be observed when using the ordering with another object {@code c}
|
|
||||||
* (i.e., other than {@code a} or {@code b}): the returned ordering will throw
|
|
||||||
* a {@link ClassCastException} if {@code c} is of an incompatible type, or
|
|
||||||
* a {@link NullPointerException} if {@code c} is {@code null}.</p>
|
|
||||||
*
|
|
||||||
* @param a an object, may be {@code null}
|
|
||||||
* @param b another object, may be {@code null}
|
|
||||||
* @return an ordering based on {@link #NUMERIC} if both {@code a} and {@code b} are numbers; an
|
|
||||||
* ordering based on {@link #STRING} if both {@code a} and {@code b} are strings; {@code null}
|
|
||||||
* otherwise
|
|
||||||
*/
|
|
||||||
public static Ordering<Object> of(Object a, Object b) {
|
|
||||||
if (a instanceof Number && b instanceof Number) {
|
|
||||||
return NUMERIC_OBJECT;
|
|
||||||
} else if (LuaType.isString(a) && LuaType.isString(b)) {
|
|
||||||
return STRING_OBJECT;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if {@code a} is equal to {@code b} in this ordering.
|
|
||||||
*
|
|
||||||
* @param a first argument, must not be {@code null}
|
|
||||||
* @param b second argument, must not be {@code null}
|
|
||||||
* @return {@code true} iff {@code a} is equal to {@code b} in this ordering
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract boolean eq(T a, T b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if {@code a} is lesser than {@code b} in this ordering.
|
|
||||||
*
|
|
||||||
* @param a first argument, must not be {@code null}
|
|
||||||
* @param b second argument, must not be {@code null}
|
|
||||||
* @return {@code true} iff {@code a} is lesser than {@code b} in this ordering
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract boolean lt(T a, T b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if {@code a} is lesser than or equal to {@code b} in this ordering.
|
|
||||||
*
|
|
||||||
* @param a first argument, must not be {@code null}
|
|
||||||
* @param b second argument, must not be {@code null}
|
|
||||||
* @return {@code true} iff {@code a} is lesser than or equal to equal to {@code b} in this
|
|
||||||
* ordering
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract boolean le(T a, T b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Numeric ordering.
|
|
||||||
*
|
|
||||||
* <p>Numbers are compared using the comparison methods provided by {@link LuaMathOperators},
|
|
||||||
* defining the ordering as one based on the ordering of the mathematical values
|
|
||||||
* of the numbers in question.</p>
|
|
||||||
*
|
|
||||||
* <p>This class implements the {@link Comparator} interface by imposing a total order
|
|
||||||
* on numbers that differs from the ordering defined by the methods
|
|
||||||
* {@link #eq(Number, Number)}, {@link #lt(Number, Number)}
|
|
||||||
* and {@link #le(Number, Number)}:</p>
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li><i>NaN</i> is treated as equal to itself and greater than any other
|
|
||||||
* number, while {@code eq(a, b) == false} and {@code lt(a, b) == false}
|
|
||||||
* when {@code a} or {@code b} is <i>NaN</i>;
|
|
||||||
* <li>{@code -0.0} is considered to be lesser than {@code 0.0},
|
|
||||||
* while {@code eq(-0.0, 0.0) == true} and {@code lt(-0.0, 0.0) == false}.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>Note that the total ordering imposed by the {@link #compare(Number, Number)}
|
|
||||||
* is <i>inconsistent with equals</i>.</p>
|
|
||||||
*
|
|
||||||
* <p>For proper treatment of <i>NaN</i>s and (float) zero values, use the
|
|
||||||
* {@code Ordering} methods directly.</p>
|
|
||||||
*/
|
|
||||||
public static final class NumericOrdering extends Ordering<Number> {
|
|
||||||
|
|
||||||
private NumericOrdering() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff {@code a} denotes the same mathematical value
|
|
||||||
* as {@code b}.
|
|
||||||
*
|
|
||||||
* <p>Note that since <i>NaN</i> does not denote any mathematical value,
|
|
||||||
* this method returns {@code false} whenever any of its arguments is <i>NaN</i>.</p>
|
|
||||||
*
|
|
||||||
* @param a first argument, must not be {@code null}
|
|
||||||
* @param b second argument, must not be {@code null}
|
|
||||||
* @return {@code true} iff {@code a} and {@code b} denote the same mathematical value
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean eq(Number a, Number b) {
|
|
||||||
boolean isflt_a = a instanceof Double || a instanceof Float;
|
|
||||||
boolean isflt_b = b instanceof Double || b instanceof Float;
|
|
||||||
|
|
||||||
if (isflt_a) {
|
|
||||||
return isflt_b
|
|
||||||
? LuaMathOperators.eq(a.doubleValue(), b.doubleValue())
|
|
||||||
: LuaMathOperators.eq(a.doubleValue(), b.longValue());
|
|
||||||
} else {
|
|
||||||
return isflt_b
|
|
||||||
? LuaMathOperators.eq(a.longValue(), b.doubleValue())
|
|
||||||
: LuaMathOperators.eq(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value denoted by {@code a}
|
|
||||||
* is lesser than the mathematical value denoted by {@code b}.
|
|
||||||
*
|
|
||||||
* <p>Note that since <i>NaN</i> does not denote any mathematical value,
|
|
||||||
* this method returns {@code false} whenever any of its arguments is <i>NaN</i>.</p>
|
|
||||||
*
|
|
||||||
* @param a first argument, must not be {@code null}
|
|
||||||
* @param b second argument, must not be {@code null}
|
|
||||||
* @return {@code true} iff the mathematical value denoted by {@code a} is lesser than the
|
|
||||||
* mathematical value denoted by {@code b}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean lt(Number a, Number b) {
|
|
||||||
boolean isflt_a = a instanceof Double || a instanceof Float;
|
|
||||||
boolean isflt_b = b instanceof Double || b instanceof Float;
|
|
||||||
|
|
||||||
if (isflt_a) {
|
|
||||||
return isflt_b
|
|
||||||
? LuaMathOperators.lt(a.doubleValue(), b.doubleValue())
|
|
||||||
: LuaMathOperators.lt(a.doubleValue(), b.longValue());
|
|
||||||
} else {
|
|
||||||
return isflt_b
|
|
||||||
? LuaMathOperators.lt(a.longValue(), b.doubleValue())
|
|
||||||
: LuaMathOperators.lt(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} iff the mathematical value denoted by {@code a}
|
|
||||||
* is lesser than or equal to the mathematical value denoted by {@code b}.
|
|
||||||
*
|
|
||||||
* <p>Note that since <i>NaN</i> does not denote any mathematical value,
|
|
||||||
* this method returns {@code false} whenever any of its arguments is <i>NaN</i>.</p>
|
|
||||||
*
|
|
||||||
* @param a first argument, must not be {@code null}
|
|
||||||
* @param b second argument, must not be {@code null}
|
|
||||||
* @return {@code true} iff the mathematical value denoted by {@code a} is lesser than or equal
|
|
||||||
* to the mathematical value denoted by {@code b}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean le(Number a, Number b) {
|
|
||||||
boolean isflt_a = a instanceof Double || a instanceof Float;
|
|
||||||
boolean isflt_b = b instanceof Double || b instanceof Float;
|
|
||||||
|
|
||||||
if (isflt_a) {
|
|
||||||
return isflt_b
|
|
||||||
? LuaMathOperators.le(a.doubleValue(), b.doubleValue())
|
|
||||||
: LuaMathOperators.le(a.doubleValue(), b.longValue());
|
|
||||||
} else {
|
|
||||||
return isflt_b
|
|
||||||
? LuaMathOperators.le(a.longValue(), b.doubleValue())
|
|
||||||
: LuaMathOperators.le(a.longValue(), b.longValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare the numbers {@code a} and {@code b}, yielding an integer that
|
|
||||||
* is negative, zero or positive if {@code a} is lesser than, equal to, or greater
|
|
||||||
* than {@code b}.
|
|
||||||
*
|
|
||||||
* <p>The ordering imposed by this method differs from the one defined
|
|
||||||
* by the methods {@link #eq(Number, Number)}, {@link #lt(Number, Number)}
|
|
||||||
* and {@link #le(Number, Number)} in the treatment of <i>NaN</i>s
|
|
||||||
* and float zeros:</p>
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li><i>NaN</i> is treated as equal to itself and greater than any other
|
|
||||||
* number, while {@code eq(a, b) == false} and {@code lt(a, b) == false}
|
|
||||||
* when {@code a} or {@code b} is <i>NaN</i>;
|
|
||||||
* <li>{@code -0.0} is considered to be lesser than {@code 0.0},
|
|
||||||
* while {@code eq(-0.0, 0.0) == true} and {@code lt(-0.0, 0.0) == false}.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>The total ordering of {@code Number} objects imposed by this method
|
|
||||||
* is <i>inconsistent with equals</i>.</p>
|
|
||||||
*
|
|
||||||
* @param a first argument, must not be {@code null}
|
|
||||||
* @param b second argument, must not be {@code null}
|
|
||||||
* @return a negative, zero or positive integer if the number {@code a} is lesser than, equal
|
|
||||||
* to, or greater than the number {@code b}
|
|
||||||
* @throws NullPointerException if {@code a} or {@code b} is {@code null}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compare(Number a, Number b) {
|
|
||||||
if (lt(a, b)) {
|
|
||||||
return -1;
|
|
||||||
} else if (lt(b, a)) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
// treat NaN as equal to itself and greater than any other number,
|
|
||||||
// and -0.0 as lesser than 0.0
|
|
||||||
return Double.compare(a.doubleValue(), b.doubleValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* String ordering.
|
|
||||||
*
|
|
||||||
* <p>This is the (total) lexicographical ordering imposed by the method
|
|
||||||
* {@link String#compareTo(String)}.</p>
|
|
||||||
*/
|
|
||||||
public static final class StringOrdering extends Ordering<ByteString> {
|
|
||||||
|
|
||||||
private StringOrdering() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean eq(ByteString a, ByteString b) {
|
|
||||||
return a.compareTo(b) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean lt(ByteString a, ByteString b) {
|
|
||||||
return a.compareTo(b) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean le(ByteString a, ByteString b) {
|
|
||||||
return a.compareTo(b) <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(ByteString a, ByteString b) {
|
|
||||||
return a.compareTo(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NumericObjectOrdering extends Ordering<Object> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean eq(Object a, Object b) {
|
|
||||||
return NUMERIC.eq((Number) a, (Number) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean lt(Object a, Object b) {
|
|
||||||
return NUMERIC.lt((Number) a, (Number) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean le(Object a, Object b) {
|
|
||||||
return NUMERIC.le((Number) a, (Number) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(Object a, Object b) {
|
|
||||||
return NUMERIC.compare((Number) a, (Number) b);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class StringObjectOrdering extends Ordering<Object> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean eq(Object a, Object b) {
|
|
||||||
return STRING.eq(toByteString(a), toByteString(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean lt(Object a, Object b) {
|
|
||||||
return STRING.lt(toByteString(a), toByteString(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean le(Object a, Object b) {
|
|
||||||
return STRING.le(toByteString(a), toByteString(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(Object a, Object b) {
|
|
||||||
return STRING.compare(toByteString(a), toByteString(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_BOOLEAN;
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_FUNCTION;
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_NIL;
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_NUMBER;
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_STRING;
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_TABLE;
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_THREAD;
|
|
||||||
import static org.classdump.luna.LuaFormat.TYPENAME_USERDATA;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A value type namer that uses a fixed mapping from types to type names.
|
|
||||||
*
|
|
||||||
* <p>This is a wrapper of the static method {@link #luaTypeToName(LuaType)}.</p>
|
|
||||||
*/
|
|
||||||
public class PlainValueTypeNamer implements ValueTypeNamer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A static instance of this value type namer.
|
|
||||||
*/
|
|
||||||
public static final PlainValueTypeNamer INSTANCE = new PlainValueTypeNamer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name (a string) of a given Lua type.
|
|
||||||
*
|
|
||||||
* <p>The result of this method is one of {@code "nil"}, {@code "boolean"}, {@code "number"},
|
|
||||||
* {@code "string"}, {@code "table"}, {@code "function"}, {@code "userdata"}
|
|
||||||
* and {@code "thread"}.</p>
|
|
||||||
*
|
|
||||||
* @param type the type, must not be {@code null}
|
|
||||||
* @return the name of {@code type}
|
|
||||||
* @throws NullPointerException if {@code type} is {@code null}
|
|
||||||
*/
|
|
||||||
public static ByteString luaTypeToName(LuaType type) {
|
|
||||||
switch (type) {
|
|
||||||
case NIL:
|
|
||||||
return TYPENAME_NIL;
|
|
||||||
case BOOLEAN:
|
|
||||||
return TYPENAME_BOOLEAN;
|
|
||||||
case NUMBER:
|
|
||||||
return TYPENAME_NUMBER;
|
|
||||||
case STRING:
|
|
||||||
return TYPENAME_STRING;
|
|
||||||
case TABLE:
|
|
||||||
return TYPENAME_TABLE;
|
|
||||||
case FUNCTION:
|
|
||||||
return TYPENAME_FUNCTION;
|
|
||||||
case USERDATA:
|
|
||||||
return TYPENAME_USERDATA;
|
|
||||||
case THREAD:
|
|
||||||
return TYPENAME_THREAD;
|
|
||||||
default:
|
|
||||||
throw new NullPointerException("Illegal type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteString typeNameOf(Object instance) {
|
|
||||||
return luaTypeToName(LuaType.typeOf(instance));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A global context holding shared metatables and providing methods for instantiating new
|
|
||||||
* tables.
|
|
||||||
*/
|
|
||||||
public interface StateContext extends MetatableAccessor, TableFactory {
|
|
||||||
|
|
||||||
}
|
|
@ -1,266 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.util.ByteIterator;
|
|
||||||
import org.classdump.luna.util.CharsetEncoderByteIterator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A byte string backed by a {@link java.lang.String}.
|
|
||||||
*/
|
|
||||||
class StringByteString extends ByteString {
|
|
||||||
|
|
||||||
private final String string;
|
|
||||||
private final Charset charset;
|
|
||||||
|
|
||||||
private int byteHashCode;
|
|
||||||
private int byteLength;
|
|
||||||
|
|
||||||
StringByteString(String s, Charset charset) {
|
|
||||||
this.string = Objects.requireNonNull(s);
|
|
||||||
this.charset = Objects.requireNonNull(charset);
|
|
||||||
if (!charset.canEncode()) {
|
|
||||||
throw new IllegalArgumentException("Charset cannot encode: " + charset.name());
|
|
||||||
}
|
|
||||||
this.byteHashCode = 0;
|
|
||||||
this.byteLength = string.isEmpty() ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkSubstringBounds(int start, int end, int len) {
|
|
||||||
if (start > end) {
|
|
||||||
throw new IndexOutOfBoundsException("start > end (" + start + " > " + end + ")");
|
|
||||||
} else if (start < 0) {
|
|
||||||
throw new IndexOutOfBoundsException("start < 0 (" + start + " < 0)");
|
|
||||||
} else if (end < 0) {
|
|
||||||
throw new IndexOutOfBoundsException("end < 0 (" + end + " < 0)");
|
|
||||||
} else if (end > len) {
|
|
||||||
throw new IndexOutOfBoundsException("end > length (" + start + " > " + len + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean equalsByteString(ByteString that) {
|
|
||||||
if (this.isEmpty() && that.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (that instanceof StringByteString tstring) {
|
|
||||||
return string.equals(tstring.string);
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't force hashCode computation, but use if already known
|
|
||||||
int thisHash = this.maybeHashCode();
|
|
||||||
int thatHash = that.maybeHashCode();
|
|
||||||
if (thisHash != 0 && thatHash != 0 && thisHash != thatHash) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't force length computation, but use if already known
|
|
||||||
int thisLength = this.maybeLength();
|
|
||||||
int thatLength = that.maybeLength();
|
|
||||||
if (thisLength >= 0 && thatLength >= 0 && thisLength != thatLength) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare byte-by-byte
|
|
||||||
ByteIterator thisIterator = this.byteIterator();
|
|
||||||
ByteIterator thatIterator = that.byteIterator();
|
|
||||||
while (thisIterator.hasNext() && thatIterator.hasNext()) {
|
|
||||||
byte thisByte = thisIterator.nextByte();
|
|
||||||
byte thatByte = thatIterator.nextByte();
|
|
||||||
if (thisByte != thatByte) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return thisIterator.hasNext() == thatIterator.hasNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(ByteString that) {
|
|
||||||
if (that instanceof StringByteString bstring)
|
|
||||||
return string.compareTo(bstring.string);
|
|
||||||
|
|
||||||
return super.compareTo(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int computeHashCode() {
|
|
||||||
int hc = 0;
|
|
||||||
|
|
||||||
ByteIterator it = new CharsetEncoderByteIterator(string, charset);
|
|
||||||
while (it.hasNext()) {
|
|
||||||
hc = (hc * 31) + (it.nextByte() & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int hc = byteHashCode;
|
|
||||||
|
|
||||||
if (hc == 0 && !string.isEmpty()) {
|
|
||||||
hc = computeHashCode();
|
|
||||||
|
|
||||||
// update cached hashCode
|
|
||||||
byteHashCode = hc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int maybeHashCode() {
|
|
||||||
return byteHashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int computeLength() {
|
|
||||||
int len = 0;
|
|
||||||
ByteIterator it = new CharsetEncoderByteIterator(string, charset);
|
|
||||||
while (it.hasNext()) {
|
|
||||||
it.nextByte();
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length() {
|
|
||||||
int len = byteLength;
|
|
||||||
if (len < 0) {
|
|
||||||
len = computeLength();
|
|
||||||
byteLength = len;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int maybeLength() {
|
|
||||||
return byteLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return string.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] bytesCache;
|
|
||||||
|
|
||||||
// must not escape, may be an array from the cache!
|
|
||||||
private byte[] toBytes() {
|
|
||||||
if (bytesCache == null) {
|
|
||||||
bytesCache = string.getBytes(charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getBytes() {
|
|
||||||
byte[] bytes = toBytes();
|
|
||||||
|
|
||||||
// must make a defensive copy
|
|
||||||
return Arrays.copyOf(bytes, bytes.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte byteAt(int index) {
|
|
||||||
if (index < 0) {
|
|
||||||
// don't even have to convert to bytes
|
|
||||||
throw new IndexOutOfBoundsException(String.valueOf(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
return toBytes()[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteIterator byteIterator() {
|
|
||||||
return new CharsetEncoderByteIterator(string, charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteString substring(int start, int end) {
|
|
||||||
byte[] bytes = toBytes();
|
|
||||||
checkSubstringBounds(start, end, bytes.length);
|
|
||||||
return new ArrayByteString(Arrays.copyOfRange(bytes, start, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String decode(Charset charset) {
|
|
||||||
if (this.charset.equals(charset)) {
|
|
||||||
return string;
|
|
||||||
} else {
|
|
||||||
return super.decode(charset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toRawString() {
|
|
||||||
byte[] bytes = toBytes();
|
|
||||||
char[] chars = new char[bytes.length];
|
|
||||||
for (int i = 0; i < chars.length; i++) {
|
|
||||||
chars[i] = (char) (bytes[i] & 0xff);
|
|
||||||
}
|
|
||||||
return String.valueOf(chars);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putTo(ByteBuffer buffer) {
|
|
||||||
// ByteBuffer cannot be directly extended: it's safe to use a possibly cached array
|
|
||||||
buffer.put(toBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream stream) throws IOException {
|
|
||||||
// OutputStream can be extended: pass a defensive copy
|
|
||||||
stream.write(getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteString concat(ByteString other) {
|
|
||||||
if (other instanceof StringByteString) {
|
|
||||||
StringByteString that = (StringByteString) other;
|
|
||||||
if (this.charset.equals(that.charset)) {
|
|
||||||
// Caveat: preserves malformed characters and characters unmappable by charset
|
|
||||||
return ByteString.of(this.string.concat(that.string));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.concat(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean startsWith(byte b) {
|
|
||||||
if (string.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ByteIterator it = new CharsetEncoderByteIterator(string, charset);
|
|
||||||
return it.hasNext() && it.nextByte() == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,330 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
import org.classdump.luna.runtime.Dispatch;
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract class representing a Lua table.
|
|
||||||
*
|
|
||||||
* <p><b>Note on equality:</b> according to §3.4.4 of the Lua Reference Manual,
|
|
||||||
* tables {@code a} and {@code b} are expected to be equal if and only if they are
|
|
||||||
* the same object. However, {@link Ordering#isRawEqual(Object, Object)} compares
|
|
||||||
* tables using {@link Object#equals(Object)}. <b>Exercise caution when overriding
|
|
||||||
* {@code equals()}.</b></p>
|
|
||||||
*/
|
|
||||||
public abstract class Table extends LuaObject {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A weak set containing the references to tables this table is a metatable of.
|
|
||||||
*
|
|
||||||
* Let M be the metatable of a table T. Then T is a *basetable* of M. M may have multiple
|
|
||||||
* basetables; this is the set of all basetables of this table.
|
|
||||||
*
|
|
||||||
* According to LRM §2.5.2, when a table T has a metatable M, the value associated
|
|
||||||
* with the key "__mode" in M determines whether T has weak keys, values or both. This means
|
|
||||||
* that an update of M["__mode"] may trigger a change in the weakness status of all basetables
|
|
||||||
* of M. Therefore, each table must keep track of its basetables.
|
|
||||||
*/
|
|
||||||
private final Set<Table> basetables = Collections
|
|
||||||
.newSetFromMap(new WeakHashMap<Table, Boolean>());
|
|
||||||
/**
|
|
||||||
* The metatable of this table, may be {@code null}.
|
|
||||||
*/
|
|
||||||
private Table metatable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the value associated with the given {@code key}, returning {@code null}
|
|
||||||
* when {@code key} has no value associated with it.
|
|
||||||
*
|
|
||||||
* <p>Implementations of this method must ensure that the Lua rules for valid
|
|
||||||
* table keys are honoured, e.g. by normalising keys using
|
|
||||||
* {@link Conversions#normaliseKey(Object)}.</p>
|
|
||||||
*
|
|
||||||
* <p>This method provides <i>raw</i> access to the table. For non-raw access
|
|
||||||
* (i.e., handling the {@code __index} metamethod), use
|
|
||||||
* {@link Dispatch#index(ExecutionContext, Table, Object)}.</p>
|
|
||||||
*
|
|
||||||
* @param key the key, may be {@code null}
|
|
||||||
* @return the value associated with {@code key}, or {@code null} when there is no value
|
|
||||||
* associated with {@code key} in this table
|
|
||||||
*/
|
|
||||||
public abstract Object rawget(Object key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the value associated with the given integer {@code idx}, returning
|
|
||||||
* {@code null} when {@code idx} has no value associated with it.
|
|
||||||
*
|
|
||||||
* <p>This method must be functionally equivalent to {@link #rawget(Object)} with the
|
|
||||||
* corresponding boxed key. However, implementations of this method may optimise the retrieval
|
|
||||||
* in this case, since the type of the key is known at compile-time.</p>
|
|
||||||
*
|
|
||||||
* <p>This method provides <i>raw</i> access to the table. For non-raw access
|
|
||||||
* (i.e., handling the {@code __index} metamethod), use
|
|
||||||
* {@link Dispatch#index(ExecutionContext, Table, long)}.</p>
|
|
||||||
*
|
|
||||||
* @param idx the integer key
|
|
||||||
* @return the value associated with {@code idx}, or {@code null} when there is no value
|
|
||||||
* associated with {@code idx} in this table
|
|
||||||
*/
|
|
||||||
public Object rawget(long idx) {
|
|
||||||
return rawget(Long.valueOf(idx));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value associated with the key {@code key} to {@code value}. When {@code value}
|
|
||||||
* is {@code null}, removes {@code key} from the table.
|
|
||||||
*
|
|
||||||
* <p>When {@code key} is {@code null} (i.e., a <b>nil</b>) or a <i>NaN</i>,
|
|
||||||
* an {@link IllegalArgumentException} is thrown.</p>
|
|
||||||
*
|
|
||||||
* <p>This method provides <i>raw</i> access to the table. For non-raw access
|
|
||||||
* (i.e., handling the {@code __newindex} metamethod), use
|
|
||||||
* {@link Dispatch#setindex(ExecutionContext, Table, Object, Object)}.</p>
|
|
||||||
*
|
|
||||||
* <p><b>Implementation notes:</b> Implementations of this method must ensure that
|
|
||||||
* the behaviour of this method conforms to the Lua semantics as delineated in the Lua
|
|
||||||
* Reference Manual. In particular:</p>
|
|
||||||
* <ul>
|
|
||||||
* <li>float keys that have an integer value must be treated as integer keys
|
|
||||||
* (e.g. by using {@link Conversions#normaliseKey(Object)};</li>
|
|
||||||
* <li>updates of the value associated with the key {@code "__mode"}
|
|
||||||
* must call {@link #updateBasetableModes(Object, Object)}.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @param key the key, must not be {@code null} or <i>NaN</i>
|
|
||||||
* @param value the value to associate with {@code key}, may be {@code null}
|
|
||||||
* @throws IllegalArgumentException when {@code key} is {@code null} or a <i>NaN</i>
|
|
||||||
*/
|
|
||||||
public abstract void rawset(Object key, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value associated with the integer key {@code idx} to {@code value}.
|
|
||||||
* When {@code value} is {@code null}, removes {@code idx} from the table.
|
|
||||||
*
|
|
||||||
* <p>This method must be functionally equivalent to {@link #rawset(Object, Object)} with the
|
|
||||||
* corresponding boxed key. However, implementations of this method may be more optimised
|
|
||||||
* than in the generic case, since the type of the key is known at compile-time.</p>
|
|
||||||
*
|
|
||||||
* <p>This method provides <i>raw</i> access to the table. For non-raw access
|
|
||||||
* (i.e., handling the {@code __newindex} metamethod), use
|
|
||||||
* {@link Dispatch#setindex(ExecutionContext, Table, long, Object)}.</p>
|
|
||||||
*
|
|
||||||
* @param idx the integer key
|
|
||||||
* @param value the value to associate with {@code idx}, may be {@code null}
|
|
||||||
*/
|
|
||||||
public void rawset(long idx, Object value) {
|
|
||||||
rawset(Long.valueOf(idx), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this table is a sequence, returns the length of this sequence.
|
|
||||||
*
|
|
||||||
* <p>According to §2.1 of the Lua Reference Manual, a <i>sequence</i> is</p>
|
|
||||||
* <blockquote>
|
|
||||||
* a table where the set of all positive numeric keys is equal to {1..<i>n</i>} for some
|
|
||||||
* non-negative integer <i>n</i>, which is called the length of the sequence
|
|
||||||
* </blockquote>
|
|
||||||
*
|
|
||||||
* <p>Note that when this table is not a sequence, the return value of this method
|
|
||||||
* is undefined.</p>
|
|
||||||
*
|
|
||||||
* @return the length of the sequence if this table is a sequence
|
|
||||||
*/
|
|
||||||
public long rawlen() {
|
|
||||||
long idx = 1;
|
|
||||||
|
|
||||||
while (idx >= 0 && rawget(idx) != null) {
|
|
||||||
idx <<= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if idx overflows (idx < 0), don't check rawget(idx)
|
|
||||||
|
|
||||||
if (idx == 1) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
// binary search in [idx >>> 1, idx]
|
|
||||||
|
|
||||||
long min = idx >>> 1;
|
|
||||||
long max = idx;
|
|
||||||
|
|
||||||
// invariant: (min > 0 && rawget(min) != null) && (max < 0 || rawget(max) == null)
|
|
||||||
|
|
||||||
while (min + 1 != max) {
|
|
||||||
// works even if max == (1 << 63)
|
|
||||||
long mid = (min + max) >>> 1;
|
|
||||||
if (rawget(mid) == null) {
|
|
||||||
max = mid;
|
|
||||||
} else {
|
|
||||||
min = mid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// min + 1 == max; given the invariant, min is the result
|
|
||||||
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the initial key for iterating through the set of keys in this table.
|
|
||||||
*
|
|
||||||
* <p>Conceptually speaking, all keys in this table are totally ordered; this method
|
|
||||||
* returns the minimal key.</p>
|
|
||||||
*
|
|
||||||
* <p>The key returned by this method, together with the subsequent calls
|
|
||||||
* to {@link #successorKeyOf(Object)} will visit all keys in this table exactly once
|
|
||||||
* (in an unspecified order):</p>
|
|
||||||
* <pre>
|
|
||||||
* Object k = table.initialIndex();
|
|
||||||
* while (k != null) {
|
|
||||||
* // process the key k
|
|
||||||
* k = table.nextIndex(k);
|
|
||||||
* }
|
|
||||||
* // at this point, we visited all keys in table exactly once
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @return an initial key for iteration through all keys in this table
|
|
||||||
*/
|
|
||||||
public abstract Object initialKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next key for iterating through the set of keys in this table.
|
|
||||||
*
|
|
||||||
* <p>Conceptually speaking, all keys in this table are totally ordered; this method
|
|
||||||
* returns the immediate successor of {@code key}, or {@code null} if {@code key} is
|
|
||||||
* the maximal key.</p>
|
|
||||||
*
|
|
||||||
* <p>When no value is associated with the key {@code key} in this table,
|
|
||||||
* an {@link IllegalArgumentException} is thrown.</p>
|
|
||||||
*
|
|
||||||
* <p>To retrieve the initial key for iterating through this table, use
|
|
||||||
* {@link #initialKey()}.</p>
|
|
||||||
*
|
|
||||||
* @param key the key to get the immediate successor of, must not be {@code null}
|
|
||||||
* @return the immediate successor of {@code key} in this table
|
|
||||||
* @throws IllegalArgumentException when no value is associated with {@code key} in this table, or
|
|
||||||
* {@code key} is {@code null}
|
|
||||||
*/
|
|
||||||
public abstract Object successorKeyOf(Object key);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Table getMetatable() {
|
|
||||||
// not thread-safe!
|
|
||||||
return metatable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the metatable of this table to {@code mt}. {@code mt} may be {@code null}:
|
|
||||||
* in that case, removes the metatable from this object.
|
|
||||||
*
|
|
||||||
* <p>Returns the metatable previously associated with this object (i.e., the metatable
|
|
||||||
* before the call of this method; possibly {@code null}).</p>
|
|
||||||
*
|
|
||||||
* <p>This method maintains the weakness of this table by invoking
|
|
||||||
* {@link #setMode(boolean, boolean)} every time it is called.</p>
|
|
||||||
*
|
|
||||||
* @param mt new metatable to attach to this object, may be {@code null}
|
|
||||||
* @return previous metatable associated with this object
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Table setMetatable(Table mt) {
|
|
||||||
|
|
||||||
// not thread-safe!
|
|
||||||
|
|
||||||
Table old = metatable;
|
|
||||||
|
|
||||||
if (old != null) {
|
|
||||||
// update the basetable mapping
|
|
||||||
old.basetables.remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean wk = false;
|
|
||||||
boolean wv = false;
|
|
||||||
|
|
||||||
if (mt != null) {
|
|
||||||
mt.basetables.add(this);
|
|
||||||
Object m = mt.rawget(Metatables.MT_MODE);
|
|
||||||
if (m instanceof String) {
|
|
||||||
String s = (String) m;
|
|
||||||
wk = s.indexOf('k') > -1;
|
|
||||||
wv = s.indexOf('v') > -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
metatable = mt;
|
|
||||||
setMode(wk, wv);
|
|
||||||
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If {@code key} is equal to {@link Metatables#MT_MODE}, updates the weakness of the tables
|
|
||||||
* that use this table as their metatable (i.e., the <i>basetables</i> of this table).
|
|
||||||
* Otherwise, this method has no effect.
|
|
||||||
*
|
|
||||||
* <p>Whenever applicable, this method <b>must</b> be called by the implementations
|
|
||||||
* of {@link #rawset(Object, Object)} in order to ensure that assignments to
|
|
||||||
* the {@link Metatables#MT_MODE} key update the weakness mode of the tables that use this
|
|
||||||
* table as a metatable, as required by §2.5.2 of the Lua Reference Manual.</p>
|
|
||||||
*
|
|
||||||
* <p>It is safe not to call this method when {@code key} is known not to be equal to
|
|
||||||
* {@link Metatables#MT_MODE}.</p>
|
|
||||||
*
|
|
||||||
* @param key the key, may be {@code null}
|
|
||||||
* @param value the value, may be {@code null}
|
|
||||||
*/
|
|
||||||
protected void updateBasetableModes(Object key, Object value) {
|
|
||||||
// not thread-safe!
|
|
||||||
if (Metatables.MT_MODE.equals(key)) {
|
|
||||||
boolean wk = false;
|
|
||||||
boolean wv = false;
|
|
||||||
|
|
||||||
if (value instanceof String) {
|
|
||||||
String s = (String) value;
|
|
||||||
wk = s.indexOf('k') > -1;
|
|
||||||
wv = s.indexOf('v') > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update all tables
|
|
||||||
for (Table t : basetables) {
|
|
||||||
t.setMode(wk, wv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the weakness of this table. If {@code weakKeys} is {@code true}, the table will have
|
|
||||||
* weak keys (otherwise, the table will have non-weak keys). Similarly, if {@code weakValues}
|
|
||||||
* is {@code true}, the table will have weak values (and non-weak values if {@code false}).
|
|
||||||
*
|
|
||||||
* <p>This method is not meant to be called directly: according to §2.5.2 of the Lua
|
|
||||||
* Reference Manual, the weakness of a table is fully determined by the value of the
|
|
||||||
* {@code "__mode"} field of its metatable. It is, however, meant to be called as part
|
|
||||||
* of maintenance of this requirement by {@link #setMetatable(Table)} and
|
|
||||||
* {@link #updateBasetableModes(Object, Object)}.</p>
|
|
||||||
*
|
|
||||||
* @param weakKeys key mode ({@code true} for weak, {@code false} for non-weak keys)
|
|
||||||
* @param weakValues value mode ({@code true} for weak, {@code false} for non-weak values)
|
|
||||||
*/
|
|
||||||
protected abstract void setMode(boolean weakKeys, boolean weakValues);
|
|
||||||
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A factory for {@link Table} instances.
|
|
||||||
*/
|
|
||||||
public interface TableFactory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new empty table. This is functionally equivalent to {@code newTable(0, 0)}.
|
|
||||||
*
|
|
||||||
* @return new empty table
|
|
||||||
* @see #newTable(int, int)
|
|
||||||
*/
|
|
||||||
Table newTable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new empty table with the given initial capacities for its array and hash
|
|
||||||
* parts.
|
|
||||||
*
|
|
||||||
* @param array initial capacity for the array part
|
|
||||||
* @param hash initial capacity for the hash part
|
|
||||||
* @return new empty table
|
|
||||||
*/
|
|
||||||
Table newTable(int array, int hash);
|
|
||||||
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Full userdata.
|
|
||||||
*
|
|
||||||
* <p>Instances of this class may have a <i>user value</i> attached to them,
|
|
||||||
* accessible using the methods {@link #getUserValue()} and {@link #setUserValue(Object)}.</p>
|
|
||||||
*
|
|
||||||
* <p><b>Note on equality:</b> according to §3.4.4 of the Lua Reference Manual,
|
|
||||||
* userdata {@code a} and {@code b} are expected to be equal if and only if they are
|
|
||||||
* the same object. However, {@link Ordering#isRawEqual(Object, Object)} compares
|
|
||||||
* userdata using {@link Object#equals(Object)}. <b>Exercise caution when overriding
|
|
||||||
* {@code equals()}.</b></p>
|
|
||||||
*/
|
|
||||||
public abstract class Userdata<T> extends LuaObject {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the user value attached to this full userdata.
|
|
||||||
*
|
|
||||||
* @return the user value attached to this full userdata
|
|
||||||
*/
|
|
||||||
public abstract T getUserValue();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the user value attached to this full userdata to {@code value}, returning
|
|
||||||
* the old user value.
|
|
||||||
*
|
|
||||||
* @param value new user value, may be {@code null}
|
|
||||||
* @return old user value
|
|
||||||
*/
|
|
||||||
public abstract T setUserValue(T value);
|
|
||||||
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mapping from values to a string representation of their type.
|
|
||||||
*/
|
|
||||||
public interface ValueTypeNamer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the type name (a byte string) of the value {@code instance}.
|
|
||||||
*
|
|
||||||
* @param instance the object in question, may be {@code null}
|
|
||||||
* @return the type name of {@code instance}
|
|
||||||
*/
|
|
||||||
ByteString typeNameOf(Object instance);
|
|
||||||
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A reified variable.
|
|
||||||
*
|
|
||||||
* <p>A variable is an object storing a single value of arbitrary type. When used as fields
|
|
||||||
* in function class instances, they serve a function equivalent to that of <i>upvalues</i>
|
|
||||||
* in PUC-Lua.</p>
|
|
||||||
*/
|
|
||||||
public class Variable {
|
|
||||||
|
|
||||||
private Object value;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new variable instance with the given initial value.
|
|
||||||
*
|
|
||||||
* @param initialValue the initial value of the variable, may be {@code null}
|
|
||||||
*/
|
|
||||||
public Variable(Object initialValue) {
|
|
||||||
this.value = initialValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the value stored in this variable.
|
|
||||||
*
|
|
||||||
* @return the value of this variable (possibly {@code null})
|
|
||||||
*/
|
|
||||||
public Object get() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value stored in this variable to {@code value}.
|
|
||||||
*
|
|
||||||
* @param value the new value of this variable, may be {@code null}
|
|
||||||
*/
|
|
||||||
public void set(Object value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.load.CompiledChunk;
|
|
||||||
import org.classdump.luna.util.ByteVector;
|
|
||||||
|
|
||||||
public class CompiledModule implements CompiledChunk {
|
|
||||||
|
|
||||||
private final Map<String, ByteVector> classMap;
|
|
||||||
private final String mainClassName;
|
|
||||||
|
|
||||||
public CompiledModule(Map<String, ByteVector> classMap, String mainClassName) {
|
|
||||||
this.classMap = Objects.requireNonNull(classMap);
|
|
||||||
this.mainClassName = Objects.requireNonNull(mainClassName);
|
|
||||||
|
|
||||||
if (!classMap.containsKey(mainClassName)) {
|
|
||||||
throw new IllegalStateException("No main class in class map");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, ByteVector> classMap() {
|
|
||||||
return classMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String mainClassName() {
|
|
||||||
return mainClassName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,207 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.Variable;
|
|
||||||
import org.classdump.luna.load.ChunkClassLoader;
|
|
||||||
import org.classdump.luna.load.ChunkFactory;
|
|
||||||
import org.classdump.luna.load.ChunkLoader;
|
|
||||||
import org.classdump.luna.load.LoaderException;
|
|
||||||
import org.classdump.luna.parser.ParseException;
|
|
||||||
import org.classdump.luna.parser.Parser;
|
|
||||||
import org.classdump.luna.parser.TokenMgrError;
|
|
||||||
import org.classdump.luna.runtime.LuaFunction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A chunk loader that uses the {@linkplain LuaCompiler compiler} to convert Lua source
|
|
||||||
* text to Java classfiles, and loads these classfiles into the VM using a {@link ClassLoader}.
|
|
||||||
*/
|
|
||||||
public class CompilerChunkLoader implements ChunkLoader {
|
|
||||||
|
|
||||||
private final ChunkClassLoader chunkClassLoader;
|
|
||||||
private final String rootClassPrefix;
|
|
||||||
private final LuaCompiler compiler;
|
|
||||||
|
|
||||||
private int idx;
|
|
||||||
|
|
||||||
CompilerChunkLoader(ClassLoader classLoader, LuaCompiler compiler, String rootClassPrefix) {
|
|
||||||
this.chunkClassLoader = new ChunkClassLoader(Objects.requireNonNull(classLoader));
|
|
||||||
this.compiler = Objects.requireNonNull(compiler);
|
|
||||||
this.rootClassPrefix = Objects.requireNonNull(rootClassPrefix);
|
|
||||||
this.idx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new instance of {@code CompilerChunkLoader} that uses the specified
|
|
||||||
* class loader {@code classLoader} to load classes it compiles using {@code compiler},
|
|
||||||
* with every main chunk class having the class name {@code rootClassPrefix} followed
|
|
||||||
* by a monotonically-increasing integer suffix.
|
|
||||||
*
|
|
||||||
* @param classLoader the class loader used by this chunk loader, must not be {@code null}
|
|
||||||
* @param compiler the compiler instance used by this chunk loader, must not be {@code null}
|
|
||||||
* @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null}
|
|
||||||
* @return a new instance of {@code CompilerChunkLoader}
|
|
||||||
* @throws NullPointerException if {@code classLoader}, {@code compiler} or {@code
|
|
||||||
* rootClassPrefix} is {@code null}
|
|
||||||
*/
|
|
||||||
public static CompilerChunkLoader of(ClassLoader classLoader, LuaCompiler compiler,
|
|
||||||
String rootClassPrefix) {
|
|
||||||
return new CompilerChunkLoader(classLoader, compiler, rootClassPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new instance of {@code CompilerChunkLoader} that uses the specified
|
|
||||||
* class loader {@code classLoader} to load classes it compiles using a new instance
|
|
||||||
* of the Lua compiler with the settings {@code compilerSettings},
|
|
||||||
* with every main chunk class having the class name {@code rootClassPrefix} followed
|
|
||||||
* by a monotonically-increasing integer suffix.
|
|
||||||
*
|
|
||||||
* @param classLoader the class loader used by this chunk loader, must not be {@code null}
|
|
||||||
* @param compilerSettings the compiler settings used to instantiate the compiler, must not be
|
|
||||||
* {@code null}
|
|
||||||
* @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null}
|
|
||||||
* @return a new instance of {@code CompilerChunkLoader}
|
|
||||||
* @throws NullPointerException if {@code classLoader}, {@code compilerSettings} or {@code
|
|
||||||
* rootClassPrefix} is {@code null}
|
|
||||||
*/
|
|
||||||
public static CompilerChunkLoader of(ClassLoader classLoader, CompilerSettings compilerSettings,
|
|
||||||
String rootClassPrefix) {
|
|
||||||
return new CompilerChunkLoader(classLoader, new LuaCompiler(compilerSettings), rootClassPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new instance of {@code CompilerChunkLoader} that uses the specified
|
|
||||||
* class loader {@code classLoader} to load classes it compiles using a compiler
|
|
||||||
* instantiated with {@linkplain CompilerSettings#defaultSettings() default settings},
|
|
||||||
* with every main chunk class having the class name {@code rootClassPrefix} followed
|
|
||||||
* by a monotonically-increasing integer suffix.
|
|
||||||
*
|
|
||||||
* @param classLoader the class loader used by this chunk loader, must not be {@code null}
|
|
||||||
* @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null}
|
|
||||||
* @return a new instance of {@code CompilerChunkLoader}
|
|
||||||
* @throws NullPointerException if {@code classLoader} or {@code rootClassPrefix} is {@code null}
|
|
||||||
*/
|
|
||||||
public static CompilerChunkLoader of(ClassLoader classLoader, String rootClassPrefix) {
|
|
||||||
return of(classLoader, CompilerSettings.defaultSettings(), rootClassPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new instance of {@code CompilerChunkLoader} that uses the class loader
|
|
||||||
* that loaded the {@code CompilerChunkLoader} class to load classes it compiles using
|
|
||||||
* {@code compiler}, with every main chunk class having the class name {@code rootClassPrefix}
|
|
||||||
* followed by a monotonically-increasing integer suffix.
|
|
||||||
*
|
|
||||||
* @param compiler the compiler instance used by this chunk loader, must not be {@code null}
|
|
||||||
* @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null}
|
|
||||||
* @return a new instance of {@code CompilerChunkLoader}
|
|
||||||
* @throws NullPointerException {@code compiler} or {@code rootClassPrefix} is {@code null}
|
|
||||||
*/
|
|
||||||
public static CompilerChunkLoader of(LuaCompiler compiler, String rootClassPrefix) {
|
|
||||||
return of(CompilerChunkLoader.class.getClassLoader(), compiler, rootClassPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new instance of {@code CompilerChunkLoader} that uses the class loader
|
|
||||||
* that loaded the {@code CompilerChunkLoader} class to load classes it compiles using
|
|
||||||
* a new instance of the Lua compiler with the settings {@code compilerSettings},
|
|
||||||
* with every main chunk class having the class name {@code rootClassPrefix} followed
|
|
||||||
* by a monotonically-increasing integer suffix.
|
|
||||||
*
|
|
||||||
* @param compilerSettings the compiler settings used to instantiate the compiler, must not be
|
|
||||||
* {@code null}
|
|
||||||
* @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null}
|
|
||||||
* @return a new instance of {@code CompilerChunkLoader}
|
|
||||||
* @throws NullPointerException if {@code compilerSettings} or {@code rootClassPrefix} is {@code
|
|
||||||
* null}
|
|
||||||
*/
|
|
||||||
public static CompilerChunkLoader of(CompilerSettings compilerSettings, String rootClassPrefix) {
|
|
||||||
return of(new LuaCompiler(compilerSettings), rootClassPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new instance of {@code CompilerChunkLoader} that uses the class loader
|
|
||||||
* that loaded the {@code CompilerChunkLoader} class to load classes it compiles using
|
|
||||||
* a compiler instantiated with
|
|
||||||
* {@linkplain CompilerSettings#defaultSettings() default settings},
|
|
||||||
* with every main chunk class having the class name {@code rootClassPrefix} followed
|
|
||||||
* by a monotonically-increasing integer suffix.
|
|
||||||
*
|
|
||||||
* @param rootClassPrefix the class name prefix for compiled classes, must not be {@code null}
|
|
||||||
* @return a new instance of {@code CompilerChunkLoader}
|
|
||||||
* @throws NullPointerException if {@code rootClassPrefix} is {@code null}
|
|
||||||
*/
|
|
||||||
public static CompilerChunkLoader of(String rootClassPrefix) {
|
|
||||||
return of(CompilerSettings.defaultSettings(), rootClassPrefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChunkClassLoader getChunkClassLoader() {
|
|
||||||
return chunkClassLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LuaFunction<Variable, ?, ?, ?, ?> loadTextChunk(Variable env, String chunkName,
|
|
||||||
String sourceText) throws LoaderException {
|
|
||||||
Objects.requireNonNull(env);
|
|
||||||
Objects.requireNonNull(chunkName);
|
|
||||||
Objects.requireNonNull(sourceText);
|
|
||||||
|
|
||||||
return compileTextChunk(chunkName, sourceText).newInstance(env);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkFactory compileTextChunk(String chunkName, String sourceText) throws LoaderException {
|
|
||||||
Objects.requireNonNull(chunkName);
|
|
||||||
Objects.requireNonNull(sourceText);
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
try {
|
|
||||||
String rootClassName = rootClassPrefix + (idx++);
|
|
||||||
|
|
||||||
CompiledModule result = compiler.compile(sourceText, chunkName, rootClassName);
|
|
||||||
|
|
||||||
String mainClassName = chunkClassLoader.install(result);
|
|
||||||
//noinspection unchecked
|
|
||||||
return new ChunkFactory(
|
|
||||||
(Class<? extends LuaFunction<Variable, ?, ?, ?, ?>>) chunkClassLoader
|
|
||||||
.loadClass(mainClassName), chunkName);
|
|
||||||
} catch (TokenMgrError ex) {
|
|
||||||
String msg = ex.getMessage();
|
|
||||||
int line = 0; // TODO
|
|
||||||
boolean partial = msg != null && msg
|
|
||||||
.contains("Encountered: <EOF>"); // TODO: is there really no better way?
|
|
||||||
throw new LoaderException(ex, chunkName, line, partial);
|
|
||||||
} catch (ParseException ex) {
|
|
||||||
boolean partial = ex.currentToken != null
|
|
||||||
&& ex.currentToken.next != null
|
|
||||||
&& ex.currentToken.next.kind == Parser.EOF;
|
|
||||||
int line = ex.currentToken != null
|
|
||||||
? ex.currentToken.beginLine
|
|
||||||
: 0;
|
|
||||||
throw new LoaderException(ex, chunkName, line, partial);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new LoaderException(e, chunkName, 0, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public LuaFunction loadBinaryChunk(Variable env, String chunkName, byte[] bytes, int offset, int len) throws LoaderException {
|
|
||||||
// throw new UnsupportedOperationException(); // TODO
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,291 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An immutable class encapsulating the settings of the compilation of Lua to Java bytecode
|
|
||||||
* by {@link LuaCompiler}.
|
|
||||||
*
|
|
||||||
* <p>The settings are the following</p>
|
|
||||||
* <ul>
|
|
||||||
* <li><b>CPU accounting</b> ({@link CPUAccountingMode}): controls whether and how
|
|
||||||
* the functions generated by the compiler account for number of ticks spent in execution
|
|
||||||
* and pause when their time slice has expired;</li>
|
|
||||||
* <li><b>const folding</b> (boolean): when {@code true}, constants are folded at compile
|
|
||||||
* time (note that this does not have an influence on the number of ticks counted);</li>
|
|
||||||
* <li><b>const caching</b> (boolean): when {@code true}, boxed numeric constants are stored
|
|
||||||
* as static fields rather than being instantiated (and boxed) at execution time;</li>
|
|
||||||
* <li><b>node size limit</b> (int): when positive, long functions are split up into smaller
|
|
||||||
* Java methods (each containing at most the specified number of IR nodes); otherwise,
|
|
||||||
* a single method containing the entire function code is generated. Java class files
|
|
||||||
* impose a strict limit of 64 kB per method: this setting allows the compilation
|
|
||||||
* of arbitrarily-long Lua functions.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>To obtain the settings with sensible defaults, use {@link CompilerSettings#defaultSettings()}.
|
|
||||||
* To obtain the default settings with CPU accounting disabled,
|
|
||||||
* use {@link CompilerSettings#defaultNoAccountingSettings()}.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public final class CompilerSettings {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default CPU accounting mode.
|
|
||||||
*/
|
|
||||||
public static final CPUAccountingMode DEFAULT_CPU_ACCOUNTING_MODE = CPUAccountingMode.IN_EVERY_BASIC_BLOCK;
|
|
||||||
/**
|
|
||||||
* The default const folding mode.
|
|
||||||
*/
|
|
||||||
public static final boolean DEFAULT_CONST_FOLDING_MODE = true;
|
|
||||||
/**
|
|
||||||
* The default const caching mode.
|
|
||||||
*/
|
|
||||||
public static final boolean DEFAULT_CONST_CACHING_MODE = true;
|
|
||||||
/**
|
|
||||||
* The default byte string mode.
|
|
||||||
*/
|
|
||||||
public static final boolean DEFAULT_BYTE_STRING_MODE = true;
|
|
||||||
/**
|
|
||||||
* The default method size limit.
|
|
||||||
*/
|
|
||||||
public static final int DEFAULT_NODE_SIZE_LIMIT = 2000;
|
|
||||||
private final CPUAccountingMode cpuAccountingMode;
|
|
||||||
private final boolean constFolding;
|
|
||||||
private final boolean constCaching;
|
|
||||||
private final boolean byteStrings;
|
|
||||||
private final int nodeSizeLimit;
|
|
||||||
CompilerSettings(
|
|
||||||
CPUAccountingMode cpuAccountingMode,
|
|
||||||
boolean constFolding,
|
|
||||||
boolean constCaching,
|
|
||||||
boolean byteStrings,
|
|
||||||
int nodeSizeLimit) {
|
|
||||||
|
|
||||||
this.cpuAccountingMode = Objects.requireNonNull(cpuAccountingMode);
|
|
||||||
this.constFolding = constFolding;
|
|
||||||
this.constCaching = constCaching;
|
|
||||||
this.byteStrings = byteStrings;
|
|
||||||
this.nodeSizeLimit = nodeSizeLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the compiler settings with the given parameters.
|
|
||||||
*
|
|
||||||
* <p>When {@code nodeSizeLimit} is non-positive, no chunking of the body method
|
|
||||||
* will be performed.</p>
|
|
||||||
*
|
|
||||||
* @param cpuAccountingMode CPU accounting mode, must not be {@code null}
|
|
||||||
* @param constFolding const folding mode
|
|
||||||
* @param constCaching const caching mode
|
|
||||||
* @param byteStrings byte string mode
|
|
||||||
* @param nodeSizeLimit node size limit
|
|
||||||
* @return the corresponding compiler settings
|
|
||||||
* @throws NullPointerException if {@code cpuAccountingMode} is {@code null}
|
|
||||||
*/
|
|
||||||
public static CompilerSettings of(
|
|
||||||
CPUAccountingMode cpuAccountingMode,
|
|
||||||
boolean constFolding,
|
|
||||||
boolean constCaching,
|
|
||||||
boolean byteStrings,
|
|
||||||
int nodeSizeLimit) {
|
|
||||||
|
|
||||||
return new CompilerSettings(
|
|
||||||
cpuAccountingMode, constFolding, constCaching, byteStrings, nodeSizeLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default compiler settings.
|
|
||||||
*
|
|
||||||
* @return the default compiler settings
|
|
||||||
*/
|
|
||||||
public static CompilerSettings defaultSettings() {
|
|
||||||
return CompilerSettings.of(
|
|
||||||
DEFAULT_CPU_ACCOUNTING_MODE,
|
|
||||||
DEFAULT_CONST_FOLDING_MODE,
|
|
||||||
DEFAULT_CONST_CACHING_MODE,
|
|
||||||
DEFAULT_BYTE_STRING_MODE,
|
|
||||||
DEFAULT_NODE_SIZE_LIMIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default compiler settings without CPU accounting.
|
|
||||||
*
|
|
||||||
* @return the default compiler settings without CPU accounting
|
|
||||||
*/
|
|
||||||
public static CompilerSettings defaultNoAccountingSettings() {
|
|
||||||
return defaultSettings().withCPUAccountingMode(CPUAccountingMode.NO_CPU_ACCOUNTING);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompilerSettings that = (CompilerSettings) o;
|
|
||||||
|
|
||||||
return this.cpuAccountingMode == that.cpuAccountingMode
|
|
||||||
&& this.constFolding == that.constFolding
|
|
||||||
&& this.constCaching == that.constCaching
|
|
||||||
&& this.byteStrings == that.byteStrings
|
|
||||||
&& this.nodeSizeLimit == that.nodeSizeLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = cpuAccountingMode.hashCode();
|
|
||||||
result = 31 * result + (constFolding ? 1 : 0);
|
|
||||||
result = 31 * result + (constCaching ? 1 : 0);
|
|
||||||
result = 31 * result + (byteStrings ? 1 : 0);
|
|
||||||
result = 31 * result + nodeSizeLimit;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the CPU accounting mode.
|
|
||||||
*
|
|
||||||
* @return the CPU accounting mode
|
|
||||||
*/
|
|
||||||
public CPUAccountingMode cpuAccountingMode() {
|
|
||||||
return cpuAccountingMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the const folding mode.
|
|
||||||
*
|
|
||||||
* @return the const folding mode
|
|
||||||
*/
|
|
||||||
public boolean constFolding() {
|
|
||||||
return constFolding;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the const caching mode.
|
|
||||||
*
|
|
||||||
* @return the const caching mode
|
|
||||||
*/
|
|
||||||
public boolean constCaching() {
|
|
||||||
return constCaching;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean byteStrings() {
|
|
||||||
return byteStrings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the node size limit.
|
|
||||||
*
|
|
||||||
* @return the node size limit
|
|
||||||
*/
|
|
||||||
public int nodeSizeLimit() {
|
|
||||||
return nodeSizeLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns compiler settings derived from this compiler settings by updating
|
|
||||||
* the CPU accounting mode to {@code mode}.
|
|
||||||
*
|
|
||||||
* @param mode new CPU accounting mode, must not be {@code null}
|
|
||||||
* @return settings derived from {@code this} by updating the CPU accounting mode to {@code mode}
|
|
||||||
* @throws NullPointerException if {@code mode} is {@code null}
|
|
||||||
*/
|
|
||||||
public CompilerSettings withCPUAccountingMode(CPUAccountingMode mode) {
|
|
||||||
return mode != this.cpuAccountingMode
|
|
||||||
? new CompilerSettings(mode, constFolding, constCaching, byteStrings, nodeSizeLimit)
|
|
||||||
: this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns compiler settings derived from this compiler settings by updating
|
|
||||||
* the const folding mode to {@code mode}.
|
|
||||||
*
|
|
||||||
* @param mode new const folding mode
|
|
||||||
* @return settings derived from {@code this} by updating the const folding mode to {@code mode}
|
|
||||||
*/
|
|
||||||
public CompilerSettings withConstFolding(boolean mode) {
|
|
||||||
return mode != this.constFolding
|
|
||||||
? new CompilerSettings(cpuAccountingMode, mode, constCaching, byteStrings, nodeSizeLimit)
|
|
||||||
: this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns compiler settings derived from this compiler settings by updating
|
|
||||||
* the const caching mode to {@code mode}.
|
|
||||||
*
|
|
||||||
* @param mode new const caching mode
|
|
||||||
* @return settings derived from {@code this} by updating the const caching mode to {@code mode}
|
|
||||||
*/
|
|
||||||
public CompilerSettings withConstCaching(boolean mode) {
|
|
||||||
return mode != this.constCaching
|
|
||||||
? new CompilerSettings(cpuAccountingMode, constFolding, mode, byteStrings, nodeSizeLimit)
|
|
||||||
: this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns compiler settings derived from this compiler settings by updating
|
|
||||||
* the byte string mode to {@code mode}.
|
|
||||||
*
|
|
||||||
* @param mode new byte string mode
|
|
||||||
* @return settings derived from {@code this} by updating the byte string mode to {@code mode}
|
|
||||||
*/
|
|
||||||
public CompilerSettings withByteStrings(boolean mode) {
|
|
||||||
return mode != this.byteStrings
|
|
||||||
? new CompilerSettings(cpuAccountingMode, constFolding, constCaching, mode, nodeSizeLimit)
|
|
||||||
: this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns compiler settings derived from this compiler settings by updating
|
|
||||||
* the node size limit to {@code limit}.
|
|
||||||
*
|
|
||||||
* @param limit new node size limit
|
|
||||||
* @return settings derived from {@code this} by updating the node size limit to {@code limit}
|
|
||||||
*/
|
|
||||||
public CompilerSettings withNodeSizeLimit(int limit) {
|
|
||||||
return limit != this.nodeSizeLimit
|
|
||||||
? new CompilerSettings(cpuAccountingMode, constFolding, constCaching, byteStrings, limit)
|
|
||||||
: this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CPU accounting mode.
|
|
||||||
*/
|
|
||||||
public enum CPUAccountingMode {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not do CPU accounting.
|
|
||||||
*/
|
|
||||||
NO_CPU_ACCOUNTING,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check CPU time usage at the beginning of every basic block.
|
|
||||||
*
|
|
||||||
* <p>At the beginning of every basic block, update the ticks by invoking
|
|
||||||
* {@link org.classdump.luna.runtime.ExecutionContext#registerTicks(int)}
|
|
||||||
* and potentially pause by invoking
|
|
||||||
* {@link org.classdump.luna.runtime.ExecutionContext#pauseIfRequested()}.</p>
|
|
||||||
*/
|
|
||||||
IN_EVERY_BASIC_BLOCK
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.compiler.gen.ClassNameTranslator;
|
|
||||||
import org.classdump.luna.util.Check;
|
|
||||||
|
|
||||||
public class FunctionId {
|
|
||||||
|
|
||||||
public static final Comparator<FunctionId> LEXICOGRAPHIC_COMPARATOR = new Comparator<FunctionId>() {
|
|
||||||
@Override
|
|
||||||
public int compare(FunctionId a, FunctionId b) {
|
|
||||||
int la = a.indices.size();
|
|
||||||
int lb = b.indices.size();
|
|
||||||
|
|
||||||
int len = Math.min(la, lb);
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
int ai = a.indices.get(i);
|
|
||||||
int bi = b.indices.get(i);
|
|
||||||
|
|
||||||
int diff = ai - bi;
|
|
||||||
if (diff != 0) {
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return la - lb;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private final static FunctionId ROOT = new FunctionId(Collections.<Integer>emptyList());
|
|
||||||
private final List<Integer> indices;
|
|
||||||
|
|
||||||
private FunctionId(List<Integer> indices) {
|
|
||||||
this.indices = Objects.requireNonNull(indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FunctionId root() {
|
|
||||||
return ROOT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FunctionId fromIndices(List<Integer> indices) {
|
|
||||||
Objects.requireNonNull(indices); // FIXME: make a copy?
|
|
||||||
return indices.isEmpty() ? root() : new FunctionId(indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FunctionId that = (FunctionId) o;
|
|
||||||
|
|
||||||
return this.indices.equals(that.indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return indices.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder bld = new StringBuilder();
|
|
||||||
Iterator<Integer> it = indices.iterator();
|
|
||||||
bld.append("/");
|
|
||||||
while (it.hasNext()) {
|
|
||||||
int i = it.next();
|
|
||||||
bld.append(i);
|
|
||||||
if (it.hasNext()) {
|
|
||||||
bld.append("/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bld.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Integer> indices() {
|
|
||||||
return indices;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRoot() {
|
|
||||||
return indices.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public FunctionId child(int index) {
|
|
||||||
Check.nonNegative(index);
|
|
||||||
List<Integer> childIndices = new ArrayList<>(indices.size() + 1);
|
|
||||||
childIndices.addAll(indices);
|
|
||||||
childIndices.add(index);
|
|
||||||
return new FunctionId(Collections.unmodifiableList(childIndices));
|
|
||||||
}
|
|
||||||
|
|
||||||
public FunctionId parent() {
|
|
||||||
if (isRoot()) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
List<Integer> subIndices = indices.subList(0, indices.size() - 1);
|
|
||||||
return new FunctionId(subIndices);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toClassName(ClassNameTranslator tr) {
|
|
||||||
Objects.requireNonNull(tr);
|
|
||||||
for (Integer index : indices) {
|
|
||||||
tr = tr.child(index);
|
|
||||||
}
|
|
||||||
return tr.className();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.compiler.ir.Code;
|
|
||||||
import org.classdump.luna.compiler.ir.UpVar;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
|
|
||||||
public class IRFunc {
|
|
||||||
|
|
||||||
private final FunctionId id;
|
|
||||||
private final List<Var> params;
|
|
||||||
private final boolean vararg;
|
|
||||||
private final List<UpVar> upvals;
|
|
||||||
private final Code code;
|
|
||||||
|
|
||||||
public IRFunc(FunctionId id, List<Var> params, boolean vararg, List<UpVar> upvals, Code code) {
|
|
||||||
this.id = Objects.requireNonNull(id);
|
|
||||||
this.params = Objects.requireNonNull(params);
|
|
||||||
this.vararg = vararg;
|
|
||||||
this.upvals = Objects.requireNonNull(upvals);
|
|
||||||
this.code = Objects.requireNonNull(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
IRFunc that = (IRFunc) o;
|
|
||||||
return id.equals(that.id)
|
|
||||||
&& params.equals(that.params)
|
|
||||||
&& upvals.equals(that.upvals)
|
|
||||||
&& code.equals(that.code);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(id, params, code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FunctionId id() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Var> params() {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVararg() {
|
|
||||||
return vararg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<UpVar> upvals() {
|
|
||||||
return upvals;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Code code() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRFunc update(Code code) {
|
|
||||||
if (this.code.equals(code)) {
|
|
||||||
return this;
|
|
||||||
} else {
|
|
||||||
return new IRFunc(id, params, vararg, upvals, code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import org.classdump.luna.parser.ast.Chunk;
|
|
||||||
|
|
||||||
public class IRTranslator {
|
|
||||||
|
|
||||||
public static Module translate(Chunk chunk) {
|
|
||||||
ModuleBuilder moduleBuilder = new ModuleBuilder();
|
|
||||||
IRTranslatorTransformer translator = new IRTranslatorTransformer(moduleBuilder);
|
|
||||||
translator.transform(chunk);
|
|
||||||
return moduleBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,238 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.analysis.DependencyAnalyser;
|
|
||||||
import org.classdump.luna.compiler.analysis.DependencyInfo;
|
|
||||||
import org.classdump.luna.compiler.analysis.LivenessAnalyser;
|
|
||||||
import org.classdump.luna.compiler.analysis.LivenessInfo;
|
|
||||||
import org.classdump.luna.compiler.analysis.SlotAllocInfo;
|
|
||||||
import org.classdump.luna.compiler.analysis.SlotAllocator;
|
|
||||||
import org.classdump.luna.compiler.analysis.TypeInfo;
|
|
||||||
import org.classdump.luna.compiler.analysis.Typer;
|
|
||||||
import org.classdump.luna.compiler.gen.BytecodeEmitter;
|
|
||||||
import org.classdump.luna.compiler.gen.ClassNameTranslator;
|
|
||||||
import org.classdump.luna.compiler.gen.CompiledClass;
|
|
||||||
import org.classdump.luna.compiler.gen.SuffixingClassNameTranslator;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.ASMBytecodeEmitter;
|
|
||||||
import org.classdump.luna.compiler.tf.BranchInliner;
|
|
||||||
import org.classdump.luna.compiler.tf.CPUAccounter;
|
|
||||||
import org.classdump.luna.compiler.tf.CodeSimplifier;
|
|
||||||
import org.classdump.luna.compiler.tf.ConstFolder;
|
|
||||||
import org.classdump.luna.compiler.tf.DeadCodePruner;
|
|
||||||
import org.classdump.luna.parser.ParseException;
|
|
||||||
import org.classdump.luna.parser.Parser;
|
|
||||||
import org.classdump.luna.parser.TokenMgrError;
|
|
||||||
import org.classdump.luna.parser.analysis.NameResolver;
|
|
||||||
import org.classdump.luna.parser.ast.Chunk;
|
|
||||||
import org.classdump.luna.util.ByteVector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Lua-to-Java-bytecode compiler.
|
|
||||||
*/
|
|
||||||
public class LuaCompiler {
|
|
||||||
|
|
||||||
private final CompilerSettings settings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new compiler instance with the given settings.
|
|
||||||
*
|
|
||||||
* @param settings the settings, must not be {@code null}
|
|
||||||
* @throws NullPointerException if {@code settings} is {@code null}
|
|
||||||
*/
|
|
||||||
public LuaCompiler(CompilerSettings settings) {
|
|
||||||
this.settings = Objects.requireNonNull(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new compiler instance with
|
|
||||||
* {@linkplain CompilerSettings#defaultSettings() default settings}.
|
|
||||||
*/
|
|
||||||
public LuaCompiler() {
|
|
||||||
this(CompilerSettings.defaultSettings());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Chunk parse(String sourceText) throws ParseException, TokenMgrError {
|
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(sourceText.getBytes());
|
|
||||||
Parser parser = new Parser(bais);
|
|
||||||
return parser.Chunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Module translate(Chunk chunk) {
|
|
||||||
chunk = NameResolver.resolveNames(chunk);
|
|
||||||
return IRTranslator.translate(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the settings used in this compiler instance.
|
|
||||||
*
|
|
||||||
* @return the settings used by this compiler
|
|
||||||
*/
|
|
||||||
public CompilerSettings settings() {
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Iterable<IRFunc> sortTopologically(Module module) {
|
|
||||||
// TODO
|
|
||||||
return module.fns();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRFunc optimise(IRFunc fn) {
|
|
||||||
IRFunc oldFn;
|
|
||||||
|
|
||||||
do {
|
|
||||||
oldFn = fn;
|
|
||||||
|
|
||||||
TypeInfo typeInfo = Typer.analyseTypes(fn);
|
|
||||||
|
|
||||||
fn = CPUAccounter.collectCPUAccounting(fn);
|
|
||||||
// fn = BranchInliner.inlineBranches(fn, typeInfo);
|
|
||||||
|
|
||||||
if (settings.constFolding()) {
|
|
||||||
fn = ConstFolder.replaceConstOperations(fn, typeInfo);
|
|
||||||
LivenessInfo liveness = LivenessAnalyser.computeLiveness(fn);
|
|
||||||
fn = DeadCodePruner.pruneDeadCode(fn, typeInfo, liveness);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn = CodeSimplifier.pruneUnreachableCode(fn);
|
|
||||||
fn = CodeSimplifier.mergeBlocks(fn);
|
|
||||||
|
|
||||||
} while (!oldFn.equals(fn));
|
|
||||||
|
|
||||||
return fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessedFunc processFunction(IRFunc fn) {
|
|
||||||
fn = CPUAccounter.insertCPUAccounting(fn);
|
|
||||||
fn = optimise(fn);
|
|
||||||
|
|
||||||
SlotAllocInfo slots = SlotAllocator.allocateSlots(fn);
|
|
||||||
TypeInfo types = Typer.analyseTypes(fn);
|
|
||||||
DependencyInfo deps = DependencyAnalyser.analyse(fn);
|
|
||||||
|
|
||||||
return new ProcessedFunc(fn, slots, types, deps);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Iterable<ProcessedFunc> processModule(Module m) {
|
|
||||||
Map<FunctionId, ProcessedFunc> pfs = new HashMap<>();
|
|
||||||
|
|
||||||
for (IRFunc fn : sortTopologically(m)) {
|
|
||||||
ProcessedFunc pf = processFunction(fn);
|
|
||||||
pfs.put(fn.id(), pf);
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessedFunc main = pfs.get(FunctionId.root());
|
|
||||||
assert (main != null);
|
|
||||||
|
|
||||||
Set<ProcessedFunc> result = new HashSet<>();
|
|
||||||
Deque<ProcessedFunc> open = new ArrayDeque<>();
|
|
||||||
|
|
||||||
// only add functions reachable from main
|
|
||||||
open.add(main);
|
|
||||||
while (!open.isEmpty()) {
|
|
||||||
ProcessedFunc pf = open.pop();
|
|
||||||
if (!result.contains(pf)) {
|
|
||||||
result.add(pf);
|
|
||||||
for (FunctionId id : pf.deps.nestedRefs()) {
|
|
||||||
open.push(pfs.get(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompiledClass compileFunction(ProcessedFunc pf, String sourceFileName,
|
|
||||||
String rootClassName) {
|
|
||||||
ClassNameTranslator classNameTranslator = new SuffixingClassNameTranslator(rootClassName);
|
|
||||||
BytecodeEmitter emitter = new ASMBytecodeEmitter(
|
|
||||||
pf.fn, pf.slots, pf.types, pf.deps,
|
|
||||||
settings, classNameTranslator,
|
|
||||||
sourceFileName);
|
|
||||||
return emitter.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compiles the Lua source string {@code sourceText} into Java bytecode, giving the main
|
|
||||||
* class the name {@code rootClassName}, and using {@code sourceFileName} as the name
|
|
||||||
* of the source file (for debugging information),
|
|
||||||
*
|
|
||||||
* @param sourceText source text, must not be {@code null}
|
|
||||||
* @param sourceFileName file name of the source, must not be {@code null}
|
|
||||||
* @param rootClassName class name of the main class, must not be {@code null}
|
|
||||||
* @return {@code sourceText} compiled into a loadable module
|
|
||||||
* @throws NullPointerException if {@code sourceText}, {@code sourceFileName} or {@code
|
|
||||||
* rootClassName} is {@code null}
|
|
||||||
* @throws TokenMgrError when {@code sourceText} cannot be lexically analysed following the Lua
|
|
||||||
* lexical rules
|
|
||||||
* @throws ParseException when {@code sourceText} cannot be parsed following the Lua grammar
|
|
||||||
*/
|
|
||||||
public CompiledModule compile(String sourceText, String sourceFileName, String rootClassName)
|
|
||||||
throws ParseException, TokenMgrError {
|
|
||||||
|
|
||||||
Objects.requireNonNull(sourceText);
|
|
||||||
Chunk ast = parse(sourceText);
|
|
||||||
Module module = translate(ast);
|
|
||||||
|
|
||||||
Iterable<ProcessedFunc> pfs = processModule(module);
|
|
||||||
|
|
||||||
Map<String, ByteVector> classMap = new HashMap<>();
|
|
||||||
String mainClass = null;
|
|
||||||
for (ProcessedFunc pf : pfs) {
|
|
||||||
CompiledClass cc = compileFunction(pf, sourceFileName, rootClassName);
|
|
||||||
|
|
||||||
if (pf.fn.id().isRoot()) {
|
|
||||||
assert (mainClass == null);
|
|
||||||
mainClass = cc.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
classMap.put(cc.name(), cc.bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainClass == null) {
|
|
||||||
throw new IllegalStateException("Module main class not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CompiledModule(Collections.unmodifiableMap(classMap), mainClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ProcessedFunc {
|
|
||||||
|
|
||||||
public final IRFunc fn;
|
|
||||||
public final SlotAllocInfo slots;
|
|
||||||
public final TypeInfo types;
|
|
||||||
public final DependencyInfo deps;
|
|
||||||
|
|
||||||
private ProcessedFunc(IRFunc fn, SlotAllocInfo slots, TypeInfo types, DependencyInfo deps) {
|
|
||||||
this.fn = Objects.requireNonNull(fn);
|
|
||||||
this.slots = Objects.requireNonNull(slots);
|
|
||||||
this.types = Objects.requireNonNull(types);
|
|
||||||
this.deps = Objects.requireNonNull(deps);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class Module {
|
|
||||||
|
|
||||||
private final List<IRFunc> fns;
|
|
||||||
|
|
||||||
public Module(List<IRFunc> fns) {
|
|
||||||
this.fns = Objects.requireNonNull(fns);
|
|
||||||
verify();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verify() {
|
|
||||||
Set<FunctionId> ids = new HashSet<>();
|
|
||||||
boolean hasMain = false;
|
|
||||||
for (IRFunc fn : fns) {
|
|
||||||
if (!ids.add(fn.id())) {
|
|
||||||
throw new IllegalStateException("Function " + fn.id() + " defined more than once");
|
|
||||||
}
|
|
||||||
if (fn.id().isRoot()) {
|
|
||||||
hasMain = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasMain) {
|
|
||||||
throw new IllegalStateException("No main function in module");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IRFunc> fns() {
|
|
||||||
return fns;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRFunc get(FunctionId id) {
|
|
||||||
Objects.requireNonNull(id);
|
|
||||||
|
|
||||||
for (IRFunc fn : fns) {
|
|
||||||
if (fn.id().equals(id)) {
|
|
||||||
return fn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRFunc main() {
|
|
||||||
return get(FunctionId.root());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class ModuleBuilder {
|
|
||||||
|
|
||||||
private final List<IRFunc> fns;
|
|
||||||
|
|
||||||
public ModuleBuilder() {
|
|
||||||
this.fns = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(IRFunc fn) {
|
|
||||||
fns.add(Objects.requireNonNull(fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Module build() {
|
|
||||||
return new Module(Collections.unmodifiableList(fns));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler;
|
|
||||||
|
|
||||||
import org.classdump.luna.compiler.ir.BinOp;
|
|
||||||
import org.classdump.luna.compiler.ir.UnOp;
|
|
||||||
import org.classdump.luna.parser.analysis.FunctionVarInfo;
|
|
||||||
import org.classdump.luna.parser.analysis.ResolvedLabel;
|
|
||||||
import org.classdump.luna.parser.analysis.ResolvedVariable;
|
|
||||||
import org.classdump.luna.parser.analysis.VarMapping;
|
|
||||||
import org.classdump.luna.parser.ast.Chunk;
|
|
||||||
import org.classdump.luna.parser.ast.GotoStatement;
|
|
||||||
import org.classdump.luna.parser.ast.LabelStatement;
|
|
||||||
import org.classdump.luna.parser.ast.Operator;
|
|
||||||
import org.classdump.luna.parser.ast.SyntaxElement;
|
|
||||||
import org.classdump.luna.parser.ast.VarExpr;
|
|
||||||
import org.classdump.luna.parser.ast.util.AttributeUtils;
|
|
||||||
|
|
||||||
abstract class TranslationUtils {
|
|
||||||
|
|
||||||
private TranslationUtils() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BinOp.Op bop(Operator.Binary bop) {
|
|
||||||
switch (bop) {
|
|
||||||
case ADD:
|
|
||||||
return BinOp.Op.ADD;
|
|
||||||
case SUB:
|
|
||||||
return BinOp.Op.SUB;
|
|
||||||
case MUL:
|
|
||||||
return BinOp.Op.MUL;
|
|
||||||
case DIV:
|
|
||||||
return BinOp.Op.DIV;
|
|
||||||
case IDIV:
|
|
||||||
return BinOp.Op.IDIV;
|
|
||||||
case MOD:
|
|
||||||
return BinOp.Op.MOD;
|
|
||||||
case POW:
|
|
||||||
return BinOp.Op.POW;
|
|
||||||
|
|
||||||
case CONCAT:
|
|
||||||
return BinOp.Op.CONCAT;
|
|
||||||
|
|
||||||
case BAND:
|
|
||||||
return BinOp.Op.BAND;
|
|
||||||
case BOR:
|
|
||||||
return BinOp.Op.BOR;
|
|
||||||
case BXOR:
|
|
||||||
return BinOp.Op.BXOR;
|
|
||||||
case SHL:
|
|
||||||
return BinOp.Op.SHL;
|
|
||||||
case SHR:
|
|
||||||
return BinOp.Op.SHR;
|
|
||||||
|
|
||||||
case EQ:
|
|
||||||
return BinOp.Op.EQ;
|
|
||||||
case NEQ:
|
|
||||||
return BinOp.Op.NEQ;
|
|
||||||
case LT:
|
|
||||||
return BinOp.Op.LT;
|
|
||||||
case LE:
|
|
||||||
return BinOp.Op.LE;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UnOp.Op uop(Operator.Unary uop) {
|
|
||||||
switch (uop) {
|
|
||||||
case UNM:
|
|
||||||
return UnOp.Op.UNM;
|
|
||||||
case BNOT:
|
|
||||||
return UnOp.Op.BNOT;
|
|
||||||
case LEN:
|
|
||||||
return UnOp.Op.LEN;
|
|
||||||
case NOT:
|
|
||||||
return UnOp.Op.NOT;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResolvedVariable resolved(VarExpr e) {
|
|
||||||
ResolvedVariable rv = e.attributes().get(ResolvedVariable.class);
|
|
||||||
if (rv == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unresolved variable '" + e.name().value() + "' at " + AttributeUtils
|
|
||||||
.sourceInfoString(e));
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResolvedLabel resolvedLabel(LabelStatement e) {
|
|
||||||
ResolvedLabel rl = e.attributes().get(ResolvedLabel.class);
|
|
||||||
if (rl == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unresolved label '" + e.labelName().value() + "' at " + AttributeUtils
|
|
||||||
.sourceInfoString(e));
|
|
||||||
}
|
|
||||||
return rl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ResolvedLabel resolvedLabel(GotoStatement e) {
|
|
||||||
ResolvedLabel rl = e.attributes().get(ResolvedLabel.class);
|
|
||||||
if (rl == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unresolved goto '" + e.labelName().value() + "' at " + AttributeUtils
|
|
||||||
.sourceInfoString(e));
|
|
||||||
}
|
|
||||||
return rl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FunctionVarInfo funcVarInfo(SyntaxElement e) {
|
|
||||||
FunctionVarInfo info = e.attributes().get(FunctionVarInfo.class);
|
|
||||||
if (info == null) {
|
|
||||||
throw new IllegalStateException("No var info at " + AttributeUtils.sourceInfoString(e));
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FunctionVarInfo funcVarInfo(Chunk c) {
|
|
||||||
FunctionVarInfo info = c.attributes().get(FunctionVarInfo.class);
|
|
||||||
if (info == null) {
|
|
||||||
throw new IllegalStateException("No var info in chunk");
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static VarMapping varMapping(SyntaxElement e) {
|
|
||||||
VarMapping vm = e.attributes().get(VarMapping.class);
|
|
||||||
if (vm == null) {
|
|
||||||
throw new IllegalStateException("No var mapping at " + AttributeUtils.sourceInfoString(e));
|
|
||||||
}
|
|
||||||
return vm;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,290 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVar;
|
|
||||||
import org.classdump.luna.compiler.ir.BinOp;
|
|
||||||
import org.classdump.luna.compiler.ir.Branch;
|
|
||||||
import org.classdump.luna.compiler.ir.Call;
|
|
||||||
import org.classdump.luna.compiler.ir.Closure;
|
|
||||||
import org.classdump.luna.compiler.ir.IRVisitor;
|
|
||||||
import org.classdump.luna.compiler.ir.Jmp;
|
|
||||||
import org.classdump.luna.compiler.ir.Label;
|
|
||||||
import org.classdump.luna.compiler.ir.LoadConst;
|
|
||||||
import org.classdump.luna.compiler.ir.MultiGet;
|
|
||||||
import org.classdump.luna.compiler.ir.MultiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiLoad;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiStore;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.Ret;
|
|
||||||
import org.classdump.luna.compiler.ir.TCall;
|
|
||||||
import org.classdump.luna.compiler.ir.TabGet;
|
|
||||||
import org.classdump.luna.compiler.ir.TabNew;
|
|
||||||
import org.classdump.luna.compiler.ir.TabRawAppendMulti;
|
|
||||||
import org.classdump.luna.compiler.ir.TabRawSet;
|
|
||||||
import org.classdump.luna.compiler.ir.TabRawSetInt;
|
|
||||||
import org.classdump.luna.compiler.ir.TabSet;
|
|
||||||
import org.classdump.luna.compiler.ir.ToNext;
|
|
||||||
import org.classdump.luna.compiler.ir.ToNumber;
|
|
||||||
import org.classdump.luna.compiler.ir.UnOp;
|
|
||||||
import org.classdump.luna.compiler.ir.UpLoad;
|
|
||||||
import org.classdump.luna.compiler.ir.UpStore;
|
|
||||||
import org.classdump.luna.compiler.ir.UpVar;
|
|
||||||
import org.classdump.luna.compiler.ir.VList;
|
|
||||||
import org.classdump.luna.compiler.ir.Val;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
import org.classdump.luna.compiler.ir.VarInit;
|
|
||||||
import org.classdump.luna.compiler.ir.VarLoad;
|
|
||||||
import org.classdump.luna.compiler.ir.VarStore;
|
|
||||||
import org.classdump.luna.compiler.ir.Vararg;
|
|
||||||
|
|
||||||
abstract class AbstractUseDefVisitor extends IRVisitor {
|
|
||||||
|
|
||||||
protected abstract void def(Val v);
|
|
||||||
|
|
||||||
protected abstract void use(Val v);
|
|
||||||
|
|
||||||
protected abstract void def(PhiVal pv);
|
|
||||||
|
|
||||||
protected abstract void use(PhiVal pv);
|
|
||||||
|
|
||||||
protected abstract void def(MultiVal mv);
|
|
||||||
|
|
||||||
protected abstract void use(MultiVal mv);
|
|
||||||
|
|
||||||
protected abstract void def(Var v);
|
|
||||||
|
|
||||||
protected abstract void use(Var v);
|
|
||||||
|
|
||||||
protected abstract void def(UpVar uv);
|
|
||||||
|
|
||||||
protected abstract void use(UpVar uv);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Nil node) {
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Bool node) {
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Int node) {
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Flt node) {
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Str node) {
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(BinOp node) {
|
|
||||||
use(node.left());
|
|
||||||
use(node.right());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(UnOp node) {
|
|
||||||
use(node.arg());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabNew node) {
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabGet node) {
|
|
||||||
use(node.obj());
|
|
||||||
use(node.key());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabSet node) {
|
|
||||||
use(node.obj());
|
|
||||||
use(node.key());
|
|
||||||
use(node.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabRawSet node) {
|
|
||||||
use(node.obj());
|
|
||||||
use(node.key());
|
|
||||||
use(node.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabRawSetInt node) {
|
|
||||||
use(node.obj());
|
|
||||||
use(node.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabRawAppendMulti node) {
|
|
||||||
use(node.obj());
|
|
||||||
use(node.src());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(VarInit node) {
|
|
||||||
use(node.src());
|
|
||||||
def(node.var());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(VarLoad node) {
|
|
||||||
use(node.var());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(VarStore node) {
|
|
||||||
use(node.src());
|
|
||||||
def(node.var());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(UpLoad node) {
|
|
||||||
use(node.upval());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(UpStore node) {
|
|
||||||
use(node.src());
|
|
||||||
def(node.upval());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Vararg node) {
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void use(VList vlist) {
|
|
||||||
for (Val v : vlist.addrs()) {
|
|
||||||
use(v);
|
|
||||||
}
|
|
||||||
if (vlist.suffix() != null) {
|
|
||||||
use(vlist.suffix());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Ret node) {
|
|
||||||
use(node.args());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TCall node) {
|
|
||||||
use(node.target());
|
|
||||||
use(node.args());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Call node) {
|
|
||||||
use(node.fn());
|
|
||||||
use(node.args());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(MultiGet node) {
|
|
||||||
use(node.src());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(PhiStore node) {
|
|
||||||
use(node.src());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(PhiLoad node) {
|
|
||||||
use(node.src());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Label node) {
|
|
||||||
// no effect
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Jmp node) {
|
|
||||||
// no effect
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Closure node) {
|
|
||||||
for (AbstractVar v : node.args()) {
|
|
||||||
if (v instanceof Var) {
|
|
||||||
use((Var) v);
|
|
||||||
} else if (v instanceof UpVar) {
|
|
||||||
use((UpVar) v);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Illegal abstract var: " + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(ToNumber node) {
|
|
||||||
use(node.src());
|
|
||||||
def(node.dest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(ToNext node) {
|
|
||||||
// no effect
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Branch branch) {
|
|
||||||
branch.condition().accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Branch.Condition.Nil cond) {
|
|
||||||
use(cond.addr());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Branch.Condition.Bool cond) {
|
|
||||||
use(cond.addr());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Branch.Condition.NumLoopEnd cond) {
|
|
||||||
use(cond.var());
|
|
||||||
use(cond.limit());
|
|
||||||
use(cond.step());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import org.classdump.luna.compiler.IRFunc;
|
|
||||||
|
|
||||||
public class DependencyAnalyser {
|
|
||||||
|
|
||||||
public static DependencyInfo analyse(IRFunc fn) {
|
|
||||||
NestedRefVisitor visitor = new NestedRefVisitor();
|
|
||||||
visitor.visit(fn);
|
|
||||||
return visitor.dependencyInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.FunctionId;
|
|
||||||
|
|
||||||
public class DependencyInfo {
|
|
||||||
|
|
||||||
private final Set<FunctionId> nestedRefs;
|
|
||||||
|
|
||||||
public DependencyInfo(Set<FunctionId> nestedRefs) {
|
|
||||||
this.nestedRefs = Objects.requireNonNull(nestedRefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<FunctionId> nestedRefs() {
|
|
||||||
return nestedRefs;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,284 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.IRFunc;
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVal;
|
|
||||||
import org.classdump.luna.compiler.ir.BasicBlock;
|
|
||||||
import org.classdump.luna.compiler.ir.BlockTermNode;
|
|
||||||
import org.classdump.luna.compiler.ir.BodyNode;
|
|
||||||
import org.classdump.luna.compiler.ir.Code;
|
|
||||||
import org.classdump.luna.compiler.ir.IRNode;
|
|
||||||
import org.classdump.luna.compiler.ir.Label;
|
|
||||||
import org.classdump.luna.compiler.ir.MultiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.UpVar;
|
|
||||||
import org.classdump.luna.compiler.ir.Val;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
import org.classdump.luna.compiler.ir.VarStore;
|
|
||||||
import org.classdump.luna.compiler.util.CodeUtils;
|
|
||||||
|
|
||||||
public class LivenessAnalyser {
|
|
||||||
|
|
||||||
private final IRFunc fn;
|
|
||||||
|
|
||||||
private final Map<IRNode, Set<Var>> varLiveIn;
|
|
||||||
private final Map<IRNode, Set<AbstractVal>> valLiveIn;
|
|
||||||
|
|
||||||
private Map<Label, Set<Var>> endVarLiveIn;
|
|
||||||
private Map<Label, Set<AbstractVal>> endValLiveIn;
|
|
||||||
|
|
||||||
private LivenessAnalyser(IRFunc fn) {
|
|
||||||
this.fn = Objects.requireNonNull(fn);
|
|
||||||
|
|
||||||
this.varLiveIn = new HashMap<>();
|
|
||||||
this.valLiveIn = new HashMap<>();
|
|
||||||
|
|
||||||
this.endVarLiveIn = new HashMap<>();
|
|
||||||
this.endValLiveIn = new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LivenessInfo computeLiveness(IRFunc fn) {
|
|
||||||
LivenessAnalyser analyser = new LivenessAnalyser(fn);
|
|
||||||
return analyser.analyse();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void mergeLiveOut(Map<IRNode, LivenessInfo.Entry> entries, IRNode m, IRNode n) {
|
|
||||||
LivenessInfo.Entry e_m = entries.get(m);
|
|
||||||
LivenessInfo.Entry e_n = entries.get(n);
|
|
||||||
e_m.outVar().addAll(e_n.inVar());
|
|
||||||
e_m.outVal().addAll(e_n.inVal());
|
|
||||||
}
|
|
||||||
|
|
||||||
public LivenessInfo analyse() {
|
|
||||||
Code code = fn.code();
|
|
||||||
|
|
||||||
Map<Label, Set<Label>> in = CodeUtils.inLabels(code);
|
|
||||||
|
|
||||||
// initialise
|
|
||||||
{
|
|
||||||
for (Label l : code.labels()) {
|
|
||||||
endVarLiveIn.put(l, new HashSet<Var>());
|
|
||||||
endValLiveIn.put(l, new HashSet<AbstractVal>());
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator<IRNode> ns = CodeUtils.nodeIterator(code);
|
|
||||||
while (ns.hasNext()) {
|
|
||||||
IRNode n = ns.next();
|
|
||||||
varLiveIn.put(n, new HashSet<Var>());
|
|
||||||
valLiveIn.put(n, new HashSet<AbstractVal>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Deque<Label> open = new ArrayDeque<>();
|
|
||||||
|
|
||||||
// make sure we'll visit all labels at least once
|
|
||||||
for (Label l : CodeUtils.labelsBreadthFirst(code)) {
|
|
||||||
open.push(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!open.isEmpty()) {
|
|
||||||
Label l = open.pop();
|
|
||||||
|
|
||||||
LivenessVisitor visitor = new LivenessVisitor(
|
|
||||||
endVarLiveIn.get(l),
|
|
||||||
endValLiveIn.get(l));
|
|
||||||
|
|
||||||
processBlock(visitor, code.block(l));
|
|
||||||
|
|
||||||
for (Label inl : in.get(l)) {
|
|
||||||
boolean changed = false;
|
|
||||||
|
|
||||||
changed |= endVarLiveIn.get(inl).addAll(visitor.currentVarLiveIn());
|
|
||||||
changed |= endValLiveIn.get(inl).addAll(visitor.currentValLiveIn());
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
if (open.contains(inl)) {
|
|
||||||
open.remove(inl);
|
|
||||||
}
|
|
||||||
open.push(inl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result();
|
|
||||||
}
|
|
||||||
|
|
||||||
private LivenessInfo result() {
|
|
||||||
Map<IRNode, LivenessInfo.Entry> entries = new HashMap<>();
|
|
||||||
|
|
||||||
// initialise
|
|
||||||
Iterator<IRNode> nodeIterator = CodeUtils.nodeIterator(fn.code());
|
|
||||||
while (nodeIterator.hasNext()) {
|
|
||||||
IRNode node = nodeIterator.next();
|
|
||||||
|
|
||||||
Set<Var> var_in = varLiveIn.get(node);
|
|
||||||
Set<AbstractVal> val_in = valLiveIn.get(node);
|
|
||||||
|
|
||||||
entries.put(node,
|
|
||||||
new LivenessInfo.Entry(var_in, new HashSet<Var>(), val_in, new HashSet<AbstractVal>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute live-out from live-in
|
|
||||||
Iterator<BasicBlock> blockIterator = fn.code().blockIterator();
|
|
||||||
while (blockIterator.hasNext()) {
|
|
||||||
BasicBlock b = blockIterator.next();
|
|
||||||
|
|
||||||
// body
|
|
||||||
for (int i = 0; i < b.body().size(); i++) {
|
|
||||||
BodyNode m = b.body().get(i);
|
|
||||||
IRNode n = i + 1 < b.body().size() ? b.body().get(i + 1) : b.end();
|
|
||||||
mergeLiveOut(entries, m, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// end
|
|
||||||
BlockTermNode end = b.end();
|
|
||||||
LivenessInfo.Entry e_end = entries.get(end);
|
|
||||||
|
|
||||||
for (Label nxt : end.nextLabels()) {
|
|
||||||
BasicBlock nextBlock = fn.code().block(nxt);
|
|
||||||
IRNode n = !nextBlock.body().isEmpty() ? nextBlock.body().get(0) : nextBlock.end();
|
|
||||||
mergeLiveOut(entries, end, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LivenessInfo(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean processBlock(LivenessVisitor visitor, BasicBlock block) {
|
|
||||||
// iterating backwards
|
|
||||||
boolean changed = processNode(visitor, block.end());
|
|
||||||
|
|
||||||
for (int i = block.body().size() - 1; i >= 0; i--) {
|
|
||||||
changed |= processNode(visitor, block.body().get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean processNode(LivenessVisitor visitor, IRNode node) {
|
|
||||||
Objects.requireNonNull(node);
|
|
||||||
|
|
||||||
final Set<Var> varLive_in = varLiveIn.get(node);
|
|
||||||
final Set<AbstractVal> valLive_in = valLiveIn.get(node);
|
|
||||||
|
|
||||||
node.accept(visitor);
|
|
||||||
|
|
||||||
boolean varSame = visitor.currentVarLiveIn().equals(varLive_in);
|
|
||||||
boolean valSame = visitor.currentValLiveIn().equals(valLive_in);
|
|
||||||
|
|
||||||
if (!varSame) {
|
|
||||||
varLive_in.clear();
|
|
||||||
varLive_in.addAll(visitor.currentVarLiveIn());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valSame) {
|
|
||||||
valLive_in.clear();
|
|
||||||
valLive_in.addAll(visitor.currentValLiveIn());
|
|
||||||
}
|
|
||||||
|
|
||||||
return !varSame || !valSame;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LivenessVisitor extends AbstractUseDefVisitor {
|
|
||||||
|
|
||||||
private Set<Var> currentVarLiveIn;
|
|
||||||
private Set<AbstractVal> currentValLiveIn;
|
|
||||||
|
|
||||||
public LivenessVisitor(Set<Var> currentVarLiveIn, Set<AbstractVal> currentValLiveIn) {
|
|
||||||
this.currentVarLiveIn = new HashSet<>(Objects.requireNonNull(currentVarLiveIn));
|
|
||||||
this.currentValLiveIn = new HashSet<>(Objects.requireNonNull(currentValLiveIn));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Var> currentVarLiveIn() {
|
|
||||||
return currentVarLiveIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<AbstractVal> currentValLiveIn() {
|
|
||||||
return currentValLiveIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(Val v) {
|
|
||||||
currentValLiveIn.remove(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(Val v) {
|
|
||||||
currentValLiveIn.add(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(PhiVal pv) {
|
|
||||||
currentValLiveIn.remove(pv);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(PhiVal pv) {
|
|
||||||
currentValLiveIn.add(pv);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(MultiVal mv) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(MultiVal mv) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(Var v) {
|
|
||||||
currentVarLiveIn.remove(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(Var v) {
|
|
||||||
currentVarLiveIn.add(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(UpVar uv) {
|
|
||||||
// no effect on liveness
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(UpVar uv) {
|
|
||||||
// no effect on liveness
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(VarStore node) {
|
|
||||||
use(node.src());
|
|
||||||
use(node.var()); // Note that this is a use, not a def
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVal;
|
|
||||||
import org.classdump.luna.compiler.ir.IRNode;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
|
|
||||||
public class LivenessInfo {
|
|
||||||
|
|
||||||
private final Map<IRNode, Entry> entries;
|
|
||||||
|
|
||||||
public LivenessInfo(Map<IRNode, Entry> entries) {
|
|
||||||
this.entries = Objects.requireNonNull(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry entry(IRNode node) {
|
|
||||||
Objects.requireNonNull(node);
|
|
||||||
Entry e = entries.get(node);
|
|
||||||
if (e == null) {
|
|
||||||
throw new NoSuchElementException("No liveness information for " + node);
|
|
||||||
} else {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<Var> liveInVars(IRNode node) {
|
|
||||||
return entry(node).inVar();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<Var> liveOutVars(IRNode node) {
|
|
||||||
return entry(node).outVar();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<AbstractVal> liveInVals(IRNode node) {
|
|
||||||
return entry(node).inVal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<AbstractVal> liveOutVals(IRNode node) {
|
|
||||||
return entry(node).outVal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Entry {
|
|
||||||
|
|
||||||
private final Set<Var> var_in;
|
|
||||||
private final Set<Var> var_out;
|
|
||||||
private final Set<AbstractVal> val_in;
|
|
||||||
private final Set<AbstractVal> val_out;
|
|
||||||
|
|
||||||
Entry(Set<Var> var_in, Set<Var> var_out, Set<AbstractVal> val_in, Set<AbstractVal> val_out) {
|
|
||||||
this.var_in = Objects.requireNonNull(var_in);
|
|
||||||
this.var_out = Objects.requireNonNull(var_out);
|
|
||||||
this.val_in = Objects.requireNonNull(val_in);
|
|
||||||
this.val_out = Objects.requireNonNull(val_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry immutableCopy() {
|
|
||||||
return new Entry(
|
|
||||||
Collections.unmodifiableSet(new HashSet<>(var_in)),
|
|
||||||
Collections.unmodifiableSet(new HashSet<>(var_out)),
|
|
||||||
Collections.unmodifiableSet(new HashSet<>(val_in)),
|
|
||||||
Collections.unmodifiableSet(new HashSet<>(val_out)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Var> inVar() {
|
|
||||||
return var_in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Var> outVar() {
|
|
||||||
return var_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<AbstractVal> inVal() {
|
|
||||||
return val_in;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<AbstractVal> outVal() {
|
|
||||||
return val_out;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.FunctionId;
|
|
||||||
import org.classdump.luna.compiler.ir.Closure;
|
|
||||||
import org.classdump.luna.compiler.ir.CodeVisitor;
|
|
||||||
|
|
||||||
class NestedRefVisitor extends CodeVisitor {
|
|
||||||
|
|
||||||
private final Set<FunctionId> ids;
|
|
||||||
|
|
||||||
public NestedRefVisitor() {
|
|
||||||
this.ids = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DependencyInfo dependencyInfo() {
|
|
||||||
return new DependencyInfo(Collections.unmodifiableSet(new HashSet<>(ids)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Closure node) {
|
|
||||||
ids.add(node.id());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import org.classdump.luna.compiler.analysis.types.LuaTypes;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.Type;
|
|
||||||
|
|
||||||
public enum NumericOperationType {
|
|
||||||
|
|
||||||
Integer,
|
|
||||||
Float,
|
|
||||||
Number,
|
|
||||||
Any;
|
|
||||||
|
|
||||||
public Type toType() {
|
|
||||||
switch (this) {
|
|
||||||
case Integer:
|
|
||||||
return LuaTypes.NUMBER_INTEGER;
|
|
||||||
case Float:
|
|
||||||
return LuaTypes.NUMBER_FLOAT;
|
|
||||||
case Number:
|
|
||||||
return LuaTypes.NUMBER;
|
|
||||||
case Any:
|
|
||||||
default:
|
|
||||||
return LuaTypes.ANY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVal;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
|
|
||||||
public class SlotAllocInfo {
|
|
||||||
|
|
||||||
private final Map<AbstractVal, Integer> valSlots;
|
|
||||||
private final Map<Var, Integer> varSlots;
|
|
||||||
private final int numSlots;
|
|
||||||
|
|
||||||
public SlotAllocInfo(Map<AbstractVal, Integer> valSlots, Map<Var, Integer> varSlots) {
|
|
||||||
this.valSlots = Objects.requireNonNull(valSlots);
|
|
||||||
this.varSlots = Objects.requireNonNull(varSlots);
|
|
||||||
|
|
||||||
int n = 0;
|
|
||||||
for (Integer i : varSlots.values()) {
|
|
||||||
n = Math.max(n, i);
|
|
||||||
}
|
|
||||||
for (Integer i : valSlots.values()) {
|
|
||||||
n = Math.max(n, i);
|
|
||||||
}
|
|
||||||
this.numSlots = n + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int slotOf(AbstractVal v) {
|
|
||||||
Integer idx = valSlots.get(Objects.requireNonNull(v));
|
|
||||||
if (idx != null) {
|
|
||||||
return idx;
|
|
||||||
} else {
|
|
||||||
throw new NoSuchElementException("Undefined slot for value: " + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int slotOf(Var v) {
|
|
||||||
Integer idx = varSlots.get(Objects.requireNonNull(v));
|
|
||||||
if (idx != null) {
|
|
||||||
return idx;
|
|
||||||
} else {
|
|
||||||
throw new NoSuchElementException("Undefined slot for variable: " + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int numSlots() {
|
|
||||||
return numSlots;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,274 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.BitSet;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.IRFunc;
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVal;
|
|
||||||
import org.classdump.luna.compiler.ir.BasicBlock;
|
|
||||||
import org.classdump.luna.compiler.ir.BodyNode;
|
|
||||||
import org.classdump.luna.compiler.ir.IRNode;
|
|
||||||
import org.classdump.luna.compiler.ir.Label;
|
|
||||||
import org.classdump.luna.compiler.ir.MultiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.UpVar;
|
|
||||||
import org.classdump.luna.compiler.ir.Val;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
|
|
||||||
public class SlotAllocator {
|
|
||||||
|
|
||||||
private final IRFunc fn;
|
|
||||||
|
|
||||||
private final Map<AbstractVal, Integer> valSlots;
|
|
||||||
private final Map<Var, Integer> varSlots;
|
|
||||||
|
|
||||||
private IRNode currentNode;
|
|
||||||
|
|
||||||
public SlotAllocator(IRFunc fn) {
|
|
||||||
this.fn = Objects.requireNonNull(fn);
|
|
||||||
this.valSlots = new HashMap<>();
|
|
||||||
this.varSlots = new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SlotAllocInfo allocateSlots(IRFunc fn) {
|
|
||||||
SlotAllocator allocator = new SlotAllocator(fn);
|
|
||||||
return allocator.process();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IRNode node() {
|
|
||||||
if (currentNode == null) {
|
|
||||||
throw new IllegalStateException("Current node is null");
|
|
||||||
}
|
|
||||||
return currentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasSlot(Var v) {
|
|
||||||
return varSlots.get(Objects.requireNonNull(v)) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasSlot(AbstractVal v) {
|
|
||||||
return valSlots.get(Objects.requireNonNull(v)) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int slotOf(Var v) {
|
|
||||||
Objects.requireNonNull(v);
|
|
||||||
Integer idx = varSlots.get(v);
|
|
||||||
if (idx == null) {
|
|
||||||
throw new NoSuchElementException("Slot not defined for variable " + v);
|
|
||||||
} else {
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int slotOf(AbstractVal v) {
|
|
||||||
Objects.requireNonNull(v);
|
|
||||||
Integer idx = valSlots.get(v);
|
|
||||||
if (idx == null) {
|
|
||||||
throw new NoSuchElementException("Slot not defined for value " + v);
|
|
||||||
} else {
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BitSet occupiedSlots(LivenessInfo liveness, IRNode node) {
|
|
||||||
BitSet occupied = new BitSet();
|
|
||||||
|
|
||||||
LivenessInfo.Entry e = liveness.entry(node);
|
|
||||||
|
|
||||||
for (Var v : e.inVar()) {
|
|
||||||
int idx = slotOf(v);
|
|
||||||
if (occupied.get(idx)) {
|
|
||||||
throw new IllegalStateException("Slot " + idx + " already occupied");
|
|
||||||
}
|
|
||||||
if (e.outVar().contains(v)) {
|
|
||||||
occupied.set(slotOf(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (AbstractVal v : e.inVal()) {
|
|
||||||
int idx = slotOf(v);
|
|
||||||
if (occupied.get(idx)) {
|
|
||||||
throw new IllegalStateException("Slot " + idx + " already occupied");
|
|
||||||
}
|
|
||||||
if (e.outVal().contains(v)) {
|
|
||||||
occupied.set(slotOf(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return occupied;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int findFreeSlot(LivenessInfo liveness, IRNode node) {
|
|
||||||
BitSet occupied = occupiedSlots(liveness, node);
|
|
||||||
|
|
||||||
int idx = 0;
|
|
||||||
while (occupied.get(idx)) {
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert (!occupied.get(idx));
|
|
||||||
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assignParamSlots(List<Var> params) {
|
|
||||||
int idx = 0;
|
|
||||||
for (Var v : params) {
|
|
||||||
varSlots.put(v, idx++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assignSlot(Var v, LivenessInfo liveness, IRNode node) {
|
|
||||||
if (hasSlot(v)) {
|
|
||||||
throw new IllegalStateException("Slot already assigned for variable " + v);
|
|
||||||
}
|
|
||||||
varSlots.put(v, findFreeSlot(liveness, node));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assignSlot(AbstractVal v, LivenessInfo liveness, IRNode node) {
|
|
||||||
if (hasSlot(v)) {
|
|
||||||
throw new IllegalStateException("Slot already assigned for value " + v);
|
|
||||||
}
|
|
||||||
valSlots.put(v, findFreeSlot(liveness, node));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SlotAllocInfo process() {
|
|
||||||
LivenessInfo liveness = LivenessAnalyser.computeLiveness(fn);
|
|
||||||
|
|
||||||
Set<Label> visited = new HashSet<>();
|
|
||||||
Deque<Label> open = new ArrayDeque<>();
|
|
||||||
open.push(fn.code().entryLabel());
|
|
||||||
|
|
||||||
AllocatorVisitor visitor = new AllocatorVisitor(liveness);
|
|
||||||
|
|
||||||
assignParamSlots(fn.params());
|
|
||||||
|
|
||||||
while (!open.isEmpty()) {
|
|
||||||
Label l = open.pop();
|
|
||||||
if (visited.add(l)) {
|
|
||||||
BasicBlock b = fn.code().block(l);
|
|
||||||
|
|
||||||
assignSlots(b, visitor);
|
|
||||||
|
|
||||||
for (Label n : b.end().nextLabels()) {
|
|
||||||
open.push(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SlotAllocInfo(
|
|
||||||
Collections.unmodifiableMap(valSlots),
|
|
||||||
Collections.unmodifiableMap(varSlots));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assignSlots(BasicBlock b, AllocatorVisitor visitor) {
|
|
||||||
for (BodyNode n : b.body()) {
|
|
||||||
currentNode = n; // FIXME
|
|
||||||
n.accept(visitor);
|
|
||||||
currentNode = null;
|
|
||||||
}
|
|
||||||
currentNode = b.end(); // FIXME
|
|
||||||
b.end().accept(visitor);
|
|
||||||
currentNode = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AllocatorVisitor extends AbstractUseDefVisitor {
|
|
||||||
|
|
||||||
private final LivenessInfo liveness;
|
|
||||||
|
|
||||||
AllocatorVisitor(LivenessInfo liveness) {
|
|
||||||
this.liveness = Objects.requireNonNull(liveness);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(Val v) {
|
|
||||||
if (hasSlot(v)) {
|
|
||||||
throw new IllegalStateException("Value " + v + " already assigned to a slot");
|
|
||||||
}
|
|
||||||
assignSlot(v, liveness, node());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(Val v) {
|
|
||||||
if (!hasSlot(v)) {
|
|
||||||
throw new IllegalStateException("Value " + v + " not assigned to a slot");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(PhiVal pv) {
|
|
||||||
if (hasSlot(pv)) {
|
|
||||||
// ok: from another branch
|
|
||||||
} else {
|
|
||||||
assignSlot(pv, liveness, node());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(PhiVal pv) {
|
|
||||||
if (!hasSlot(pv)) {
|
|
||||||
throw new IllegalStateException("Value " + pv + " not assigned to a slot");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(MultiVal mv) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(MultiVal mv) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(Var v) {
|
|
||||||
if (!hasSlot(v)) {
|
|
||||||
assignSlot(v, liveness, node());
|
|
||||||
} else {
|
|
||||||
// ok: just use it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(Var v) {
|
|
||||||
if (!hasSlot(v)) {
|
|
||||||
throw new IllegalStateException("No slot assigned to variable " + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void def(UpVar uv) {
|
|
||||||
// no effect on slots
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void use(UpVar uv) {
|
|
||||||
// no effect on slots
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import static org.classdump.luna.compiler.analysis.types.LuaTypes.NUMBER;
|
|
||||||
import static org.classdump.luna.compiler.analysis.types.LuaTypes.NUMBER_FLOAT;
|
|
||||||
import static org.classdump.luna.compiler.analysis.types.LuaTypes.NUMBER_INTEGER;
|
|
||||||
|
|
||||||
import org.classdump.luna.compiler.analysis.types.Type;
|
|
||||||
|
|
||||||
public abstract class StaticMathImplementation {
|
|
||||||
|
|
||||||
public static StaticMathImplementation MAY_BE_INTEGER = new MayBeInteger();
|
|
||||||
|
|
||||||
public static StaticMathImplementation MUST_BE_FLOAT = new MustBeFloat();
|
|
||||||
|
|
||||||
public static StaticMathImplementation MUST_BE_INTEGER = new MustBeInteger();
|
|
||||||
|
|
||||||
public abstract NumericOperationType opType(Type left, Type right);
|
|
||||||
|
|
||||||
public abstract NumericOperationType opType(Type arg);
|
|
||||||
|
|
||||||
public static class MayBeInteger extends StaticMathImplementation {
|
|
||||||
|
|
||||||
private MayBeInteger() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NumericOperationType opType(Type l, Type r) {
|
|
||||||
if (l.isSubtypeOf(NUMBER) && r.isSubtypeOf(NUMBER)) {
|
|
||||||
if (l.isSubtypeOf(NUMBER_INTEGER) && r.isSubtypeOf(NUMBER_INTEGER)) {
|
|
||||||
return NumericOperationType.Integer;
|
|
||||||
} else if (l.isSubtypeOf(NUMBER_FLOAT) || r.isSubtypeOf(NUMBER_FLOAT)) {
|
|
||||||
return NumericOperationType.Float;
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Number;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NumericOperationType opType(Type arg) {
|
|
||||||
if (arg.isSubtypeOf(NUMBER)) {
|
|
||||||
if (arg.isSubtypeOf(NUMBER_INTEGER)) {
|
|
||||||
return NumericOperationType.Integer;
|
|
||||||
} else if (arg.isSubtypeOf(NUMBER_FLOAT)) {
|
|
||||||
return NumericOperationType.Float;
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Number;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MustBeFloat extends StaticMathImplementation {
|
|
||||||
|
|
||||||
private MustBeFloat() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NumericOperationType opType(Type l, Type r) {
|
|
||||||
if (l.isSubtypeOf(NUMBER) && r.isSubtypeOf(NUMBER)) {
|
|
||||||
return NumericOperationType.Float;
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NumericOperationType opType(Type arg) {
|
|
||||||
if (arg.isSubtypeOf(NUMBER)) {
|
|
||||||
return NumericOperationType.Float;
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MustBeInteger extends StaticMathImplementation {
|
|
||||||
|
|
||||||
private MustBeInteger() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NumericOperationType opType(Type l, Type r) {
|
|
||||||
if (l.isSubtypeOf(NUMBER) && r.isSubtypeOf(NUMBER)) {
|
|
||||||
return NumericOperationType.Integer;
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public NumericOperationType opType(Type arg) {
|
|
||||||
if (arg.isSubtypeOf(NUMBER)) {
|
|
||||||
return NumericOperationType.Integer;
|
|
||||||
} else {
|
|
||||||
return NumericOperationType.Any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.Type;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.TypeSeq;
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVal;
|
|
||||||
import org.classdump.luna.compiler.ir.MultiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.Val;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
|
|
||||||
public class TypeInfo {
|
|
||||||
|
|
||||||
private final Map<AbstractVal, Type> types;
|
|
||||||
private final Map<MultiVal, TypeSeq> multiTypes;
|
|
||||||
private final Map<Var, Boolean> vars;
|
|
||||||
private final TypeSeq returnType;
|
|
||||||
|
|
||||||
protected TypeInfo(
|
|
||||||
Map<AbstractVal, Type> types,
|
|
||||||
Map<MultiVal, TypeSeq> multiTypes,
|
|
||||||
Map<Var, Boolean> vars,
|
|
||||||
TypeSeq returnType) {
|
|
||||||
|
|
||||||
this.types = Objects.requireNonNull(types);
|
|
||||||
this.multiTypes = Objects.requireNonNull(multiTypes);
|
|
||||||
this.vars = Objects.requireNonNull(vars);
|
|
||||||
this.returnType = Objects.requireNonNull(returnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TypeInfo of(
|
|
||||||
Map<Val, Type> valTypes,
|
|
||||||
Map<PhiVal, Type> phiValTypes,
|
|
||||||
Map<MultiVal, TypeSeq> multiValTypes,
|
|
||||||
Set<Var> vars, Set<Var> reifiedVars,
|
|
||||||
TypeSeq returnType) {
|
|
||||||
|
|
||||||
Map<AbstractVal, Type> types = new HashMap<>();
|
|
||||||
Map<MultiVal, TypeSeq> multiTypes = new HashMap<>();
|
|
||||||
Map<Var, Boolean> vs = new HashMap<>();
|
|
||||||
|
|
||||||
for (Map.Entry<Val, Type> e : valTypes.entrySet()) {
|
|
||||||
types.put(e.getKey(), e.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<PhiVal, Type> e : phiValTypes.entrySet()) {
|
|
||||||
types.put(e.getKey(), e.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<MultiVal, TypeSeq> e : multiValTypes.entrySet()) {
|
|
||||||
multiTypes.put(e.getKey(), e.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Var v : vars) {
|
|
||||||
vs.put(v, reifiedVars.contains(v));
|
|
||||||
}
|
|
||||||
for (Var v : reifiedVars) {
|
|
||||||
if (!vs.containsKey(v)) {
|
|
||||||
throw new IllegalStateException("Reified variable " + v + " not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TypeInfo(types, multiTypes, vs, returnType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<AbstractVal> vals() {
|
|
||||||
return types.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<MultiVal> multiVals() {
|
|
||||||
return multiTypes.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type typeOf(AbstractVal v) {
|
|
||||||
Objects.requireNonNull(v);
|
|
||||||
|
|
||||||
Type t = types.get(v);
|
|
||||||
if (t == null) {
|
|
||||||
throw new NoSuchElementException("No type information for " + v);
|
|
||||||
} else {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq typeOf(MultiVal mv) {
|
|
||||||
Objects.requireNonNull(mv);
|
|
||||||
|
|
||||||
TypeSeq tseq = multiTypes.get(mv);
|
|
||||||
if (tseq == null) {
|
|
||||||
throw new NoSuchElementException("No type information for multi-value " + mv);
|
|
||||||
} else {
|
|
||||||
return tseq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterable<Var> vars() {
|
|
||||||
return vars.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isReified(Var v) {
|
|
||||||
Objects.requireNonNull(v);
|
|
||||||
|
|
||||||
Boolean r = vars.get(v);
|
|
||||||
if (r != null) {
|
|
||||||
return r.booleanValue();
|
|
||||||
} else {
|
|
||||||
throw new NoSuchElementException("Variable not found: " + v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq returnType() {
|
|
||||||
return returnType;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,233 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import org.classdump.luna.ByteString;
|
|
||||||
import org.classdump.luna.Conversions;
|
|
||||||
import org.classdump.luna.LuaMathOperators;
|
|
||||||
import org.classdump.luna.Ordering;
|
|
||||||
import org.classdump.luna.compiler.IRFunc;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.LiteralType;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.LuaTypes;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.Type;
|
|
||||||
import org.classdump.luna.compiler.ir.BinOp;
|
|
||||||
import org.classdump.luna.compiler.ir.UnOp;
|
|
||||||
import org.classdump.luna.runtime.Dispatch;
|
|
||||||
|
|
||||||
public class Typer {
|
|
||||||
|
|
||||||
private static Object literalValue(Type t) {
|
|
||||||
return t instanceof LiteralType ? ((LiteralType<?>) t).value() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LiteralType<?> objectToLiteralType(Object o) {
|
|
||||||
if (o instanceof Number) {
|
|
||||||
Number n = (Number) o;
|
|
||||||
if (n instanceof Double || n instanceof Float) {
|
|
||||||
return LuaTypes.NUMBER_FLOAT.newLiteralType(n.doubleValue());
|
|
||||||
} else {
|
|
||||||
return LuaTypes.NUMBER_INTEGER.newLiteralType(n.longValue());
|
|
||||||
}
|
|
||||||
} else if (o instanceof ByteString) {
|
|
||||||
return LuaTypes.STRING.newLiteralType((ByteString) o);
|
|
||||||
} else if (o instanceof Boolean) {
|
|
||||||
return LuaTypes.BOOLEAN.newLiteralType((Boolean) o);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object tryEmulateArithmeticOperation(BinOp.Op op, Object l, Object r) {
|
|
||||||
Number nl = Conversions.arithmeticValueOf(l);
|
|
||||||
Number nr = Conversions.arithmeticValueOf(r);
|
|
||||||
|
|
||||||
if (nl == null || nr == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (op) {
|
|
||||||
case ADD:
|
|
||||||
return Dispatch.add(nl, nr);
|
|
||||||
case SUB:
|
|
||||||
return Dispatch.sub(nl, nr);
|
|
||||||
case MUL:
|
|
||||||
return Dispatch.mul(nl, nr);
|
|
||||||
case DIV:
|
|
||||||
return Dispatch.div(nl, nr);
|
|
||||||
case MOD:
|
|
||||||
return Dispatch.mod(nl, nr);
|
|
||||||
case IDIV:
|
|
||||||
return Dispatch.idiv(nl, nr);
|
|
||||||
case POW:
|
|
||||||
return Dispatch.pow(nl, nr);
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Illegal operation: " + op);
|
|
||||||
}
|
|
||||||
} catch (ArithmeticException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object tryEmulateBitwiseOperation(BinOp.Op op, Object l, Object r) {
|
|
||||||
Long il = Conversions.integerValueOf(l);
|
|
||||||
Long ir = Conversions.integerValueOf(r);
|
|
||||||
|
|
||||||
if (il == null || ir == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (op) {
|
|
||||||
case BAND:
|
|
||||||
return LuaMathOperators.band(il, ir);
|
|
||||||
case BOR:
|
|
||||||
return LuaMathOperators.bor(il, ir);
|
|
||||||
case BXOR:
|
|
||||||
return LuaMathOperators.bxor(il, ir);
|
|
||||||
case SHL:
|
|
||||||
return LuaMathOperators.shl(il, ir);
|
|
||||||
case SHR:
|
|
||||||
return LuaMathOperators.shr(il, ir);
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Illegal operation: " + op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ByteString tryEmulateConcatOperation(Object l, Object r) {
|
|
||||||
ByteString sl = Conversions.stringValueOf(l);
|
|
||||||
ByteString sr = Conversions.stringValueOf(r);
|
|
||||||
|
|
||||||
if (sl == null || sr == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sl.concat(sr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object tryEmulateComparisonOperation(BinOp.Op op, Object l, Object r) {
|
|
||||||
if (l == null || r == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ordering<Object> c = Ordering.of(l, r);
|
|
||||||
|
|
||||||
if (c == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (op) {
|
|
||||||
case EQ:
|
|
||||||
return c.eq(l, r);
|
|
||||||
case NEQ:
|
|
||||||
return !c.eq(l, r);
|
|
||||||
case LT:
|
|
||||||
return c.lt(l, r);
|
|
||||||
case LE:
|
|
||||||
return c.le(l, r);
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Illegal operation: " + op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object tryEmulateOperation(BinOp.Op op, Object l, Object r) {
|
|
||||||
if (l == null || r == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (op) {
|
|
||||||
|
|
||||||
case ADD:
|
|
||||||
case SUB:
|
|
||||||
case MUL:
|
|
||||||
case DIV:
|
|
||||||
case MOD:
|
|
||||||
case IDIV:
|
|
||||||
case POW:
|
|
||||||
return tryEmulateArithmeticOperation(op, l, r);
|
|
||||||
|
|
||||||
case BAND:
|
|
||||||
case BOR:
|
|
||||||
case BXOR:
|
|
||||||
case SHL:
|
|
||||||
case SHR:
|
|
||||||
return tryEmulateBitwiseOperation(op, l, r);
|
|
||||||
|
|
||||||
case CONCAT:
|
|
||||||
return tryEmulateConcatOperation(l, r);
|
|
||||||
|
|
||||||
case EQ:
|
|
||||||
case NEQ:
|
|
||||||
case LT:
|
|
||||||
case LE:
|
|
||||||
return tryEmulateComparisonOperation(op, l, r);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Illegal operation: " + op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object tryEmulateOperation(UnOp.Op op, Object arg) {
|
|
||||||
if (arg == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (op) {
|
|
||||||
case UNM: {
|
|
||||||
Number n = Conversions.arithmeticValueOf(arg);
|
|
||||||
return n != null ? Dispatch.unm(n) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
case BNOT: {
|
|
||||||
Long l = Conversions.integerValueOf(arg);
|
|
||||||
return l != null ? LuaMathOperators.bnot(l) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
case NOT:
|
|
||||||
return arg.equals(Boolean.FALSE);
|
|
||||||
|
|
||||||
case LEN:
|
|
||||||
if (arg instanceof String) {
|
|
||||||
return Dispatch.len((String) arg);
|
|
||||||
} else if (arg instanceof ByteString) {
|
|
||||||
return Long.valueOf(((ByteString) arg).length());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Illegal operation: " + op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static LiteralType<?> emulateOp(BinOp.Op op, Type l, Type r) {
|
|
||||||
Object result = tryEmulateOperation(op, literalValue(l), literalValue(r));
|
|
||||||
return result != null ? objectToLiteralType(result) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static LiteralType<?> emulateOp(UnOp.Op op, Type t) {
|
|
||||||
Object result = tryEmulateOperation(op, literalValue(t));
|
|
||||||
return result != null ? objectToLiteralType(result) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static TypeInfo analyseTypes(IRFunc fn) {
|
|
||||||
TyperVisitor visitor = new TyperVisitor();
|
|
||||||
visitor.visit(fn);
|
|
||||||
return visitor.valTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,627 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
||||||
|
|
||||||
import static org.classdump.luna.compiler.analysis.StaticMathImplementation.MAY_BE_INTEGER;
|
|
||||||
import static org.classdump.luna.compiler.analysis.StaticMathImplementation.MUST_BE_FLOAT;
|
|
||||||
import static org.classdump.luna.compiler.analysis.StaticMathImplementation.MUST_BE_INTEGER;
|
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.classdump.luna.compiler.IRFunc;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.FunctionType;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.LuaTypes;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.ReturnType;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.Type;
|
|
||||||
import org.classdump.luna.compiler.analysis.types.TypeSeq;
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVar;
|
|
||||||
import org.classdump.luna.compiler.ir.BasicBlock;
|
|
||||||
import org.classdump.luna.compiler.ir.BinOp;
|
|
||||||
import org.classdump.luna.compiler.ir.Call;
|
|
||||||
import org.classdump.luna.compiler.ir.Closure;
|
|
||||||
import org.classdump.luna.compiler.ir.Code;
|
|
||||||
import org.classdump.luna.compiler.ir.CodeVisitor;
|
|
||||||
import org.classdump.luna.compiler.ir.Label;
|
|
||||||
import org.classdump.luna.compiler.ir.LoadConst;
|
|
||||||
import org.classdump.luna.compiler.ir.MultiGet;
|
|
||||||
import org.classdump.luna.compiler.ir.MultiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiLoad;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiStore;
|
|
||||||
import org.classdump.luna.compiler.ir.PhiVal;
|
|
||||||
import org.classdump.luna.compiler.ir.Ret;
|
|
||||||
import org.classdump.luna.compiler.ir.TCall;
|
|
||||||
import org.classdump.luna.compiler.ir.TabGet;
|
|
||||||
import org.classdump.luna.compiler.ir.TabNew;
|
|
||||||
import org.classdump.luna.compiler.ir.TabRawAppendMulti;
|
|
||||||
import org.classdump.luna.compiler.ir.TabSet;
|
|
||||||
import org.classdump.luna.compiler.ir.ToNumber;
|
|
||||||
import org.classdump.luna.compiler.ir.UnOp;
|
|
||||||
import org.classdump.luna.compiler.ir.UpLoad;
|
|
||||||
import org.classdump.luna.compiler.ir.UpStore;
|
|
||||||
import org.classdump.luna.compiler.ir.VList;
|
|
||||||
import org.classdump.luna.compiler.ir.Val;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
import org.classdump.luna.compiler.ir.VarInit;
|
|
||||||
import org.classdump.luna.compiler.ir.VarLoad;
|
|
||||||
import org.classdump.luna.compiler.ir.VarStore;
|
|
||||||
import org.classdump.luna.compiler.ir.Vararg;
|
|
||||||
|
|
||||||
class TyperVisitor extends CodeVisitor {
|
|
||||||
|
|
||||||
private final Map<Val, Type> valTypes;
|
|
||||||
private final Map<PhiVal, Type> phiValTypes;
|
|
||||||
private final Map<MultiVal, TypeSeq> multiValTypes;
|
|
||||||
private final Map<Label, VarState> varStates;
|
|
||||||
|
|
||||||
private final Set<Var> allVars;
|
|
||||||
private final Set<Var> reifiedVars;
|
|
||||||
|
|
||||||
private final Set<Label> seen;
|
|
||||||
private final Queue<Label> open;
|
|
||||||
|
|
||||||
private final Set<ReturnType> returnTypes;
|
|
||||||
|
|
||||||
private boolean changed;
|
|
||||||
private VarState currentVarState;
|
|
||||||
|
|
||||||
public TyperVisitor() {
|
|
||||||
this.valTypes = new HashMap<>();
|
|
||||||
this.phiValTypes = new HashMap<>();
|
|
||||||
this.multiValTypes = new HashMap<>();
|
|
||||||
this.varStates = new HashMap<>();
|
|
||||||
|
|
||||||
this.allVars = new HashSet<>();
|
|
||||||
this.reifiedVars = new HashSet<>();
|
|
||||||
|
|
||||||
this.seen = new HashSet<>();
|
|
||||||
this.open = new ArrayDeque<>();
|
|
||||||
|
|
||||||
this.returnTypes = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TypeSeq returnTypeToTypeSeq(ReturnType rt) {
|
|
||||||
if (rt instanceof ReturnType.ConcreteReturnType) {
|
|
||||||
return ((ReturnType.ConcreteReturnType) rt).typeSeq;
|
|
||||||
} else if (rt instanceof ReturnType.TailCallReturnType) {
|
|
||||||
Type targetType = ((ReturnType.TailCallReturnType) rt).target;
|
|
||||||
if (targetType instanceof FunctionType) {
|
|
||||||
FunctionType ft = (FunctionType) targetType;
|
|
||||||
return ft.returnTypes();
|
|
||||||
} else {
|
|
||||||
return TypeSeq.vararg();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Illegal return type: " + rt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Type joinTypes(Type a, Type b) {
|
|
||||||
return a == null ? b : (b == null ? a : a.join(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static StaticMathImplementation staticMath(BinOp.Op op) {
|
|
||||||
switch (op) {
|
|
||||||
case ADD:
|
|
||||||
return MAY_BE_INTEGER;
|
|
||||||
case SUB:
|
|
||||||
return MAY_BE_INTEGER;
|
|
||||||
case MUL:
|
|
||||||
return MAY_BE_INTEGER;
|
|
||||||
case MOD:
|
|
||||||
return MAY_BE_INTEGER;
|
|
||||||
case POW:
|
|
||||||
return MUST_BE_FLOAT;
|
|
||||||
case DIV:
|
|
||||||
return MUST_BE_FLOAT;
|
|
||||||
case IDIV:
|
|
||||||
return MAY_BE_INTEGER;
|
|
||||||
case BAND:
|
|
||||||
return MUST_BE_INTEGER;
|
|
||||||
case BOR:
|
|
||||||
return MUST_BE_INTEGER;
|
|
||||||
case BXOR:
|
|
||||||
return MUST_BE_INTEGER;
|
|
||||||
case SHL:
|
|
||||||
return MUST_BE_INTEGER;
|
|
||||||
case SHR:
|
|
||||||
return MUST_BE_INTEGER;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean stringable(Type t) {
|
|
||||||
return t.isSubtypeOf(LuaTypes.STRING) || t.isSubtypeOf(LuaTypes.NUMBER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeInfo valTypes() {
|
|
||||||
return TypeInfo.of(valTypes, phiValTypes, multiValTypes, allVars, reifiedVars, returnType());
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeSeq returnType() {
|
|
||||||
TypeSeq ret = null;
|
|
||||||
|
|
||||||
for (ReturnType rt : returnTypes) {
|
|
||||||
TypeSeq ts = returnTypeToTypeSeq(rt);
|
|
||||||
ret = ret != null ? ret.join(ts) : ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret != null ? ret : TypeSeq.vararg();
|
|
||||||
}
|
|
||||||
|
|
||||||
private VarState currentVarState() {
|
|
||||||
return currentVarState;
|
|
||||||
}
|
|
||||||
|
|
||||||
private VarState varState(Label l) {
|
|
||||||
VarState vs = varStates.get(l);
|
|
||||||
if (vs == null) {
|
|
||||||
VarState nvs = new VarState();
|
|
||||||
varStates.put(l, nvs);
|
|
||||||
return nvs;
|
|
||||||
} else {
|
|
||||||
return vs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void useStack() {
|
|
||||||
// TODO: clear stack state
|
|
||||||
}
|
|
||||||
|
|
||||||
private void impure() {
|
|
||||||
// TODO: clear upvalue states
|
|
||||||
|
|
||||||
// clear var state of all escaping local variables
|
|
||||||
// TODO: could be restricted for variables that escape but are read-only
|
|
||||||
currentVarState.clearReifiedVars();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mayCallMetamethod() {
|
|
||||||
useStack();
|
|
||||||
impure();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assign(Val v, Type t) {
|
|
||||||
Objects.requireNonNull(v);
|
|
||||||
Objects.requireNonNull(t);
|
|
||||||
|
|
||||||
Type ot = valTypes.put(v, t);
|
|
||||||
if (t.equals(ot)) {
|
|
||||||
// no change
|
|
||||||
} else if (ot == null) {
|
|
||||||
// initial assign
|
|
||||||
changed = true;
|
|
||||||
} else {
|
|
||||||
changed = true;
|
|
||||||
// throw new IllegalStateException(v + " assigned to more than once");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assign(PhiVal pv, Type t) {
|
|
||||||
Objects.requireNonNull(pv);
|
|
||||||
Objects.requireNonNull(t);
|
|
||||||
|
|
||||||
Type ot = phiValTypes.get(pv);
|
|
||||||
if (ot != null) {
|
|
||||||
Type nt = ot.join(t);
|
|
||||||
if (!ot.equals(nt)) {
|
|
||||||
phiValTypes.put(pv, nt);
|
|
||||||
changed = true;
|
|
||||||
} else {
|
|
||||||
// no change
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// initial assign
|
|
||||||
phiValTypes.put(pv, t);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assign(MultiVal mv, TypeSeq ts) {
|
|
||||||
Objects.requireNonNull(mv);
|
|
||||||
Objects.requireNonNull(ts);
|
|
||||||
|
|
||||||
TypeSeq ots = multiValTypes.put(mv, ts);
|
|
||||||
if (ts.equals(ots)) {
|
|
||||||
// no change
|
|
||||||
} else if (ots == null) {
|
|
||||||
// initial assign
|
|
||||||
changed = true;
|
|
||||||
} else {
|
|
||||||
changed = true;
|
|
||||||
// throw new IllegalStateException(mv + " assigned to more than once");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type typeOf(Val v) {
|
|
||||||
Type t = valTypes.get(v);
|
|
||||||
if (t == null) {
|
|
||||||
throw new IllegalStateException(v + " not assigned to yet");
|
|
||||||
} else {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type typeOf(PhiVal pv) {
|
|
||||||
Type t = phiValTypes.get(pv);
|
|
||||||
if (t == null) {
|
|
||||||
throw new IllegalStateException(pv + " not assigned to yet");
|
|
||||||
} else {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeSeq typeOf(MultiVal mv) {
|
|
||||||
TypeSeq tseq = multiValTypes.get(mv);
|
|
||||||
if (tseq == null) {
|
|
||||||
throw new IllegalStateException(mv + " not assigned to yet");
|
|
||||||
} else {
|
|
||||||
return tseq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(IRFunc func) {
|
|
||||||
Code code = func.code();
|
|
||||||
|
|
||||||
VarState vs = varState(code.entryLabel());
|
|
||||||
for (Var p : func.params()) {
|
|
||||||
vs.store(p, LuaTypes.DYNAMIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Code code) {
|
|
||||||
open.add(code.entryLabel());
|
|
||||||
|
|
||||||
while (!open.isEmpty()) {
|
|
||||||
visit(code.block(open.poll()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(BasicBlock block) {
|
|
||||||
boolean firstTimeVisit = seen.add(block.label());
|
|
||||||
|
|
||||||
currentVarState = varState(block.label()).copy();
|
|
||||||
changed = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
super.visit(block);
|
|
||||||
|
|
||||||
for (Label nxt : block.end().nextLabels()) {
|
|
||||||
VarState vs = varState(nxt);
|
|
||||||
if (vs.joinWith(currentVarState)) {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstTimeVisit || changed) {
|
|
||||||
for (Label nxt : block.end().nextLabels()) {
|
|
||||||
open.add(nxt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
changed = false;
|
|
||||||
currentVarState = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Nil node) {
|
|
||||||
assign(node.dest(), LuaTypes.NIL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Bool node) {
|
|
||||||
assign(node.dest(), LuaTypes.BOOLEAN.newLiteralType(node.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Int node) {
|
|
||||||
assign(node.dest(), LuaTypes.NUMBER_INTEGER.newLiteralType(node.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Flt node) {
|
|
||||||
assign(node.dest(), LuaTypes.NUMBER_FLOAT.newLiteralType(node.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(LoadConst.Str node) {
|
|
||||||
assign(node.dest(), LuaTypes.STRING.newLiteralType(node.value()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(BinOp node) {
|
|
||||||
Type l = typeOf(node.left());
|
|
||||||
Type r = typeOf(node.right());
|
|
||||||
|
|
||||||
final Type result;
|
|
||||||
|
|
||||||
Type emulatedResult = Typer.emulateOp(node.op(), l, r);
|
|
||||||
if (emulatedResult != null) {
|
|
||||||
result = emulatedResult;
|
|
||||||
} else {
|
|
||||||
StaticMathImplementation math = staticMath(node.op());
|
|
||||||
|
|
||||||
if (math != null) {
|
|
||||||
NumericOperationType ot = math.opType(l, r);
|
|
||||||
result = ot.toType();
|
|
||||||
|
|
||||||
if (ot == NumericOperationType.Any) {
|
|
||||||
mayCallMetamethod();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (node.op()) {
|
|
||||||
case CONCAT:
|
|
||||||
if (stringable(l) && stringable(r)) {
|
|
||||||
result = LuaTypes.STRING;
|
|
||||||
} else {
|
|
||||||
result = LuaTypes.ANY;
|
|
||||||
mayCallMetamethod();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EQ:
|
|
||||||
case NEQ:
|
|
||||||
case LT:
|
|
||||||
case LE:
|
|
||||||
result = LuaTypes.BOOLEAN;
|
|
||||||
mayCallMetamethod(); // TODO: may be restricted (see §2.4 of LRM)
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new UnsupportedOperationException("Illegal binary operation: " + node.op());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assign(node.dest(), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(UnOp node) {
|
|
||||||
Type a = typeOf(node.arg());
|
|
||||||
|
|
||||||
final Type result;
|
|
||||||
|
|
||||||
Type emulatedResult = Typer.emulateOp(node.op(), a);
|
|
||||||
if (emulatedResult != null) {
|
|
||||||
result = emulatedResult;
|
|
||||||
} else {
|
|
||||||
switch (node.op()) {
|
|
||||||
case UNM:
|
|
||||||
result = MAY_BE_INTEGER.opType(a).toType();
|
|
||||||
break;
|
|
||||||
case BNOT:
|
|
||||||
result = MUST_BE_INTEGER.opType(a).toType();
|
|
||||||
break;
|
|
||||||
case NOT:
|
|
||||||
result = LuaTypes.BOOLEAN;
|
|
||||||
break;
|
|
||||||
case LEN:
|
|
||||||
result = a.isSubtypeOf(LuaTypes.STRING) ? LuaTypes.NUMBER_INTEGER : LuaTypes.ANY;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new UnsupportedOperationException("Illegal unary operation: " + node.op());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assign(node.dest(), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabNew node) {
|
|
||||||
mayCallMetamethod();
|
|
||||||
assign(node.dest(), LuaTypes.TABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabGet node) {
|
|
||||||
mayCallMetamethod();
|
|
||||||
assign(node.dest(), LuaTypes.ANY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabSet node) {
|
|
||||||
mayCallMetamethod();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TabRawAppendMulti node) {
|
|
||||||
// no effect on vals
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(VarLoad node) {
|
|
||||||
Type t = currentVarState().load(node.var());
|
|
||||||
assign(node.dest(), t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(VarInit node) {
|
|
||||||
currentVarState().store(node.var(), typeOf(node.src()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(VarStore node) {
|
|
||||||
currentVarState().store(node.var(), typeOf(node.src()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(UpLoad node) {
|
|
||||||
// TODO
|
|
||||||
assign(node.dest(), LuaTypes.ANY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(UpStore node) {
|
|
||||||
// no effect on vals
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Vararg node) {
|
|
||||||
TypeSeq varargType = TypeSeq.empty().withVararg(); // TODO
|
|
||||||
assign(node.dest(), varargType);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TypeSeq vlistType(VList vlist) {
|
|
||||||
Type[] fixed = new Type[vlist.addrs().size()];
|
|
||||||
for (int i = 0; i < vlist.addrs().size(); i++) {
|
|
||||||
fixed[i] = typeOf(vlist.addrs().get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return vlist.suffix() != null
|
|
||||||
? typeOf(vlist.suffix()).prefixedBy(fixed)
|
|
||||||
: TypeSeq.of(fixed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Ret node) {
|
|
||||||
returnTypes.add(new ReturnType.ConcreteReturnType(vlistType(node.args())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(TCall node) {
|
|
||||||
returnTypes
|
|
||||||
.add(new ReturnType.TailCallReturnType(typeOf(node.target()), vlistType(node.args())));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TypeSeq callReturnType(Val target, VList args) {
|
|
||||||
TypeSeq argTypes = vlistType(args);
|
|
||||||
return TypeSeq.empty().withVararg(); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Call node) {
|
|
||||||
TypeSeq returnType = callReturnType(node.fn(), node.args());
|
|
||||||
assign(node.dest(), returnType);
|
|
||||||
impure();
|
|
||||||
useStack();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(MultiGet node) {
|
|
||||||
assign(node.dest(), typeOf(node.src()).get(node.idx()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(PhiStore node) {
|
|
||||||
assign(node.dest(), typeOf(node.src()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(PhiLoad node) {
|
|
||||||
assign(node.dest(), typeOf(node.src()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(Closure node) {
|
|
||||||
for (AbstractVar av : node.args()) {
|
|
||||||
if (av instanceof Var) {
|
|
||||||
Var v = (Var) av;
|
|
||||||
currentVarState().load(v); // ignoring the result, just marking its use
|
|
||||||
reifiedVars.add(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: look up the type for this closure
|
|
||||||
Type t = LuaTypes.FUNCTION;
|
|
||||||
|
|
||||||
assign(node.dest(), t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void visit(ToNumber node) {
|
|
||||||
Type t = typeOf(node.src());
|
|
||||||
Type result = t.isSubtypeOf(LuaTypes.NUMBER) ? t : LuaTypes.NUMBER;
|
|
||||||
|
|
||||||
assign(node.dest(), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class VarState {
|
|
||||||
|
|
||||||
private final Map<Var, Type> types;
|
|
||||||
|
|
||||||
private VarState(Map<Var, Type> types) {
|
|
||||||
this.types = Objects.requireNonNull(types);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VarState() {
|
|
||||||
this(new HashMap<Var, Type>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public VarState copy() {
|
|
||||||
return new VarState(new HashMap<>(types));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void store(Var v, Type t) {
|
|
||||||
Objects.requireNonNull(v);
|
|
||||||
allVars.add(v);
|
|
||||||
types.put(v, Objects.requireNonNull(t));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type load(Var v) {
|
|
||||||
Objects.requireNonNull(v);
|
|
||||||
allVars.add(v);
|
|
||||||
Type t = types.get(Objects.requireNonNull(v));
|
|
||||||
if (t == null) {
|
|
||||||
throw new IllegalStateException(v + " used before stored into");
|
|
||||||
} else {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean joinWith(VarState that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
Map<Var, Type> result = new HashMap<>();
|
|
||||||
for (Var v : this.types.keySet()) {
|
|
||||||
Type t = joinTypes(types.get(v), that.types.get(v));
|
|
||||||
result.put(v, Objects.requireNonNull(t));
|
|
||||||
}
|
|
||||||
for (Var v : that.types.keySet()) {
|
|
||||||
Type t = joinTypes(types.get(v), that.types.get(v));
|
|
||||||
result.put(v, Objects.requireNonNull(t));
|
|
||||||
}
|
|
||||||
if (!result.equals(types)) {
|
|
||||||
types.clear();
|
|
||||||
types.putAll(result);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearReifiedVars() {
|
|
||||||
for (Var v : this.types.keySet()) {
|
|
||||||
if (reifiedVars.contains(v)) {
|
|
||||||
store(v, LuaTypes.ANY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static analysis of Lua programs operating on the IR.
|
|
||||||
*/
|
|
||||||
package org.classdump.luna.compiler.analysis;
|
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class AbstractType extends Type {
|
|
||||||
|
|
||||||
protected final AbstractType supertype; // may be null
|
|
||||||
protected final String name;
|
|
||||||
|
|
||||||
protected AbstractType(AbstractType supertype, String name) {
|
|
||||||
this.supertype = supertype;
|
|
||||||
this.name = Objects.requireNonNull(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbstractType supertype() {
|
|
||||||
return supertype;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSubtypeOf(Type that) {
|
|
||||||
return this.equals(that) || (this.supertype() != null && this.supertype().isSubtypeOf(that));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type restrict(Type that) {
|
|
||||||
return that instanceof DynamicType ? that : this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type join(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (that.isSubtypeOf(this)) {
|
|
||||||
return this;
|
|
||||||
} else if (this.supertype() != null) {
|
|
||||||
return this.supertype().join(that);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type meet(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (this.isSubtypeOf(that)) {
|
|
||||||
return this;
|
|
||||||
} else if (that.isSubtypeOf(this)) {
|
|
||||||
return that;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
public final class BottomType extends Type {
|
|
||||||
|
|
||||||
public static final BottomType INSTANCE = new BottomType();
|
|
||||||
|
|
||||||
private BottomType() {
|
|
||||||
// not to be instantiated by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "⊥";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSubtypeOf(Type that) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type restrict(Type that) {
|
|
||||||
return that instanceof DynamicType ? that : this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type join(Type that) {
|
|
||||||
return that;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type meet(Type that) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public Type unionWith(Type that) {
|
|
||||||
// return that;
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
public class ConcreteLitType<T> extends ConcreteType {
|
|
||||||
|
|
||||||
protected ConcreteLitType(AbstractType supertype, String name) {
|
|
||||||
super(supertype, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiteralType<T> newLiteralType(T value) {
|
|
||||||
return new LiteralType<>(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class ConcreteType extends Type {
|
|
||||||
|
|
||||||
protected final AbstractType supertype;
|
|
||||||
protected final String name;
|
|
||||||
|
|
||||||
protected ConcreteType(AbstractType supertype, String name) {
|
|
||||||
this.supertype = Objects.requireNonNull(supertype);
|
|
||||||
this.name = Objects.requireNonNull(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbstractType supertype() {
|
|
||||||
return supertype;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSubtypeOf(Type that) {
|
|
||||||
return this.equals(that) || this.supertype().isSubtypeOf(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type restrict(Type that) {
|
|
||||||
return that instanceof DynamicType ? that : this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type join(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (that.isSubtypeOf(this)) {
|
|
||||||
return this;
|
|
||||||
} else {
|
|
||||||
return this.supertype().join(that);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type meet(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (this.isSubtypeOf(that)) {
|
|
||||||
return this;
|
|
||||||
} else if (that.isSubtypeOf(this)) {
|
|
||||||
return that;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public Type unionWith(Type that) {
|
|
||||||
// if (this.isSubtypeOf(that)) return that;
|
|
||||||
// else if (that.isSubtypeOf(this)) return this;
|
|
||||||
// else {
|
|
||||||
// Type t = this.join(that);
|
|
||||||
// if (t != null) return t;
|
|
||||||
// else return DynamicType.INSTANCE; // FIXME: is this correct?
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public final class DynamicType extends Type {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
public DynamicType(String name) {
|
|
||||||
this.name = Objects.requireNonNull(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSubtypeOf(Type that) {
|
|
||||||
return this.equals(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type restrict(Type that) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type join(Type that) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type meet(Type that) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public Type unionWith(Type that) {
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class FunctionType extends ConcreteType {
|
|
||||||
|
|
||||||
// FIXME: masking the field in the superclass!
|
|
||||||
protected final AbstractType supertype;
|
|
||||||
|
|
||||||
protected final TypeSeq typeSeq;
|
|
||||||
protected final TypeSeq returnTypes;
|
|
||||||
|
|
||||||
FunctionType(AbstractType supertype, TypeSeq arg, TypeSeq ret) {
|
|
||||||
super(supertype, supertype.name);
|
|
||||||
this.supertype = Objects.requireNonNull(supertype);
|
|
||||||
this.typeSeq = Objects.requireNonNull(arg);
|
|
||||||
this.returnTypes = Objects.requireNonNull(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FunctionType that = (FunctionType) o;
|
|
||||||
|
|
||||||
return typeSeq.equals(that.typeSeq) && returnTypes.equals(that.returnTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = typeSeq.hashCode();
|
|
||||||
result = 31 * result + returnTypes.hashCode();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return name + argumentTypes().toString() + "->" + returnTypes().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public String toExplicitString() {
|
|
||||||
return "(" + argumentTypes().toString() + ") -> (" + returnTypes().toString() + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq argumentTypes() {
|
|
||||||
return typeSeq;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq returnTypes() {
|
|
||||||
return returnTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSubtypeOf(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (this.equals(that)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (that instanceof FunctionType) {
|
|
||||||
FunctionType ft = (FunctionType) that;
|
|
||||||
|
|
||||||
return ft.argumentTypes().isSubsumedBy(this.argumentTypes())
|
|
||||||
&& this.returnTypes().isSubsumedBy(ft.returnTypes());
|
|
||||||
} else {
|
|
||||||
return this.supertype().isSubtypeOf(that);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type join(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (this.isSubtypeOf(that)) {
|
|
||||||
return that;
|
|
||||||
} else if (that instanceof FunctionType) {
|
|
||||||
FunctionType ft = (FunctionType) that;
|
|
||||||
|
|
||||||
TypeSeq arg = this.argumentTypes().meet(ft.argumentTypes());
|
|
||||||
TypeSeq ret = this.returnTypes().join(ft.returnTypes());
|
|
||||||
|
|
||||||
return arg != null && ret != null ? new FunctionType(supertype, arg, ret) : null;
|
|
||||||
} else {
|
|
||||||
return this.supertype().join(that);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type meet(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (this.isSubtypeOf(that)) {
|
|
||||||
return this;
|
|
||||||
} else if (that.isSubtypeOf(this)) {
|
|
||||||
return that;
|
|
||||||
} else if (that instanceof FunctionType) {
|
|
||||||
FunctionType ft = (FunctionType) that;
|
|
||||||
|
|
||||||
TypeSeq arg = this.argumentTypes().join(ft.argumentTypes());
|
|
||||||
TypeSeq ret = this.returnTypes().meet(ft.returnTypes());
|
|
||||||
|
|
||||||
return arg != null && ret != null ? new FunctionType(supertype, arg, ret) : null;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type restrict(Type that) {
|
|
||||||
if (that instanceof FunctionType) {
|
|
||||||
FunctionType thatFt = (FunctionType) that;
|
|
||||||
return new FunctionType(supertype,
|
|
||||||
this.argumentTypes().restrict(thatFt.argumentTypes()),
|
|
||||||
this.returnTypes().restrict(thatFt.returnTypes()));
|
|
||||||
} else {
|
|
||||||
return that instanceof DynamicType ? that : this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConsistentWith(Type that) {
|
|
||||||
if (that instanceof FunctionType) {
|
|
||||||
FunctionType thatFunc = (FunctionType) that;
|
|
||||||
|
|
||||||
return this.argumentTypes().isConsistentWith(thatFunc.argumentTypes())
|
|
||||||
&& this.returnTypes().isConsistentWith(thatFunc.returnTypes());
|
|
||||||
} else {
|
|
||||||
return super.isConsistentWith(that);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public Type unionWith(Type that) {
|
|
||||||
// return this.restrict(that).join(that.restrict(this));
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
public interface GradualTypeLike<T> {
|
|
||||||
|
|
||||||
boolean isConsistentWith(T that);
|
|
||||||
|
|
||||||
boolean isConsistentSubtypeOf(T that);
|
|
||||||
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class LiteralType<T> extends Type {
|
|
||||||
|
|
||||||
private final ConcreteType type;
|
|
||||||
private final T value;
|
|
||||||
|
|
||||||
public LiteralType(ConcreteType type, T value) {
|
|
||||||
this.type = Objects.requireNonNull(type);
|
|
||||||
this.value = Objects.requireNonNull(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteralType<?> that = (LiteralType<?>) o;
|
|
||||||
|
|
||||||
if (!type.equals(that.type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return value.equals(that.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = type.hashCode();
|
|
||||||
result = 31 * result + value.hashCode();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return type.toString() + "(" + value + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConcreteType type() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T value() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSubtypeOf(Type that) {
|
|
||||||
return this.equals(that) || this.type().isSubtypeOf(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type restrict(Type that) {
|
|
||||||
return that instanceof DynamicType ? that : this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type join(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (that.isSubtypeOf(this)) {
|
|
||||||
return this;
|
|
||||||
} else {
|
|
||||||
return this.type().join(that);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type meet(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (this.isSubtypeOf(that)) {
|
|
||||||
return this;
|
|
||||||
} else if (that.isSubtypeOf(this)) {
|
|
||||||
return that;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import org.classdump.luna.ByteString;
|
|
||||||
|
|
||||||
public abstract class LuaTypes {
|
|
||||||
|
|
||||||
public static final TopType ANY = new TopType("any");
|
|
||||||
public static final DynamicType DYNAMIC = new DynamicType("dynamic");
|
|
||||||
public static final ConcreteType NIL = concreteSubtype(ANY, "nil");
|
|
||||||
public static final AbstractType NON_NIL = abstractSubtype(ANY, "nonnil");
|
|
||||||
public static final ConcreteLitType<Boolean> BOOLEAN = concreteSubtype(NON_NIL, "boolean",
|
|
||||||
Boolean.class);
|
|
||||||
public static final AbstractType NUMBER = abstractSubtype(NON_NIL, "number");
|
|
||||||
public static final ConcreteLitType<Long> NUMBER_INTEGER = concreteSubtype(NUMBER, "integer",
|
|
||||||
Long.class);
|
|
||||||
public static final ConcreteLitType<Double> NUMBER_FLOAT = concreteSubtype(NUMBER, "float",
|
|
||||||
Double.class);
|
|
||||||
public static final ConcreteLitType<ByteString> STRING = concreteSubtype(NON_NIL, "string",
|
|
||||||
ByteString.class);
|
|
||||||
public static final ConcreteType TABLE = concreteSubtype(NON_NIL, "table");
|
|
||||||
public static final AbstractType FUNCTION = abstractSubtype(NON_NIL, "function");
|
|
||||||
private LuaTypes() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
static AbstractType abstractSubtype(AbstractType parent, String name) {
|
|
||||||
return new AbstractType(parent, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ConcreteType concreteSubtype(AbstractType parent, String name) {
|
|
||||||
return new ConcreteType(parent, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> ConcreteLitType<T> concreteSubtype(AbstractType parent, String name, Class<T> clazz) {
|
|
||||||
return new ConcreteLitType<>(parent, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FunctionType functionType(TypeSeq argTypes, TypeSeq returnTypes) {
|
|
||||||
return new FunctionType(FUNCTION, argTypes, returnTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
public enum PartialOrderComparisonResult {
|
|
||||||
|
|
||||||
EQUAL,
|
|
||||||
LESSER_THAN,
|
|
||||||
GREATER_THAN,
|
|
||||||
NOT_COMPARABLE;
|
|
||||||
|
|
||||||
public static PartialOrderComparisonResult fromTotalOrderComparison(int cmp) {
|
|
||||||
if (cmp < 0) {
|
|
||||||
return LESSER_THAN;
|
|
||||||
} else if (cmp > 0) {
|
|
||||||
return GREATER_THAN;
|
|
||||||
} else {
|
|
||||||
return EQUAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDefined() {
|
|
||||||
return this != NOT_COMPARABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int toTotalOrderComparison() {
|
|
||||||
switch (this) {
|
|
||||||
case EQUAL:
|
|
||||||
return 0;
|
|
||||||
case LESSER_THAN:
|
|
||||||
return -1;
|
|
||||||
case GREATER_THAN:
|
|
||||||
return +1;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Not comparable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public abstract class ReturnType {
|
|
||||||
|
|
||||||
private ReturnType() {
|
|
||||||
// not to be extended by the outside world
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ConcreteReturnType extends ReturnType {
|
|
||||||
|
|
||||||
public final TypeSeq typeSeq;
|
|
||||||
|
|
||||||
public ConcreteReturnType(TypeSeq typeSeq) {
|
|
||||||
this.typeSeq = Objects.requireNonNull(typeSeq);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConcreteReturnType that = (ConcreteReturnType) o;
|
|
||||||
|
|
||||||
return typeSeq.equals(that.typeSeq);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return typeSeq.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return typeSeq.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TailCallReturnType extends ReturnType {
|
|
||||||
|
|
||||||
public final Type target;
|
|
||||||
public final TypeSeq typeSeq;
|
|
||||||
|
|
||||||
public TailCallReturnType(Type target, TypeSeq typeSeq) {
|
|
||||||
this.target = Objects.requireNonNull(target);
|
|
||||||
this.typeSeq = Objects.requireNonNull(typeSeq);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TailCallReturnType that = (TailCallReturnType) o;
|
|
||||||
|
|
||||||
return target == that.target && typeSeq.equals(that.typeSeq);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = target.hashCode();
|
|
||||||
result = 31 * result + typeSeq.hashCode();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return target.toString() + "(" + typeSeq + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
public final class TopType extends AbstractType {
|
|
||||||
|
|
||||||
public TopType(String name) {
|
|
||||||
super(null, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSubtypeOf(Type that) {
|
|
||||||
return this.equals(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type restrict(Type that) {
|
|
||||||
return that instanceof DynamicType ? that : this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type join(Type that) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Type meet(Type that) {
|
|
||||||
return that;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public Type unionWith(Type that) {
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public abstract class Type implements GradualTypeLike<Type> {
|
|
||||||
|
|
||||||
protected Type() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: number-as-string, string-as-number, true, false, actual constant values?
|
|
||||||
|
|
||||||
// standard subtyping relation
|
|
||||||
// must return true if this.equals(that).
|
|
||||||
public abstract boolean isSubtypeOf(Type that);
|
|
||||||
|
|
||||||
// consistency relation
|
|
||||||
@Override
|
|
||||||
public boolean isConsistentWith(Type that) {
|
|
||||||
return this.restrict(that).equals(that.restrict(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Type restrict(Type that);
|
|
||||||
|
|
||||||
// return true iff type(this) ~< type(that)
|
|
||||||
@Override
|
|
||||||
public boolean isConsistentSubtypeOf(Type that) {
|
|
||||||
return this.restrict(that).isSubtypeOf(that.restrict(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the most specific type that is more general than both this and that,
|
|
||||||
// or null if such type does not exist
|
|
||||||
@Deprecated
|
|
||||||
public abstract Type join(Type that);
|
|
||||||
|
|
||||||
// return the most general type that is more specific than both this and that,
|
|
||||||
// or null if such type does not exist
|
|
||||||
@Deprecated
|
|
||||||
public abstract Type meet(Type that);
|
|
||||||
|
|
||||||
// return a type T such that this.isConsistentSubtypeOf(T) && that.isConsistentSubtypeOf(T)
|
|
||||||
public Type unionWith(Type that) {
|
|
||||||
return this.restrict(that).join(that.restrict(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare this to that, returning:
|
|
||||||
// EQUAL if this.equals(that);
|
|
||||||
// LESSER_THAN if this.isSubtypeOf(that) && !this.equals(that);
|
|
||||||
// GREATER_THAN if that.isSubtypeOf(this) && !this.equals(that);
|
|
||||||
// NOT_COMPARABLE if !this.isSubtypeOf(that) && !that.isSubtypeOf(that).
|
|
||||||
public PartialOrderComparisonResult compareTo(Type that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
if (this.isSubtypeOf(that)) {
|
|
||||||
if (this.equals(that)) {
|
|
||||||
return PartialOrderComparisonResult.EQUAL;
|
|
||||||
} else {
|
|
||||||
return PartialOrderComparisonResult.LESSER_THAN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (that.isSubtypeOf(this)) {
|
|
||||||
return PartialOrderComparisonResult.GREATER_THAN;
|
|
||||||
} else {
|
|
||||||
return PartialOrderComparisonResult.NOT_COMPARABLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,272 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.util.Check;
|
|
||||||
|
|
||||||
public class TypeSeq implements GradualTypeLike<TypeSeq> {
|
|
||||||
|
|
||||||
protected final List<Type> fixed;
|
|
||||||
protected final Type tailType;
|
|
||||||
|
|
||||||
TypeSeq(List<Type> fixed, Type tailType) {
|
|
||||||
this.fixed = Objects.requireNonNull(fixed);
|
|
||||||
this.tailType = Objects.requireNonNull(tailType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// private static final TypeSeq EMPTY_FIXED = new TypeSeq(ReadOnlyArray.wrap(new Type[0]), false);
|
|
||||||
// private static final TypeSeq EMPTY_VARARG = new TypeSeq(ReadOnlyArray.wrap(new Type[0]), true);
|
|
||||||
|
|
||||||
public static TypeSeq empty() {
|
|
||||||
// return EMPTY_FIXED;
|
|
||||||
return of();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TypeSeq vararg() {
|
|
||||||
// return EMPTY_VARARG;
|
|
||||||
return of().withVararg();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TypeSeq of(Type... fixed) {
|
|
||||||
return of(Arrays.asList(fixed), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TypeSeq of(List<Type> fixed, boolean vararg) {
|
|
||||||
return new TypeSeq(fixed, vararg ? LuaTypes.ANY : LuaTypes.NIL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq withVararg() {
|
|
||||||
return new TypeSeq(fixed, LuaTypes.ANY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeSeq that = (TypeSeq) o;
|
|
||||||
|
|
||||||
return this.tailType.equals(that.tailType) && this.fixed.equals(that.fixed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = fixed.hashCode();
|
|
||||||
result = 31 * result + tailType.hashCode();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder bld = new StringBuilder();
|
|
||||||
|
|
||||||
bld.append('(');
|
|
||||||
|
|
||||||
final String tail = tailType.equals(LuaTypes.NIL) ? "" : tailType.toString() + "*";
|
|
||||||
|
|
||||||
Iterator<Type> it = fixed.iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
Type t = it.next();
|
|
||||||
bld.append(t.toString());
|
|
||||||
if (it.hasNext() || !tail.isEmpty()) {
|
|
||||||
bld.append(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tail.isEmpty()) {
|
|
||||||
bld.append(tail);
|
|
||||||
}
|
|
||||||
|
|
||||||
bld.append(')');
|
|
||||||
|
|
||||||
return bld.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Type> fixed() {
|
|
||||||
return fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type tailType() {
|
|
||||||
return tailType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type get(int idx) {
|
|
||||||
Check.nonNegative(idx);
|
|
||||||
return idx < fixed().size() ? fixed().get(idx) : tailType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq prefixedBy(Type[] types) {
|
|
||||||
Objects.requireNonNull(types);
|
|
||||||
List<Type> ts = new ArrayList<>(types.length + fixed.size());
|
|
||||||
ts.addAll(Arrays.asList(types));
|
|
||||||
ts.addAll(fixed);
|
|
||||||
return new TypeSeq(Collections.unmodifiableList(ts), this.tailType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSubsumedBy(TypeSeq that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
// that is more general than this
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.max(this.fixed().size(), that.fixed().size()); i++) {
|
|
||||||
if (!this.get(i).isSubtypeOf(that.get(i))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tailType.isSubtypeOf(that.tailType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq join(TypeSeq that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
ArrayList<Type> fix = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.max(this.fixed().size(), that.fixed().size()); i++) {
|
|
||||||
Type j = this.get(i).join(that.get(i));
|
|
||||||
if (j != null) {
|
|
||||||
fix.add(j);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type tt = this.tailType.join(that.tailType);
|
|
||||||
|
|
||||||
if (tt != null) {
|
|
||||||
return new TypeSeq(Collections.unmodifiableList(fix), tt);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns null to indicate that no meet exists
|
|
||||||
public TypeSeq meet(TypeSeq that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
ArrayList<Type> fix = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.max(this.fixed().size(), that.fixed().size()); i++) {
|
|
||||||
Type m = this.get(i).meet(that.get(i));
|
|
||||||
if (m != null) {
|
|
||||||
fix.add(m);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Type tt = this.tailType.meet(that.tailType);
|
|
||||||
|
|
||||||
if (tt != null) {
|
|
||||||
return new TypeSeq(Collections.unmodifiableList(fix), tt);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let < be the subtyping relation, and if seq is a TypeSeq and i is an index (a non-negative
|
|
||||||
// integer), seq[i] is a shortcut for seq.get(i). We'll say that a type u is comparable
|
|
||||||
// to type v iff (u == v || u < v || v < u), and not comparable otherwise.
|
|
||||||
//
|
|
||||||
// Then:
|
|
||||||
// Returns NOT_COMPARABLE iff there is an index i such that this[i] is not comparable to that[i];
|
|
||||||
// Returns EQUAL iff for all i, this[i] == that[i];
|
|
||||||
// Returns LESSER_THAN iff there is an index i such that for all j < i, this[j] == that[j]
|
|
||||||
// and this[i] < that[i] and for all k, this[k] is comparable to that[k];
|
|
||||||
// Returns GREATER_THAN iff there is an index i such that for all j < i, this[j] == that[j]
|
|
||||||
// and that[i] < this[i] and for all k, this[k] is comparable to that[k].
|
|
||||||
//
|
|
||||||
// If this.isSubsumedBy(that) then this.comparePointwiseTo(that) == EQUAL || this.comparePointwiseTo(that) == LESSER_THAN;
|
|
||||||
// Please note that this is an implication: the opposite direction does *not* in general hold.
|
|
||||||
public PartialOrderComparisonResult comparePointwiseTo(TypeSeq that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
int len = Math.max(this.fixed().size(), that.fixed().size());
|
|
||||||
|
|
||||||
PartialOrderComparisonResult result = null;
|
|
||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
PartialOrderComparisonResult r = this.get(i).compareTo(that.get(i));
|
|
||||||
|
|
||||||
if (!r.isDefined()) {
|
|
||||||
return PartialOrderComparisonResult.NOT_COMPARABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null && r != PartialOrderComparisonResult.EQUAL) {
|
|
||||||
result = r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PartialOrderComparisonResult tr = this.tailType.compareTo(that.tailType);
|
|
||||||
|
|
||||||
if (result != null && tr.isDefined()) {
|
|
||||||
// tr doesn't ruin the result
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
return tr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConsistentWith(TypeSeq that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.max(this.fixed().size(), that.fixed().size()); i++) {
|
|
||||||
if (!this.get(i).isConsistentWith(that.get(i))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tailType.isConsistentWith(that.tailType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypeSeq restrict(TypeSeq that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
ArrayList<Type> ts = new ArrayList<>();
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.max(this.fixed().size(), that.fixed().size()); i++) {
|
|
||||||
ts.add(this.get(i).restrict(that.get(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TypeSeq(Collections.unmodifiableList(ts), this.tailType.restrict(that.tailType));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConsistentSubtypeOf(TypeSeq that) {
|
|
||||||
Objects.requireNonNull(that);
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.max(this.fixed().size(), that.fixed().size()); i++) {
|
|
||||||
if (!this.get(i).isConsistentSubtypeOf(that.get(i))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tailType.isConsistentSubtypeOf(that.tailType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classes representing Lua types, used in static analysis.
|
|
||||||
*/
|
|
||||||
package org.classdump.luna.compiler.analysis.types;
|
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen;
|
|
||||||
|
|
||||||
public abstract class BytecodeEmitter {
|
|
||||||
|
|
||||||
public abstract CompiledClass emit();
|
|
||||||
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen;
|
|
||||||
|
|
||||||
public interface ClassNameTranslator {
|
|
||||||
|
|
||||||
String className();
|
|
||||||
|
|
||||||
ClassNameTranslator child(int idx);
|
|
||||||
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.compiler.ir.BasicBlock;
|
|
||||||
import org.classdump.luna.compiler.ir.BodyNode;
|
|
||||||
import org.classdump.luna.compiler.ir.Code;
|
|
||||||
import org.classdump.luna.compiler.ir.Label;
|
|
||||||
import org.classdump.luna.compiler.ir.Line;
|
|
||||||
import org.classdump.luna.compiler.ir.ToNext;
|
|
||||||
|
|
||||||
public final class CodeSegmenter {
|
|
||||||
|
|
||||||
private CodeSegmenter() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int blockLength(BasicBlock blk) {
|
|
||||||
return blk.body().size() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int lastLine(List<BodyNode> nodes) {
|
|
||||||
int line = -1;
|
|
||||||
for (BodyNode n : nodes) {
|
|
||||||
if (n instanceof Line) {
|
|
||||||
line = ((Line) n).lineNumber();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BlockSplit splitBlockAt(BasicBlock blk, int index, int splitIdx) {
|
|
||||||
List<BodyNode> predBody = blk.body().subList(0, index);
|
|
||||||
List<BodyNode> succBody = new ArrayList<>();
|
|
||||||
|
|
||||||
// carry line info over to succ
|
|
||||||
int firstSuccLine = lastLine(predBody);
|
|
||||||
if (firstSuccLine != -1) {
|
|
||||||
succBody.add(new Line(firstSuccLine));
|
|
||||||
}
|
|
||||||
|
|
||||||
// append nodes to succ
|
|
||||||
succBody.addAll(blk.body().subList(index, blk.body().size()));
|
|
||||||
|
|
||||||
Label succLabel = new Label(-(splitIdx + 1));
|
|
||||||
|
|
||||||
BasicBlock pred = new BasicBlock(blk.label(), predBody, new ToNext(succLabel));
|
|
||||||
BasicBlock succ = new BasicBlock(succLabel, Collections.unmodifiableList(succBody), blk.end());
|
|
||||||
|
|
||||||
return new BlockSplit(pred, succ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SegmentedCode segment(Code code, int limit) {
|
|
||||||
if (limit <= 0) {
|
|
||||||
return SegmentedCode.singleton(code);
|
|
||||||
} else {
|
|
||||||
List<List<BasicBlock>> segmentBlocks = new ArrayList<>();
|
|
||||||
|
|
||||||
List<BasicBlock> currentSegment = new ArrayList<>();
|
|
||||||
int count = 0;
|
|
||||||
int splitIdx = 0;
|
|
||||||
|
|
||||||
Iterator<BasicBlock> bit = code.blockIterator();
|
|
||||||
BasicBlock blk = bit.hasNext() ? bit.next() : null;
|
|
||||||
|
|
||||||
while (blk != null) {
|
|
||||||
int len = blockLength(blk);
|
|
||||||
|
|
||||||
if (count + len < limit) {
|
|
||||||
// block fits in with a margin, append and fetch next one
|
|
||||||
currentSegment.add(blk);
|
|
||||||
count += len;
|
|
||||||
|
|
||||||
blk = bit.hasNext() ? bit.next() : null;
|
|
||||||
} else if (count + len == limit) {
|
|
||||||
// fits entirely in, but it's the last one
|
|
||||||
currentSegment.add(blk);
|
|
||||||
segmentBlocks.add(Collections.unmodifiableList(currentSegment));
|
|
||||||
|
|
||||||
// start new segment
|
|
||||||
currentSegment = new ArrayList<>();
|
|
||||||
count = 0;
|
|
||||||
|
|
||||||
blk = bit.hasNext() ? bit.next() : null;
|
|
||||||
} else {
|
|
||||||
assert (count + len > limit);
|
|
||||||
|
|
||||||
// split blk and try again
|
|
||||||
BlockSplit split = splitBlockAt(blk, limit - count, splitIdx++);
|
|
||||||
|
|
||||||
// current segment is done
|
|
||||||
currentSegment.add(split.pred);
|
|
||||||
segmentBlocks.add(Collections.unmodifiableList(currentSegment));
|
|
||||||
|
|
||||||
// start new segment
|
|
||||||
currentSegment = new ArrayList<>();
|
|
||||||
count = 0;
|
|
||||||
|
|
||||||
blk = split.succ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentSegment.isEmpty()) {
|
|
||||||
segmentBlocks.add(Collections.unmodifiableList(currentSegment));
|
|
||||||
}
|
|
||||||
|
|
||||||
return SegmentedCode.of(segmentBlocks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BlockSplit {
|
|
||||||
|
|
||||||
final BasicBlock pred;
|
|
||||||
final BasicBlock succ;
|
|
||||||
|
|
||||||
private BlockSplit(BasicBlock pred, BasicBlock succ) {
|
|
||||||
this.pred = Objects.requireNonNull(pred);
|
|
||||||
this.succ = Objects.requireNonNull(succ);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.util.ByteVector;
|
|
||||||
|
|
||||||
public class CompiledClass {
|
|
||||||
|
|
||||||
protected final String name;
|
|
||||||
protected final ByteVector bytes;
|
|
||||||
|
|
||||||
public CompiledClass(String name, ByteVector bytes) {
|
|
||||||
this.name = Objects.requireNonNull(name);
|
|
||||||
this.bytes = Objects.requireNonNull(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompiledClass that = (CompiledClass) o;
|
|
||||||
|
|
||||||
return name.equals(that.name) && bytes.equals(that.bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = name.hashCode();
|
|
||||||
result = 31 * result + bytes.hashCode();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String name() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ByteVector bytes() {
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.compiler.ir.BasicBlock;
|
|
||||||
import org.classdump.luna.compiler.ir.Code;
|
|
||||||
import org.classdump.luna.compiler.ir.Label;
|
|
||||||
|
|
||||||
public class SegmentedCode {
|
|
||||||
|
|
||||||
private final List<List<BasicBlock>> segments;
|
|
||||||
|
|
||||||
private final Map<Label, LabelEntry> index;
|
|
||||||
|
|
||||||
private SegmentedCode(List<List<BasicBlock>> segments, Map<Label, LabelEntry> index) {
|
|
||||||
this.segments = Objects.requireNonNull(segments);
|
|
||||||
this.index = Objects.requireNonNull(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SegmentedCode singleton(Code code) {
|
|
||||||
List<List<BasicBlock>> blocks = new ArrayList<>();
|
|
||||||
|
|
||||||
List<BasicBlock> blks = new ArrayList<>();
|
|
||||||
Iterator<BasicBlock> bit = code.blockIterator();
|
|
||||||
while (bit.hasNext()) {
|
|
||||||
blks.add(bit.next());
|
|
||||||
}
|
|
||||||
blocks.add(Collections.unmodifiableList(blks));
|
|
||||||
|
|
||||||
return of(blocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SegmentedCode of(List<List<BasicBlock>> segments) {
|
|
||||||
List<List<BasicBlock>> segs = Collections.unmodifiableList(segments);
|
|
||||||
|
|
||||||
// build index
|
|
||||||
Map<Label, LabelEntry> index = new HashMap<>();
|
|
||||||
for (int i = 0; i < segs.size(); i++) {
|
|
||||||
int j = 0;
|
|
||||||
for (BasicBlock blk : segs.get(i)) {
|
|
||||||
index.put(blk.label(), new LabelEntry(blk.label(), i, j));
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SegmentedCode(segs, Collections.unmodifiableMap(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<List<BasicBlock>> segments() {
|
|
||||||
return segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LabelEntry labelEntry(Label l) {
|
|
||||||
LabelEntry le = index.get(Objects.requireNonNull(l));
|
|
||||||
if (le == null) {
|
|
||||||
throw new IllegalStateException("Label not found: " + l);
|
|
||||||
} else {
|
|
||||||
return le;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSingleton() {
|
|
||||||
return segments.size() == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LabelEntry {
|
|
||||||
|
|
||||||
public final Label label;
|
|
||||||
public final int segmentIdx;
|
|
||||||
public final int idx;
|
|
||||||
|
|
||||||
public LabelEntry(Label label, int segmentIdx, int idx) {
|
|
||||||
this.label = Objects.requireNonNull(label);
|
|
||||||
this.segmentIdx = segmentIdx;
|
|
||||||
this.idx = idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class SuffixingClassNameTranslator implements ClassNameTranslator {
|
|
||||||
|
|
||||||
private final String base;
|
|
||||||
|
|
||||||
public SuffixingClassNameTranslator(String base) {
|
|
||||||
this.base = Objects.requireNonNull(base);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String className() {
|
|
||||||
return "luna_dynamic." + base;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClassNameTranslator child(int idx) {
|
|
||||||
return new SuffixingClassNameTranslator(base + "$" + idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,337 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_FINAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_SUPER;
|
|
||||||
import static org.objectweb.asm.Opcodes.V1_7;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.Variable;
|
|
||||||
import org.classdump.luna.compiler.CompilerSettings;
|
|
||||||
import org.classdump.luna.compiler.FunctionId;
|
|
||||||
import org.classdump.luna.compiler.IRFunc;
|
|
||||||
import org.classdump.luna.compiler.analysis.DependencyInfo;
|
|
||||||
import org.classdump.luna.compiler.analysis.SlotAllocInfo;
|
|
||||||
import org.classdump.luna.compiler.analysis.TypeInfo;
|
|
||||||
import org.classdump.luna.compiler.gen.BytecodeEmitter;
|
|
||||||
import org.classdump.luna.compiler.gen.ClassNameTranslator;
|
|
||||||
import org.classdump.luna.compiler.gen.CompiledClass;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.helpers.ASMUtils;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.helpers.InvokableMethods;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.helpers.InvokeKind;
|
|
||||||
import org.classdump.luna.compiler.ir.AbstractVar;
|
|
||||||
import org.classdump.luna.compiler.ir.UpVar;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
import org.classdump.luna.impl.DefaultSavedState;
|
|
||||||
import org.classdump.luna.util.ByteVector;
|
|
||||||
import org.objectweb.asm.ClassReader;
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
|
||||||
import org.objectweb.asm.ClassWriter;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.ClassNode;
|
|
||||||
import org.objectweb.asm.tree.FieldNode;
|
|
||||||
import org.objectweb.asm.tree.InnerClassNode;
|
|
||||||
import org.objectweb.asm.util.CheckClassAdapter;
|
|
||||||
import org.objectweb.asm.util.TraceClassVisitor;
|
|
||||||
|
|
||||||
public class ASMBytecodeEmitter extends BytecodeEmitter {
|
|
||||||
|
|
||||||
public final IRFunc fn;
|
|
||||||
public final SlotAllocInfo slots;
|
|
||||||
public final TypeInfo types;
|
|
||||||
public final DependencyInfo deps;
|
|
||||||
|
|
||||||
public final CompilerSettings compilerSettings;
|
|
||||||
public final ClassNameTranslator classNameTranslator;
|
|
||||||
|
|
||||||
private final String sourceFile;
|
|
||||||
|
|
||||||
private final ClassNode classNode;
|
|
||||||
|
|
||||||
private final HashMap<UpVar, String> upvalueFieldNames;
|
|
||||||
|
|
||||||
private final List<FieldNode> fields;
|
|
||||||
|
|
||||||
private boolean verifyAndPrint;
|
|
||||||
|
|
||||||
public ASMBytecodeEmitter(
|
|
||||||
IRFunc fn,
|
|
||||||
SlotAllocInfo slots,
|
|
||||||
TypeInfo types,
|
|
||||||
DependencyInfo deps,
|
|
||||||
CompilerSettings compilerSettings,
|
|
||||||
ClassNameTranslator classNameTranslator,
|
|
||||||
String sourceFile) {
|
|
||||||
|
|
||||||
this.fn = Objects.requireNonNull(fn);
|
|
||||||
this.slots = Objects.requireNonNull(slots);
|
|
||||||
this.types = Objects.requireNonNull(types);
|
|
||||||
this.deps = Objects.requireNonNull(deps);
|
|
||||||
|
|
||||||
this.compilerSettings = Objects.requireNonNull(compilerSettings);
|
|
||||||
this.classNameTranslator = Objects.requireNonNull(classNameTranslator);
|
|
||||||
this.sourceFile = Objects.requireNonNull(sourceFile);
|
|
||||||
|
|
||||||
classNode = new ClassNode();
|
|
||||||
|
|
||||||
this.fields = new ArrayList<>();
|
|
||||||
|
|
||||||
upvalueFieldNames = new HashMap<>();
|
|
||||||
|
|
||||||
String s = System.getProperty("org.classdump.luna.compiler.VerifyAndPrint");
|
|
||||||
verifyAndPrint = s != null && "true".equals(s.trim().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static NestedInstanceKind functionKind(IRFunc fn) {
|
|
||||||
if (fn.upvals().isEmpty()) {
|
|
||||||
return NestedInstanceKind.Pure;
|
|
||||||
} else {
|
|
||||||
for (AbstractVar uv : fn.upvals()) {
|
|
||||||
if (uv instanceof Var) {
|
|
||||||
return NestedInstanceKind.Open;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NestedInstanceKind.Closed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String instanceFieldName() {
|
|
||||||
return "INSTANCE";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String toFieldName(String n) {
|
|
||||||
return n; // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String ensureUnique(Collection<String> ss, String s) {
|
|
||||||
int idx = 0;
|
|
||||||
String prefix = s;
|
|
||||||
|
|
||||||
while (ss.contains(s)) {
|
|
||||||
s = prefix + "_" + (idx++);
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String preferredUpvalueName(UpVar uv) {
|
|
||||||
return uv.name().value();
|
|
||||||
}
|
|
||||||
|
|
||||||
int kind() {
|
|
||||||
return InvokeKind.adjust_nativeKind(InvokeKind.encode(fn.params().size(), fn.isVararg()));
|
|
||||||
}
|
|
||||||
|
|
||||||
String thisClassName() {
|
|
||||||
return fn.id().toClassName(classNameTranslator);
|
|
||||||
}
|
|
||||||
|
|
||||||
Type thisClassType() {
|
|
||||||
return ASMUtils.typeForClassName(thisClassName());
|
|
||||||
}
|
|
||||||
|
|
||||||
Type superClassType() {
|
|
||||||
return Type.getType(InvokeKind.nativeClassForKind(kind()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Type parentClassType() {
|
|
||||||
FunctionId parentId = fn.id().parent();
|
|
||||||
return parentId != null
|
|
||||||
? ASMUtils.typeForClassName(parentId.toClassName(classNameTranslator))
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type savedStateClassType() {
|
|
||||||
return Type.getType(DefaultSavedState.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
Type invokeMethodType() {
|
|
||||||
return InvokableMethods.invoke_method(kind()).getMethodType();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasUpvalues() {
|
|
||||||
return !fn.upvals().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int numOfParameters() {
|
|
||||||
return fn.params().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVararg() {
|
|
||||||
return fn.isVararg();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FieldNode> fields() {
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addInnerClassLinks() {
|
|
||||||
String ownInternalName = thisClassType().getInternalName();
|
|
||||||
|
|
||||||
// parent
|
|
||||||
if (parentClassType() != null) {
|
|
||||||
String parentInternalName = parentClassType().getInternalName();
|
|
||||||
|
|
||||||
// assume (parentInternalName + "$") is the prefix of ownInternalName
|
|
||||||
String suffix = ownInternalName.substring(parentInternalName.length() + 1);
|
|
||||||
|
|
||||||
classNode.innerClasses.add(new InnerClassNode(
|
|
||||||
ownInternalName,
|
|
||||||
parentInternalName,
|
|
||||||
suffix,
|
|
||||||
ACC_PUBLIC + ACC_STATIC));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FunctionId> nestedIds = new ArrayList<>(deps.nestedRefs());
|
|
||||||
Collections.sort(nestedIds, FunctionId.LEXICOGRAPHIC_COMPARATOR);
|
|
||||||
|
|
||||||
for (FunctionId childId : nestedIds) {
|
|
||||||
String childClassName = childId.toClassName(classNameTranslator);
|
|
||||||
String childInternalName = ASMUtils.typeForClassName(childClassName).getInternalName();
|
|
||||||
|
|
||||||
// assume (ownInternalName + "$") is the prefix of childName
|
|
||||||
String suffix = childInternalName.substring(ownInternalName.length() + 1);
|
|
||||||
|
|
||||||
classNode.innerClasses.add(new InnerClassNode(
|
|
||||||
childInternalName,
|
|
||||||
ownInternalName,
|
|
||||||
suffix,
|
|
||||||
ACC_PUBLIC + ACC_STATIC));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private FieldNode instanceField() {
|
|
||||||
return new FieldNode(
|
|
||||||
ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
|
|
||||||
instanceFieldName(),
|
|
||||||
thisClassType().getDescriptor(),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
String addFieldName(String n) {
|
|
||||||
// TODO
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addUpvalueFields() {
|
|
||||||
for (UpVar uv : fn.upvals()) {
|
|
||||||
String name = toFieldName(ensureUnique(upvalueFieldNames.values(), preferredUpvalueName(uv)));
|
|
||||||
upvalueFieldNames.put(uv, name);
|
|
||||||
|
|
||||||
FieldNode fieldNode = new FieldNode(
|
|
||||||
ACC_PROTECTED + ACC_FINAL,
|
|
||||||
name,
|
|
||||||
Type.getDescriptor(Variable.class),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
classNode.fields.add(fieldNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUpvalueFieldName(UpVar uv) {
|
|
||||||
String name = upvalueFieldNames.get(uv);
|
|
||||||
if (name == null) {
|
|
||||||
throw new IllegalArgumentException("upvalue field name is null for upvalue " + uv);
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClassNode classNode() {
|
|
||||||
classNode.version = V1_7;
|
|
||||||
classNode.access = ACC_PUBLIC + ACC_SUPER;
|
|
||||||
classNode.name = thisClassType().getInternalName();
|
|
||||||
classNode.superName = superClassType().getInternalName();
|
|
||||||
classNode.sourceFile = sourceFile;
|
|
||||||
|
|
||||||
addInnerClassLinks();
|
|
||||||
|
|
||||||
if (!hasUpvalues()) {
|
|
||||||
classNode.fields.add(instanceField());
|
|
||||||
}
|
|
||||||
|
|
||||||
addUpvalueFields();
|
|
||||||
|
|
||||||
RunMethod runMethod = new RunMethod(this);
|
|
||||||
|
|
||||||
for (RunMethod.ConstFieldInstance cfi : runMethod.constFields()) {
|
|
||||||
classNode.fields.add(cfi.fieldNode());
|
|
||||||
}
|
|
||||||
|
|
||||||
ConstructorMethod ctor = new ConstructorMethod(this, runMethod);
|
|
||||||
|
|
||||||
classNode.methods.add(ctor.methodNode());
|
|
||||||
classNode.methods.add(new InvokeMethod(this, runMethod).methodNode());
|
|
||||||
classNode.methods.add(new ResumeMethod(this, runMethod).methodNode());
|
|
||||||
classNode.methods.addAll(runMethod.methodNodes());
|
|
||||||
|
|
||||||
if (runMethod.usesSnapshotMethod()) {
|
|
||||||
classNode.methods.add(runMethod.snapshotMethodNode());
|
|
||||||
}
|
|
||||||
|
|
||||||
StaticConstructorMethod staticCtor = new StaticConstructorMethod(this, ctor, runMethod);
|
|
||||||
if (!staticCtor.isEmpty()) {
|
|
||||||
classNode.methods.add(staticCtor.methodNode());
|
|
||||||
}
|
|
||||||
|
|
||||||
classNode.fields.addAll(fields);
|
|
||||||
|
|
||||||
return classNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] classNodeToBytes(ClassNode classNode) {
|
|
||||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
|
||||||
classNode.accept(writer);
|
|
||||||
byte[] bytes = writer.toByteArray();
|
|
||||||
|
|
||||||
// verify bytecode
|
|
||||||
|
|
||||||
if (verifyAndPrint) {
|
|
||||||
ClassReader reader = new ClassReader(bytes);
|
|
||||||
ClassVisitor tracer = new TraceClassVisitor(new PrintWriter(System.out));
|
|
||||||
ClassVisitor checker = new CheckClassAdapter(tracer, true);
|
|
||||||
reader.accept(checker, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompiledClass emit() {
|
|
||||||
ClassNode classNode = classNode();
|
|
||||||
byte[] bytes = classNodeToBytes(classNode);
|
|
||||||
return new CompiledClass(thisClassName(), ByteVector.wrap(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NestedInstanceKind {
|
|
||||||
Pure,
|
|
||||||
Closed,
|
|
||||||
Open
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,118 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.ALOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.PUTFIELD;
|
|
||||||
import static org.objectweb.asm.Opcodes.RETURN;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.Variable;
|
|
||||||
import org.classdump.luna.compiler.ir.UpVar;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.FieldInsnNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.InsnNode;
|
|
||||||
import org.objectweb.asm.tree.LabelNode;
|
|
||||||
import org.objectweb.asm.tree.LocalVariableNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
|
||||||
import org.objectweb.asm.tree.VarInsnNode;
|
|
||||||
|
|
||||||
class ConstructorMethod {
|
|
||||||
|
|
||||||
private final ASMBytecodeEmitter context;
|
|
||||||
private final RunMethod runMethod;
|
|
||||||
|
|
||||||
public ConstructorMethod(ASMBytecodeEmitter context, RunMethod runMethod) {
|
|
||||||
this.context = Objects.requireNonNull(context);
|
|
||||||
this.runMethod = Objects.requireNonNull(runMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type methodType() {
|
|
||||||
Type[] args = new Type[context.fn.upvals().size()];
|
|
||||||
Arrays.fill(args, Type.getType(Variable.class));
|
|
||||||
return Type.getMethodType(Type.VOID_TYPE, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode methodNode() {
|
|
||||||
|
|
||||||
MethodNode node = new MethodNode(
|
|
||||||
ACC_PUBLIC,
|
|
||||||
"<init>",
|
|
||||||
methodType().getDescriptor(),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
InsnList il = node.instructions;
|
|
||||||
|
|
||||||
LabelNode begin = new LabelNode();
|
|
||||||
LabelNode end = new LabelNode();
|
|
||||||
|
|
||||||
node.localVariables.add(
|
|
||||||
new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, begin, end,
|
|
||||||
0));
|
|
||||||
|
|
||||||
il.add(begin);
|
|
||||||
|
|
||||||
// superclass constructor
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0));
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKESPECIAL,
|
|
||||||
context.superClassType().getInternalName(),
|
|
||||||
"<init>",
|
|
||||||
Type.getMethodType(Type.VOID_TYPE).getDescriptor(),
|
|
||||||
false));
|
|
||||||
|
|
||||||
// initialise upvalue fields
|
|
||||||
int idx = 0;
|
|
||||||
for (UpVar uv : context.fn.upvals()) {
|
|
||||||
String name = context.getUpvalueFieldName(uv);
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0)); // this
|
|
||||||
il.add(new VarInsnNode(ALOAD, 1 + idx)); // upvalue #i
|
|
||||||
il.add(new FieldInsnNode(PUTFIELD,
|
|
||||||
context.thisClassType().getInternalName(),
|
|
||||||
name,
|
|
||||||
Type.getDescriptor(Variable.class)));
|
|
||||||
|
|
||||||
node.localVariables.add(
|
|
||||||
new LocalVariableNode(name, Type.getDescriptor(Variable.class), null, begin, end, idx));
|
|
||||||
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// instantiate fields for closures that have no open upvalues
|
|
||||||
for (RunMethod.ClosureFieldInstance cfi : runMethod.closureFields()) {
|
|
||||||
context.fields().add(cfi.fieldNode());
|
|
||||||
il.add(cfi.instantiateInsns());
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(new InsnNode(RETURN));
|
|
||||||
|
|
||||||
il.add(end);
|
|
||||||
|
|
||||||
node.maxStack = 2;
|
|
||||||
node.maxLocals = context.fn.upvals().size() + 1;
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,316 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.AALOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACONST_NULL;
|
|
||||||
import static org.objectweb.asm.Opcodes.ALOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.ANEWARRAY;
|
|
||||||
import static org.objectweb.asm.Opcodes.ARRAYLENGTH;
|
|
||||||
import static org.objectweb.asm.Opcodes.ASTORE;
|
|
||||||
import static org.objectweb.asm.Opcodes.DUP;
|
|
||||||
import static org.objectweb.asm.Opcodes.F_APPEND;
|
|
||||||
import static org.objectweb.asm.Opcodes.F_SAME;
|
|
||||||
import static org.objectweb.asm.Opcodes.GOTO;
|
|
||||||
import static org.objectweb.asm.Opcodes.IFLE;
|
|
||||||
import static org.objectweb.asm.Opcodes.ILOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.ISTORE;
|
|
||||||
import static org.objectweb.asm.Opcodes.ISUB;
|
|
||||||
import static org.objectweb.asm.Opcodes.NEW;
|
|
||||||
import static org.objectweb.asm.Opcodes.RETURN;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.Variable;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.helpers.ASMUtils;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.helpers.VariableMethods;
|
|
||||||
import org.classdump.luna.compiler.ir.Var;
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.FrameNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.InsnNode;
|
|
||||||
import org.objectweb.asm.tree.JumpInsnNode;
|
|
||||||
import org.objectweb.asm.tree.LabelNode;
|
|
||||||
import org.objectweb.asm.tree.LocalVariableNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
|
||||||
import org.objectweb.asm.tree.TableSwitchInsnNode;
|
|
||||||
import org.objectweb.asm.tree.TypeInsnNode;
|
|
||||||
import org.objectweb.asm.tree.VarInsnNode;
|
|
||||||
|
|
||||||
class InvokeMethod {
|
|
||||||
|
|
||||||
private final ASMBytecodeEmitter context;
|
|
||||||
private final RunMethod runMethod;
|
|
||||||
|
|
||||||
public InvokeMethod(ASMBytecodeEmitter context, RunMethod runMethod) {
|
|
||||||
this.context = Objects.requireNonNull(context);
|
|
||||||
this.runMethod = Objects.requireNonNull(runMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode methodNode() {
|
|
||||||
MethodNode node = new MethodNode(
|
|
||||||
ACC_PUBLIC,
|
|
||||||
"invoke",
|
|
||||||
context.invokeMethodType().getDescriptor(),
|
|
||||||
null,
|
|
||||||
runMethod.throwsExceptions());
|
|
||||||
|
|
||||||
InsnList il = node.instructions;
|
|
||||||
List<LocalVariableNode> locals = node.localVariables;
|
|
||||||
|
|
||||||
LabelNode begin = new LabelNode();
|
|
||||||
LabelNode end = new LabelNode();
|
|
||||||
|
|
||||||
int invokeKind = context.kind();
|
|
||||||
|
|
||||||
il.add(begin);
|
|
||||||
|
|
||||||
// a (slotIdx -> paramIdx) map
|
|
||||||
int[] slotParamMap = new int[context.slots.numSlots()];
|
|
||||||
Arrays.fill(slotParamMap, -1);
|
|
||||||
|
|
||||||
for (int paramIdx = 0; paramIdx < context.fn.params().size(); paramIdx++) {
|
|
||||||
int slotIdx = context.slots.slotOf(context.fn.params().get(paramIdx));
|
|
||||||
assert (slotParamMap[slotIdx] == -1);
|
|
||||||
slotParamMap[slotIdx] = paramIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invokeKind > 0) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0)); // this
|
|
||||||
il.add(new VarInsnNode(ALOAD, 1)); // context
|
|
||||||
il.add(ASMUtils.loadInt(0)); // resumption point
|
|
||||||
|
|
||||||
// we have (invokeKind - 1) standalone parameters, mapping them onto numSlots
|
|
||||||
|
|
||||||
for (int paramIdx : slotParamMap) {
|
|
||||||
if (paramIdx < 0) {
|
|
||||||
// slot unused
|
|
||||||
il.add(new InsnNode(ACONST_NULL));
|
|
||||||
} else {
|
|
||||||
// used by the parameter #paramIdx
|
|
||||||
Var param = context.fn.params().get(paramIdx);
|
|
||||||
boolean reified = context.types.isReified(param);
|
|
||||||
|
|
||||||
if (reified) {
|
|
||||||
il.add(new TypeInsnNode(NEW, Type.getInternalName(Variable.class)));
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 2 + paramIdx));
|
|
||||||
|
|
||||||
if (reified) {
|
|
||||||
il.add(VariableMethods.constructor());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// variable number of parameters, encoded in an array at position 2
|
|
||||||
|
|
||||||
int lv_varargsSize = 3;
|
|
||||||
int lv_varargs = 4;
|
|
||||||
|
|
||||||
int numParams = context.numOfParameters();
|
|
||||||
|
|
||||||
if (context.isVararg()) {
|
|
||||||
|
|
||||||
LabelNode l_v_begin = new LabelNode();
|
|
||||||
LabelNode l_v_nonempty = new LabelNode();
|
|
||||||
LabelNode l_v_empty = new LabelNode();
|
|
||||||
LabelNode l_v_done = new LabelNode();
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 2));
|
|
||||||
il.add(new InsnNode(ARRAYLENGTH));
|
|
||||||
|
|
||||||
if (numParams > 0) {
|
|
||||||
il.add(ASMUtils.loadInt(context.numOfParameters()));
|
|
||||||
il.add(new InsnNode(ISUB));
|
|
||||||
}
|
|
||||||
il.add(new VarInsnNode(ISTORE, lv_varargsSize));
|
|
||||||
|
|
||||||
il.add(l_v_begin);
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ILOAD, lv_varargsSize));
|
|
||||||
il.add(new JumpInsnNode(IFLE, l_v_empty));
|
|
||||||
|
|
||||||
// nonempty varargs
|
|
||||||
|
|
||||||
// varargs = new Object[varargsSize];
|
|
||||||
il.add(new VarInsnNode(ILOAD, lv_varargsSize));
|
|
||||||
il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));
|
|
||||||
il.add(new VarInsnNode(ASTORE, lv_varargs));
|
|
||||||
|
|
||||||
il.add(l_v_nonempty);
|
|
||||||
|
|
||||||
// call System.arraycopy(src, srcPos, dest, destPos, len)
|
|
||||||
il.add(new VarInsnNode(ALOAD, 2)); // src
|
|
||||||
il.add(ASMUtils.loadInt(numParams)); // srcPos
|
|
||||||
il.add(new VarInsnNode(ALOAD, lv_varargs)); // dest
|
|
||||||
il.add(ASMUtils.loadInt(0)); // destPos
|
|
||||||
il.add(new VarInsnNode(ILOAD, lv_varargsSize)); // len
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(System.class),
|
|
||||||
"arraycopy",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.getType(Object.class),
|
|
||||||
Type.INT_TYPE,
|
|
||||||
Type.getType(Object.class),
|
|
||||||
Type.INT_TYPE,
|
|
||||||
Type.INT_TYPE),
|
|
||||||
false));
|
|
||||||
|
|
||||||
il.add(new JumpInsnNode(GOTO, l_v_done));
|
|
||||||
|
|
||||||
// empty varargs
|
|
||||||
il.add(l_v_empty);
|
|
||||||
il.add(new FrameNode(F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null));
|
|
||||||
|
|
||||||
// varargs = new Object[0];
|
|
||||||
il.add(ASMUtils.loadInt(0));
|
|
||||||
il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));
|
|
||||||
il.add(new VarInsnNode(ASTORE, lv_varargs));
|
|
||||||
|
|
||||||
il.add(l_v_done);
|
|
||||||
il.add(new FrameNode(F_APPEND, 1, new Object[]{
|
|
||||||
ASMUtils.arrayTypeFor(Object.class).getInternalName()
|
|
||||||
}, 0, null));
|
|
||||||
|
|
||||||
locals.add(new LocalVariableNode("sz", Type.INT_TYPE.getDescriptor(), null, l_v_begin, end,
|
|
||||||
lv_varargsSize));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("varargs", ASMUtils.arrayTypeFor(Object.class).getDescriptor(),
|
|
||||||
null, l_v_nonempty, l_v_empty, lv_varargs));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("varargs", ASMUtils.arrayTypeFor(Object.class).getDescriptor(),
|
|
||||||
null, l_v_done, end, lv_varargs));
|
|
||||||
}
|
|
||||||
|
|
||||||
// load #numOfParameters, mapping them onto #numOfRegisters
|
|
||||||
|
|
||||||
int lv_param_offset = context.isVararg() ? lv_varargs + 1 : lv_varargsSize;
|
|
||||||
|
|
||||||
if (numParams > 0) {
|
|
||||||
// initialise parameter slot variables to null
|
|
||||||
|
|
||||||
for (int i = 0; i < numParams; i++) {
|
|
||||||
LabelNode l = new LabelNode();
|
|
||||||
int lv = lv_param_offset + i;
|
|
||||||
il.add(new InsnNode(ACONST_NULL));
|
|
||||||
il.add(new VarInsnNode(ASTORE, lv));
|
|
||||||
il.add(l);
|
|
||||||
il.add(new FrameNode(F_APPEND, 1, new Object[]{Type.getInternalName(Object.class)}, 0,
|
|
||||||
null));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("arg_" + i, Type.getDescriptor(Object.class), null, l, end,
|
|
||||||
lv));
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert switch for filling parameter slots
|
|
||||||
|
|
||||||
LabelNode[] l_s_table = new LabelNode[numParams + 1];
|
|
||||||
for (int i = 0; i < numParams + 1; i++) {
|
|
||||||
l_s_table[i] = new LabelNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 2));
|
|
||||||
il.add(new InsnNode(ARRAYLENGTH));
|
|
||||||
il.add(new TableSwitchInsnNode(0, numParams, l_s_table[numParams], l_s_table));
|
|
||||||
|
|
||||||
for (int i = numParams; i >= 0; i--) {
|
|
||||||
// length of args is at least i; may assign into param (i - 1)
|
|
||||||
int paramIdx = i - 1;
|
|
||||||
|
|
||||||
il.add(l_s_table[i]);
|
|
||||||
il.add(new FrameNode(F_SAME, 0, null, 0, null));
|
|
||||||
|
|
||||||
if (paramIdx >= 0) {
|
|
||||||
// assign into param #paramIdx
|
|
||||||
il.add(new VarInsnNode(ALOAD, 2));
|
|
||||||
il.add(ASMUtils.loadInt(paramIdx));
|
|
||||||
il.add(new InsnNode(AALOAD));
|
|
||||||
il.add(new VarInsnNode(ASTORE, lv_param_offset + paramIdx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now assemble the run() method invocation, filling in nulls for non-parameter slots
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0)); // this
|
|
||||||
il.add(new VarInsnNode(ALOAD, 1)); // context
|
|
||||||
il.add(ASMUtils.loadInt(0)); // resumption point
|
|
||||||
if (context.isVararg()) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, lv_varargs));
|
|
||||||
}
|
|
||||||
for (int paramIdx : slotParamMap) {
|
|
||||||
if (paramIdx < 0) {
|
|
||||||
// slot not used by a parameter
|
|
||||||
il.add(new InsnNode(ACONST_NULL));
|
|
||||||
} else {
|
|
||||||
// slot is parameter #paramIdx
|
|
||||||
Var param = context.fn.params().get(paramIdx);
|
|
||||||
boolean reified = context.types.isReified(param);
|
|
||||||
|
|
||||||
if (reified) {
|
|
||||||
il.add(new TypeInsnNode(NEW, Type.getInternalName(Variable.class)));
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, lv_param_offset + paramIdx));
|
|
||||||
|
|
||||||
if (reified) {
|
|
||||||
il.add(VariableMethods.constructor());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(runMethod.methodInvokeInsn());
|
|
||||||
|
|
||||||
il.add(new InsnNode(RETURN));
|
|
||||||
il.add(end);
|
|
||||||
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, begin, end,
|
|
||||||
0));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("context", Type.getDescriptor(ExecutionContext.class), null, begin,
|
|
||||||
end, 1));
|
|
||||||
if (invokeKind > 0) {
|
|
||||||
for (int i = 0; i < invokeKind; i++) {
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("arg_" + i, Type.getDescriptor(Object.class), null, begin, end,
|
|
||||||
2 + i));
|
|
||||||
}
|
|
||||||
// TODO: maxLocals, maxStack
|
|
||||||
} else {
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("args", ASMUtils.arrayTypeFor(Object.class).getDescriptor(), null,
|
|
||||||
begin, end, 2));
|
|
||||||
// TODO: maxLocals, maxStack
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.AALOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.ALOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.ASTORE;
|
|
||||||
import static org.objectweb.asm.Opcodes.ATHROW;
|
|
||||||
import static org.objectweb.asm.Opcodes.CHECKCAST;
|
|
||||||
import static org.objectweb.asm.Opcodes.DUP;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.NEW;
|
|
||||||
import static org.objectweb.asm.Opcodes.RETURN;
|
|
||||||
import static org.objectweb.asm.Opcodes.SWAP;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.helpers.ASMUtils;
|
|
||||||
import org.classdump.luna.impl.DefaultSavedState;
|
|
||||||
import org.classdump.luna.impl.NonsuspendableFunctionException;
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.InsnNode;
|
|
||||||
import org.objectweb.asm.tree.LabelNode;
|
|
||||||
import org.objectweb.asm.tree.LocalVariableNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
|
||||||
import org.objectweb.asm.tree.TypeInsnNode;
|
|
||||||
import org.objectweb.asm.tree.VarInsnNode;
|
|
||||||
|
|
||||||
class ResumeMethod {
|
|
||||||
|
|
||||||
private final ASMBytecodeEmitter context;
|
|
||||||
private final RunMethod runMethod;
|
|
||||||
|
|
||||||
public ResumeMethod(ASMBytecodeEmitter context, RunMethod runMethod) {
|
|
||||||
this.context = Objects.requireNonNull(context);
|
|
||||||
this.runMethod = Objects.requireNonNull(runMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode methodNode() {
|
|
||||||
MethodNode node = new MethodNode(
|
|
||||||
ACC_PUBLIC,
|
|
||||||
"resume",
|
|
||||||
Type.getMethodType(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.getType(ExecutionContext.class),
|
|
||||||
Type.getType(Object.class)).getDescriptor(),
|
|
||||||
null,
|
|
||||||
runMethod.throwsExceptions());
|
|
||||||
|
|
||||||
if (runMethod.isResumable()) {
|
|
||||||
InsnList il = node.instructions;
|
|
||||||
List<LocalVariableNode> locals = node.localVariables;
|
|
||||||
|
|
||||||
LabelNode begin = new LabelNode();
|
|
||||||
LabelNode vars = new LabelNode();
|
|
||||||
LabelNode end = new LabelNode();
|
|
||||||
|
|
||||||
il.add(begin);
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 2));
|
|
||||||
il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(DefaultSavedState.class)));
|
|
||||||
|
|
||||||
il.add(vars);
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ASTORE, 3));
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0)); // this
|
|
||||||
il.add(new VarInsnNode(ALOAD, 1)); // context
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 3)); // saved state
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(DefaultSavedState.class),
|
|
||||||
"resumptionPoint",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.INT_TYPE),
|
|
||||||
false
|
|
||||||
)); // resumption point
|
|
||||||
|
|
||||||
// registers
|
|
||||||
if (context.isVararg() || runMethod.numOfRegisters() > 0) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, 3));
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(DefaultSavedState.class),
|
|
||||||
"registers",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
ASMUtils.arrayTypeFor(Object.class)),
|
|
||||||
false
|
|
||||||
));
|
|
||||||
|
|
||||||
// varargs stored as the 0th element
|
|
||||||
int numRegs = runMethod.numOfRegisters() + (context.isVararg() ? 1 : 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < numRegs; i++) {
|
|
||||||
|
|
||||||
// Note: it might be more elegant to use a local variable
|
|
||||||
// to store the array instead of having to perform SWAPs
|
|
||||||
|
|
||||||
if (i + 1 < numRegs) {
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
}
|
|
||||||
il.add(ASMUtils.loadInt(i));
|
|
||||||
il.add(new InsnNode(AALOAD));
|
|
||||||
if (i == 0 && context.isVararg()) {
|
|
||||||
il.add(
|
|
||||||
new TypeInsnNode(CHECKCAST, ASMUtils.arrayTypeFor(Object.class).getInternalName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i + 1 < numRegs) {
|
|
||||||
il.add(new InsnNode(SWAP));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call run(...)
|
|
||||||
il.add(runMethod.methodInvokeInsn());
|
|
||||||
|
|
||||||
il.add(new InsnNode(RETURN));
|
|
||||||
il.add(end);
|
|
||||||
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, begin, end,
|
|
||||||
0));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("context", Type.getDescriptor(ExecutionContext.class), null, begin,
|
|
||||||
end, 1));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("suspendedState", context.savedStateClassType().getDescriptor(),
|
|
||||||
null, begin, end, 2));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("ss", Type.getDescriptor(DefaultSavedState.class), null, vars, end,
|
|
||||||
3));
|
|
||||||
|
|
||||||
// TODO: maxStack, maxLocals
|
|
||||||
node.maxStack = 3 + (runMethod.numOfRegisters() > 0 ? 3 : 0);
|
|
||||||
node.maxLocals = 5;
|
|
||||||
} else {
|
|
||||||
InsnList il = node.instructions;
|
|
||||||
List<LocalVariableNode> locals = node.localVariables;
|
|
||||||
|
|
||||||
LabelNode begin = new LabelNode();
|
|
||||||
LabelNode end = new LabelNode();
|
|
||||||
|
|
||||||
il.add(begin);
|
|
||||||
il.add(new TypeInsnNode(NEW, Type.getInternalName(NonsuspendableFunctionException.class)));
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
il.add(ASMUtils.ctor(NonsuspendableFunctionException.class));
|
|
||||||
il.add(new InsnNode(ATHROW));
|
|
||||||
il.add(end);
|
|
||||||
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, begin, end,
|
|
||||||
0));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("context", Type.getDescriptor(ExecutionContext.class), null, begin,
|
|
||||||
end, 1));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("suspendedState", context.savedStateClassType().getDescriptor(),
|
|
||||||
null, begin, end, 2));
|
|
||||||
|
|
||||||
node.maxStack = 2;
|
|
||||||
node.maxLocals = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,789 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.AALOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.AASTORE;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_FINAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.ACONST_NULL;
|
|
||||||
import static org.objectweb.asm.Opcodes.ALOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.ANEWARRAY;
|
|
||||||
import static org.objectweb.asm.Opcodes.ARETURN;
|
|
||||||
import static org.objectweb.asm.Opcodes.ASTORE;
|
|
||||||
import static org.objectweb.asm.Opcodes.ATHROW;
|
|
||||||
import static org.objectweb.asm.Opcodes.CHECKCAST;
|
|
||||||
import static org.objectweb.asm.Opcodes.DUP;
|
|
||||||
import static org.objectweb.asm.Opcodes.F_APPEND;
|
|
||||||
import static org.objectweb.asm.Opcodes.F_SAME;
|
|
||||||
import static org.objectweb.asm.Opcodes.GETSTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.GOTO;
|
|
||||||
import static org.objectweb.asm.Opcodes.IAND;
|
|
||||||
import static org.objectweb.asm.Opcodes.IFNULL;
|
|
||||||
import static org.objectweb.asm.Opcodes.ILOAD;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.ISTORE;
|
|
||||||
import static org.objectweb.asm.Opcodes.ISUB;
|
|
||||||
import static org.objectweb.asm.Opcodes.IUSHR;
|
|
||||||
import static org.objectweb.asm.Opcodes.NEW;
|
|
||||||
import static org.objectweb.asm.Opcodes.PUTSTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.RETURN;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.compiler.gen.CodeSegmenter;
|
|
||||||
import org.classdump.luna.compiler.gen.SegmentedCode;
|
|
||||||
import org.classdump.luna.compiler.gen.asm.helpers.ASMUtils;
|
|
||||||
import org.classdump.luna.compiler.ir.BasicBlock;
|
|
||||||
import org.classdump.luna.compiler.ir.Label;
|
|
||||||
import org.classdump.luna.impl.DefaultSavedState;
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext;
|
|
||||||
import org.classdump.luna.runtime.ResolvedControlThrowable;
|
|
||||||
import org.classdump.luna.runtime.Resumable;
|
|
||||||
import org.classdump.luna.runtime.UnresolvedControlThrowable;
|
|
||||||
import org.objectweb.asm.Opcodes;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.FieldInsnNode;
|
|
||||||
import org.objectweb.asm.tree.FieldNode;
|
|
||||||
import org.objectweb.asm.tree.FrameNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.InsnNode;
|
|
||||||
import org.objectweb.asm.tree.JumpInsnNode;
|
|
||||||
import org.objectweb.asm.tree.LabelNode;
|
|
||||||
import org.objectweb.asm.tree.LocalVariableNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
|
||||||
import org.objectweb.asm.tree.TableSwitchInsnNode;
|
|
||||||
import org.objectweb.asm.tree.TryCatchBlockNode;
|
|
||||||
import org.objectweb.asm.tree.TypeInsnNode;
|
|
||||||
import org.objectweb.asm.tree.VarInsnNode;
|
|
||||||
|
|
||||||
class RunMethod {
|
|
||||||
|
|
||||||
public static final int ST_SHIFT_SEGMENT = 24;
|
|
||||||
public static final int ST_SHIFT_LABELIDX = 16;
|
|
||||||
public final int LV_CONTEXT = 1;
|
|
||||||
public final int LV_RESUME = 2;
|
|
||||||
public final int LV_VARARGS = 3; // index of the varargs argument, if present
|
|
||||||
private final ASMBytecodeEmitter context;
|
|
||||||
private final List<MethodNode> methodNodes;
|
|
||||||
private final boolean resumable;
|
|
||||||
|
|
||||||
private final List<ClosureFieldInstance> closureFields;
|
|
||||||
private final List<ConstFieldInstance> constFields;
|
|
||||||
|
|
||||||
public RunMethod(ASMBytecodeEmitter context) {
|
|
||||||
this.context = Objects.requireNonNull(context);
|
|
||||||
|
|
||||||
final SegmentedCode segmentedCode = CodeSegmenter.segment(
|
|
||||||
context.fn.code(),
|
|
||||||
context.compilerSettings.nodeSizeLimit());
|
|
||||||
|
|
||||||
this.methodNodes = new ArrayList<>();
|
|
||||||
|
|
||||||
this.closureFields = new ArrayList<>();
|
|
||||||
this.constFields = new ArrayList<>();
|
|
||||||
|
|
||||||
if (segmentedCode.isSingleton()) {
|
|
||||||
// as before
|
|
||||||
BytecodeEmitVisitor visitor = new BytecodeEmitVisitor(
|
|
||||||
context, this, context.slots, context.types, closureFields, constFields, -1,
|
|
||||||
new LabelResolver() {
|
|
||||||
@Override
|
|
||||||
public boolean isLocalLabel(Label l) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int labelStateIndex(Label l) {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.methodNodes.add(emitSingletonRunMethod(visitor, segmentedCode.segments().get(0)));
|
|
||||||
this.resumable = visitor.isResumable();
|
|
||||||
} else {
|
|
||||||
// split up into multiple segments
|
|
||||||
|
|
||||||
boolean resumable = false;
|
|
||||||
for (int i = 0; i < segmentedCode.segments().size(); i++) {
|
|
||||||
|
|
||||||
final int thisSegmentIdx = i;
|
|
||||||
|
|
||||||
BytecodeEmitVisitor visitor = new BytecodeEmitVisitor(
|
|
||||||
context, this, context.slots, context.types, closureFields, constFields, i,
|
|
||||||
new LabelResolver() {
|
|
||||||
@Override
|
|
||||||
public boolean isLocalLabel(Label l) {
|
|
||||||
return segmentedCode.labelEntry(l).segmentIdx == thisSegmentIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int labelStateIndex(Label l) {
|
|
||||||
return labelStateIdx(segmentedCode.labelEntry(l));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.methodNodes
|
|
||||||
.add(emitSegmentedSubRunMethod(i, visitor, segmentedCode.segments().get(i)));
|
|
||||||
resumable |= visitor.isResumable();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resumable = resumable;
|
|
||||||
|
|
||||||
this.methodNodes.add(emitSegmentedRunMethod(segmentedCode.segments().size()));
|
|
||||||
|
|
||||||
// throw new UnsupportedOperationException(); // TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int labelStateIdx(SegmentedCode.LabelEntry le) {
|
|
||||||
return (le.segmentIdx << ST_SHIFT_SEGMENT) | (le.idx << ST_SHIFT_LABELIDX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int numOfRegisters() {
|
|
||||||
return context.slots.numSlots();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int slotOffset() {
|
|
||||||
return context.isVararg() ? LV_VARARGS + 1 : LV_VARARGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isResumable() {
|
|
||||||
return resumable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] throwsExceptions() {
|
|
||||||
return new String[]{Type.getInternalName(ResolvedControlThrowable.class)};
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean usesSnapshotMethod() {
|
|
||||||
return isResumable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String snapshotMethodName() {
|
|
||||||
return "snapshot";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type snapshotMethodType() {
|
|
||||||
ArrayList<Type> args = new ArrayList<>();
|
|
||||||
|
|
||||||
args.add(Type.INT_TYPE);
|
|
||||||
if (context.isVararg()) {
|
|
||||||
args.add(ASMUtils.arrayTypeFor(Object.class));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < numOfRegisters(); i++) {
|
|
||||||
args.add(Type.getType(Object.class));
|
|
||||||
}
|
|
||||||
return Type.getMethodType(context.savedStateClassType(), args.toArray(new Type[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodInsnNode snapshotMethodInvokeInsn() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESPECIAL,
|
|
||||||
context.thisClassType().getInternalName(),
|
|
||||||
snapshotMethodName(),
|
|
||||||
snapshotMethodType().getDescriptor(),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode snapshotMethodNode() {
|
|
||||||
MethodNode node = new MethodNode(
|
|
||||||
ACC_PRIVATE,
|
|
||||||
snapshotMethodName(),
|
|
||||||
snapshotMethodType().getDescriptor(),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
InsnList il = node.instructions;
|
|
||||||
LabelNode begin = new LabelNode();
|
|
||||||
LabelNode end = new LabelNode();
|
|
||||||
|
|
||||||
il.add(begin);
|
|
||||||
|
|
||||||
il.add(new TypeInsnNode(NEW, Type.getInternalName(DefaultSavedState.class)));
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
|
|
||||||
// resumption point
|
|
||||||
il.add(new VarInsnNode(ILOAD, 1));
|
|
||||||
|
|
||||||
// registers
|
|
||||||
int numRegs = numOfRegisters() + (context.isVararg() ? 1 : 0);
|
|
||||||
int regOffset = context.isVararg() ? 3 : 2;
|
|
||||||
|
|
||||||
il.add(ASMUtils.loadInt(numRegs));
|
|
||||||
il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));
|
|
||||||
{
|
|
||||||
for (int i = 0; i < numRegs; i++) {
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
il.add(ASMUtils.loadInt(i));
|
|
||||||
il.add(new VarInsnNode(ALOAD, 2 + i));
|
|
||||||
il.add(new InsnNode(AASTORE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(ASMUtils.ctor(
|
|
||||||
Type.getType(DefaultSavedState.class),
|
|
||||||
Type.INT_TYPE,
|
|
||||||
ASMUtils.arrayTypeFor(Object.class)));
|
|
||||||
|
|
||||||
il.add(new InsnNode(ARETURN));
|
|
||||||
|
|
||||||
il.add(end);
|
|
||||||
|
|
||||||
List<LocalVariableNode> locals = node.localVariables;
|
|
||||||
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, begin, end,
|
|
||||||
0));
|
|
||||||
locals.add(new LocalVariableNode("rp", Type.INT_TYPE.getDescriptor(), null, begin, end, 1));
|
|
||||||
if (context.isVararg()) {
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("varargs", ASMUtils.arrayTypeFor(Object.class).getDescriptor(),
|
|
||||||
null, begin, end, 2));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < numOfRegisters(); i++) {
|
|
||||||
locals.add(new LocalVariableNode("r_" + i, Type.getDescriptor(Object.class), null, begin, end,
|
|
||||||
regOffset + i));
|
|
||||||
}
|
|
||||||
|
|
||||||
node.maxLocals = 2 + numOfRegisters();
|
|
||||||
node.maxStack = 4 + 3; // 4 to get register array at top, +3 to add element to it
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String methodName() {
|
|
||||||
return "run";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type methodType(Type returnType) {
|
|
||||||
ArrayList<Type> args = new ArrayList<>();
|
|
||||||
|
|
||||||
args.add(Type.getType(ExecutionContext.class));
|
|
||||||
args.add(Type.INT_TYPE);
|
|
||||||
if (context.isVararg()) {
|
|
||||||
args.add(ASMUtils.arrayTypeFor(Object.class));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < numOfRegisters(); i++) {
|
|
||||||
args.add(Type.getType(Object.class));
|
|
||||||
}
|
|
||||||
return Type.getMethodType(returnType, args.toArray(new Type[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type methodType() {
|
|
||||||
return methodType(Type.VOID_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type subMethodType() {
|
|
||||||
return methodType(context.savedStateClassType());
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbstractInsnNode methodInvokeInsn() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESPECIAL,
|
|
||||||
context.thisClassType().getInternalName(),
|
|
||||||
methodName(),
|
|
||||||
methodType().getDescriptor(),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private InsnList errorState(LabelNode label) {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
il.add(label);
|
|
||||||
il.add(ASMUtils.frameSame());
|
|
||||||
il.add(new TypeInsnNode(NEW, Type.getInternalName(IllegalStateException.class)));
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
il.add(ASMUtils.ctor(IllegalStateException.class));
|
|
||||||
il.add(new InsnNode(ATHROW));
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
private InsnList dispatchTable(List<LabelNode> extLabels, List<LabelNode> resumptionLabels,
|
|
||||||
LabelNode errorStateLabel) {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
|
|
||||||
assert (!extLabels.isEmpty());
|
|
||||||
|
|
||||||
ArrayList<LabelNode> labels = new ArrayList<>();
|
|
||||||
labels.addAll(extLabels);
|
|
||||||
labels.addAll(resumptionLabels);
|
|
||||||
LabelNode[] labelArray = labels.toArray(new LabelNode[labels.size()]);
|
|
||||||
|
|
||||||
int min = 1 - extLabels.size();
|
|
||||||
int max = resumptionLabels.size();
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ILOAD, LV_RESUME));
|
|
||||||
il.add(new TableSwitchInsnNode(min, max, errorStateLabel, labelArray));
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
InsnList createSnapshot() {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0)); // this
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0));
|
|
||||||
il.add(new VarInsnNode(ILOAD, LV_RESUME));
|
|
||||||
if (context.isVararg()) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, LV_VARARGS));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < numOfRegisters(); i++) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, slotOffset() + i));
|
|
||||||
}
|
|
||||||
il.add(snapshotMethodInvokeInsn());
|
|
||||||
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InsnList resumptionHandler(LabelNode label) {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
|
|
||||||
il.add(label);
|
|
||||||
il.add(ASMUtils.frameSame1(UnresolvedControlThrowable.class));
|
|
||||||
|
|
||||||
il.add(createSnapshot());
|
|
||||||
|
|
||||||
// register snapshot with the control exception
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(UnresolvedControlThrowable.class),
|
|
||||||
"resolve",
|
|
||||||
Type.getMethodType(
|
|
||||||
Type.getType(ResolvedControlThrowable.class),
|
|
||||||
Type.getType(Resumable.class),
|
|
||||||
Type.getType(Object.class)).getDescriptor(),
|
|
||||||
false));
|
|
||||||
|
|
||||||
// rethrow
|
|
||||||
il.add(new InsnNode(ATHROW));
|
|
||||||
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ClosureFieldInstance> closureFields() {
|
|
||||||
return closureFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ConstFieldInstance> constFields() {
|
|
||||||
return constFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<LocalVariableNode> baseLocals(LabelNode l_begin, LabelNode l_end) {
|
|
||||||
List<LocalVariableNode> locals = new ArrayList<>();
|
|
||||||
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, l_begin, l_end,
|
|
||||||
0));
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("context", Type.getDescriptor(ExecutionContext.class), null, l_begin,
|
|
||||||
l_end, LV_CONTEXT));
|
|
||||||
locals.add(new LocalVariableNode("rp", Type.INT_TYPE.getDescriptor(), null, l_begin, l_end,
|
|
||||||
LV_RESUME));
|
|
||||||
|
|
||||||
if (context.isVararg()) {
|
|
||||||
locals.add(new LocalVariableNode(
|
|
||||||
"varargs",
|
|
||||||
ASMUtils.arrayTypeFor(Object.class).getDescriptor(),
|
|
||||||
null,
|
|
||||||
l_begin,
|
|
||||||
l_end,
|
|
||||||
LV_VARARGS
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < numOfRegisters(); i++) {
|
|
||||||
locals.add(
|
|
||||||
new LocalVariableNode("s_" + i, Type.getDescriptor(Object.class), null, l_begin, l_end,
|
|
||||||
slotOffset() + i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return locals;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addLocals(MethodNode node, LabelNode l_begin, LabelNode l_end,
|
|
||||||
BytecodeEmitVisitor visitor) {
|
|
||||||
List<LocalVariableNode> locals = node.localVariables;
|
|
||||||
locals.addAll(baseLocals(l_begin, l_end));
|
|
||||||
locals.addAll(visitor.locals());
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodNode emitRunMethod(String methodName, Type returnType, BytecodeEmitVisitor visitor,
|
|
||||||
List<BasicBlock> blocks, boolean sub) {
|
|
||||||
MethodNode node = new MethodNode(
|
|
||||||
ACC_PRIVATE,
|
|
||||||
methodName,
|
|
||||||
methodType(returnType).getDescriptor(),
|
|
||||||
null,
|
|
||||||
throwsExceptions());
|
|
||||||
|
|
||||||
InsnList insns = node.instructions;
|
|
||||||
|
|
||||||
LabelNode l_begin = new LabelNode();
|
|
||||||
LabelNode l_end = new LabelNode();
|
|
||||||
|
|
||||||
visitor.visitBlocks(blocks);
|
|
||||||
|
|
||||||
InsnList prefix = new InsnList();
|
|
||||||
InsnList suffix = new InsnList();
|
|
||||||
|
|
||||||
final LabelNode l_head;
|
|
||||||
final List<LabelNode> els = new ArrayList<>();
|
|
||||||
if (sub) {
|
|
||||||
assert (!blocks.isEmpty());
|
|
||||||
for (int i = blocks.size() - 1; i >= 0; i--) {
|
|
||||||
BasicBlock blk = blocks.get(i);
|
|
||||||
LabelNode l = visitor.labels.get(blk.label());
|
|
||||||
assert (l != null);
|
|
||||||
els.add(l);
|
|
||||||
}
|
|
||||||
l_head = visitor.labels.get(blocks.get(0).label());
|
|
||||||
} else {
|
|
||||||
l_head = new LabelNode();
|
|
||||||
els.add(l_head);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert (l_head != null);
|
|
||||||
|
|
||||||
if (visitor.isResumable()) {
|
|
||||||
LabelNode l_error_state = new LabelNode();
|
|
||||||
LabelNode l_handler_begin = new LabelNode();
|
|
||||||
|
|
||||||
List<LabelNode> rls = visitor.resumptionLabels();
|
|
||||||
|
|
||||||
assert (!rls.isEmpty() || !els.isEmpty());
|
|
||||||
|
|
||||||
prefix.add(dispatchTable(els, rls, l_error_state));
|
|
||||||
|
|
||||||
final LabelNode l_entry = l_head;
|
|
||||||
|
|
||||||
if (!sub) {
|
|
||||||
prefix.add(l_entry);
|
|
||||||
prefix.add(ASMUtils.frameSame());
|
|
||||||
}
|
|
||||||
|
|
||||||
suffix.add(errorState(l_error_state));
|
|
||||||
suffix.add(resumptionHandler(l_handler_begin));
|
|
||||||
|
|
||||||
node.tryCatchBlocks.add(new TryCatchBlockNode(l_entry, l_error_state, l_handler_begin,
|
|
||||||
Type.getInternalName(UnresolvedControlThrowable.class)));
|
|
||||||
}
|
|
||||||
|
|
||||||
insns.add(l_begin);
|
|
||||||
insns.add(prefix);
|
|
||||||
insns.add(visitor.instructions());
|
|
||||||
insns.add(suffix);
|
|
||||||
insns.add(l_end);
|
|
||||||
|
|
||||||
addLocals(node, l_begin, l_end, visitor);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodNode emitSingletonRunMethod(BytecodeEmitVisitor visitor, List<BasicBlock> blocks) {
|
|
||||||
return emitRunMethod(methodName(), Type.VOID_TYPE, visitor, blocks, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String subRunMethodName(int segmentIdx) {
|
|
||||||
return "run_" + segmentIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodNode emitSegmentedSubRunMethod(int segmentIdx, BytecodeEmitVisitor visitor,
|
|
||||||
List<BasicBlock> blocks) {
|
|
||||||
return emitRunMethod(subRunMethodName(segmentIdx), context.savedStateClassType(), visitor,
|
|
||||||
blocks, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodNode emitSegmentedRunMethod(int numOfSegments) {
|
|
||||||
MethodNode node = new MethodNode(
|
|
||||||
ACC_PRIVATE,
|
|
||||||
methodName(),
|
|
||||||
methodType().getDescriptor(),
|
|
||||||
null,
|
|
||||||
throwsExceptions());
|
|
||||||
|
|
||||||
InsnList il = node.instructions;
|
|
||||||
|
|
||||||
int lvOffset = slotOffset() + numOfRegisters();
|
|
||||||
|
|
||||||
int lv_rpp = lvOffset + 0;
|
|
||||||
int lv_methodIdx = lvOffset + 1;
|
|
||||||
int lv_jmpIdx = lvOffset + 2;
|
|
||||||
int lv_stateIdx = lvOffset + 3;
|
|
||||||
int lv_savedState = lvOffset + 4;
|
|
||||||
|
|
||||||
LabelNode l_top = new LabelNode();
|
|
||||||
LabelNode l_ret = new LabelNode();
|
|
||||||
LabelNode l_end = new LabelNode();
|
|
||||||
|
|
||||||
LabelNode l_rpp = new LabelNode();
|
|
||||||
LabelNode l_methodIdx = new LabelNode();
|
|
||||||
LabelNode l_jmpIdx = new LabelNode();
|
|
||||||
LabelNode l_stateIdx = new LabelNode();
|
|
||||||
LabelNode l_savedState = new LabelNode();
|
|
||||||
|
|
||||||
il.add(l_top);
|
|
||||||
il.add(new FrameNode(F_SAME, 0, null, 0, null));
|
|
||||||
|
|
||||||
// rpp = rp & ((1 << ST_SHIFT_SEGMENT) - 1)
|
|
||||||
il.add(new VarInsnNode(ILOAD, LV_RESUME));
|
|
||||||
il.add(ASMUtils.loadInt((1 << ST_SHIFT_SEGMENT) - 1));
|
|
||||||
il.add(new InsnNode(IAND));
|
|
||||||
il.add(new VarInsnNode(ISTORE, lv_rpp));
|
|
||||||
il.add(l_rpp);
|
|
||||||
il.add(new FrameNode(F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null));
|
|
||||||
|
|
||||||
// methodIdx = rp >>> ST_SHIFT_SEGMENT
|
|
||||||
il.add(new VarInsnNode(ILOAD, LV_RESUME));
|
|
||||||
il.add(ASMUtils.loadInt(ST_SHIFT_SEGMENT));
|
|
||||||
il.add(new InsnNode(IUSHR));
|
|
||||||
il.add(new VarInsnNode(ISTORE, lv_methodIdx));
|
|
||||||
il.add(l_methodIdx);
|
|
||||||
il.add(new FrameNode(F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null));
|
|
||||||
|
|
||||||
// jmpIdx = rpp >>> ST_SHIFT_LABELIDX
|
|
||||||
il.add(new VarInsnNode(ILOAD, lv_rpp));
|
|
||||||
il.add(ASMUtils.loadInt(ST_SHIFT_LABELIDX));
|
|
||||||
il.add(new InsnNode(IUSHR));
|
|
||||||
il.add(new VarInsnNode(ISTORE, lv_jmpIdx));
|
|
||||||
il.add(l_jmpIdx);
|
|
||||||
il.add(new FrameNode(F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null));
|
|
||||||
|
|
||||||
// stateIdx = (rp & ((1 << ST_SHIFT_LABELIDX) - 1)) - jmpIdx
|
|
||||||
il.add(new VarInsnNode(ILOAD, LV_RESUME));
|
|
||||||
il.add(ASMUtils.loadInt((1 << ST_SHIFT_LABELIDX) - 1));
|
|
||||||
il.add(new InsnNode(IAND));
|
|
||||||
il.add(new VarInsnNode(ILOAD, lv_jmpIdx));
|
|
||||||
il.add(new InsnNode(ISUB));
|
|
||||||
il.add(new VarInsnNode(ISTORE, lv_stateIdx));
|
|
||||||
il.add(l_stateIdx);
|
|
||||||
il.add(new FrameNode(F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null));
|
|
||||||
|
|
||||||
// savedState = null
|
|
||||||
il.add(new InsnNode(ACONST_NULL));
|
|
||||||
il.add(new VarInsnNode(ASTORE, lv_savedState));
|
|
||||||
il.add(l_savedState);
|
|
||||||
il.add(
|
|
||||||
new FrameNode(F_APPEND, 1, new Object[]{context.savedStateClassType().getInternalName()}, 0,
|
|
||||||
null));
|
|
||||||
|
|
||||||
// switch on methodIdx
|
|
||||||
|
|
||||||
LabelNode l_after = new LabelNode();
|
|
||||||
|
|
||||||
LabelNode l_error = new LabelNode();
|
|
||||||
LabelNode[] l_invokes = new LabelNode[numOfSegments];
|
|
||||||
for (int i = 0; i < numOfSegments; i++) {
|
|
||||||
l_invokes[i] = new LabelNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ILOAD, lv_methodIdx));
|
|
||||||
il.add(new TableSwitchInsnNode(0, numOfSegments - 1, l_error, l_invokes));
|
|
||||||
|
|
||||||
for (int i = 0; i < numOfSegments; i++) {
|
|
||||||
il.add(l_invokes[i]);
|
|
||||||
il.add(new FrameNode(F_SAME, 0, null, 0, null));
|
|
||||||
// push arguments to stack
|
|
||||||
il.add(new VarInsnNode(ALOAD, 0));
|
|
||||||
il.add(new VarInsnNode(ALOAD, LV_CONTEXT));
|
|
||||||
il.add(new VarInsnNode(ILOAD, lv_stateIdx)); // pass stateIdx to the sub-method
|
|
||||||
if (context.isVararg()) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, LV_VARARGS));
|
|
||||||
}
|
|
||||||
for (int j = 0; j < numOfRegisters(); j++) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, slotOffset() + j));
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(new MethodInsnNode(INVOKESPECIAL,
|
|
||||||
context.thisClassType().getInternalName(),
|
|
||||||
subRunMethodName(i),
|
|
||||||
subMethodType().getDescriptor(),
|
|
||||||
false));
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ASTORE, lv_savedState));
|
|
||||||
il.add(new JumpInsnNode(GOTO, l_after));
|
|
||||||
}
|
|
||||||
|
|
||||||
// error state
|
|
||||||
il.add(errorState(l_error));
|
|
||||||
|
|
||||||
il.add(l_after);
|
|
||||||
il.add(new FrameNode(F_SAME, 0, null, 0, null));
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, lv_savedState));
|
|
||||||
il.add(new JumpInsnNode(IFNULL, l_ret)); // savedState == null ?
|
|
||||||
|
|
||||||
// continuing: savedState != null
|
|
||||||
|
|
||||||
// FIXME: taken from ResumeMethod -- beware of code duplication!
|
|
||||||
|
|
||||||
il.add(new VarInsnNode(ALOAD, lv_savedState)); // saved state
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(DefaultSavedState.class),
|
|
||||||
"resumptionPoint",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.INT_TYPE),
|
|
||||||
false
|
|
||||||
)); // resumption point
|
|
||||||
il.add(new VarInsnNode(ISTORE, LV_RESUME));
|
|
||||||
|
|
||||||
// registers
|
|
||||||
if (context.isVararg() || numOfRegisters() > 0) {
|
|
||||||
il.add(new VarInsnNode(ALOAD, lv_savedState));
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(DefaultSavedState.class),
|
|
||||||
"registers",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
ASMUtils.arrayTypeFor(Object.class)),
|
|
||||||
false
|
|
||||||
));
|
|
||||||
|
|
||||||
int numRegs = numOfRegisters() + (context.isVararg() ? 1 : 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < numRegs; i++) {
|
|
||||||
if (i + 1 < numRegs) {
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
}
|
|
||||||
il.add(ASMUtils.loadInt(i));
|
|
||||||
il.add(new InsnNode(AALOAD));
|
|
||||||
if (i == 0 && context.isVararg()) {
|
|
||||||
il.add(
|
|
||||||
new TypeInsnNode(CHECKCAST, ASMUtils.arrayTypeFor(Object.class).getInternalName()));
|
|
||||||
}
|
|
||||||
il.add(new VarInsnNode(ASTORE, LV_VARARGS + i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop back to the beginning
|
|
||||||
il.add(new JumpInsnNode(GOTO, l_top));
|
|
||||||
|
|
||||||
// got a null, that's the end
|
|
||||||
il.add(l_ret);
|
|
||||||
il.add(new FrameNode(F_SAME, 0, null, 0, null));
|
|
||||||
il.add(new InsnNode(RETURN));
|
|
||||||
|
|
||||||
il.add(l_end);
|
|
||||||
|
|
||||||
// add local variables
|
|
||||||
node.localVariables.addAll(baseLocals(l_top, l_end));
|
|
||||||
node.localVariables.add(
|
|
||||||
new LocalVariableNode("rpp", Type.INT_TYPE.getDescriptor(), null, l_rpp, l_ret, lv_rpp));
|
|
||||||
node.localVariables.add(
|
|
||||||
new LocalVariableNode("methodIdx", Type.INT_TYPE.getDescriptor(), null, l_methodIdx, l_ret,
|
|
||||||
lv_methodIdx));
|
|
||||||
node.localVariables.add(
|
|
||||||
new LocalVariableNode("jmpIdx", Type.INT_TYPE.getDescriptor(), null, l_jmpIdx, l_ret,
|
|
||||||
lv_jmpIdx));
|
|
||||||
node.localVariables.add(
|
|
||||||
new LocalVariableNode("stateIdx", Type.INT_TYPE.getDescriptor(), null, l_stateIdx, l_ret,
|
|
||||||
lv_stateIdx));
|
|
||||||
node.localVariables.add(
|
|
||||||
new LocalVariableNode("savedState", context.savedStateClassType().getDescriptor(), null,
|
|
||||||
l_savedState, l_ret, lv_savedState));
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MethodNode> methodNodes() {
|
|
||||||
return methodNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LabelResolver {
|
|
||||||
|
|
||||||
boolean isLocalLabel(Label l);
|
|
||||||
|
|
||||||
int labelStateIndex(Label l);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ClosureFieldInstance {
|
|
||||||
|
|
||||||
private final FieldNode fieldNode;
|
|
||||||
private final InsnList instantiateInsns;
|
|
||||||
|
|
||||||
public ClosureFieldInstance(FieldNode fieldNode, InsnList instantiateInsns) {
|
|
||||||
this.fieldNode = Objects.requireNonNull(fieldNode);
|
|
||||||
this.instantiateInsns = Objects.requireNonNull(instantiateInsns);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FieldNode fieldNode() {
|
|
||||||
return fieldNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InsnList instantiateInsns() {
|
|
||||||
return instantiateInsns;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract static class ConstFieldInstance {
|
|
||||||
|
|
||||||
private final Object value;
|
|
||||||
private final String fieldName;
|
|
||||||
private final Type ownerClassType;
|
|
||||||
private final Type fieldType;
|
|
||||||
|
|
||||||
public ConstFieldInstance(Object value, String fieldName, Type ownerClassType, Type fieldType) {
|
|
||||||
this.value = Objects.requireNonNull(value);
|
|
||||||
this.fieldName = Objects.requireNonNull(fieldName);
|
|
||||||
this.ownerClassType = Objects.requireNonNull(ownerClassType);
|
|
||||||
this.fieldType = Objects.requireNonNull(fieldType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object value() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FieldNode fieldNode() {
|
|
||||||
return new FieldNode(
|
|
||||||
ACC_PRIVATE + ACC_STATIC + ACC_FINAL,
|
|
||||||
fieldName,
|
|
||||||
fieldType.getDescriptor(),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void doInstantiate(InsnList il);
|
|
||||||
|
|
||||||
public InsnList instantiateInsns() {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
doInstantiate(il);
|
|
||||||
il.add(new FieldInsnNode(
|
|
||||||
PUTSTATIC,
|
|
||||||
ownerClassType.getInternalName(),
|
|
||||||
fieldName,
|
|
||||||
fieldType.getDescriptor()));
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InsnList accessInsns() {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
il.add(new FieldInsnNode(
|
|
||||||
GETSTATIC,
|
|
||||||
ownerClassType.getInternalName(),
|
|
||||||
fieldName,
|
|
||||||
fieldType.getDescriptor()));
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.DUP;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.NEW;
|
|
||||||
import static org.objectweb.asm.Opcodes.PUTSTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.RETURN;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.objectweb.asm.tree.FieldInsnNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.InsnNode;
|
|
||||||
import org.objectweb.asm.tree.LabelNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodNode;
|
|
||||||
import org.objectweb.asm.tree.TypeInsnNode;
|
|
||||||
|
|
||||||
class StaticConstructorMethod {
|
|
||||||
|
|
||||||
private final ASMBytecodeEmitter context;
|
|
||||||
private final ConstructorMethod ctorMethod;
|
|
||||||
private final RunMethod runMethod;
|
|
||||||
|
|
||||||
public StaticConstructorMethod(ASMBytecodeEmitter context, ConstructorMethod ctorMethod,
|
|
||||||
RunMethod runMethod) {
|
|
||||||
this.context = Objects.requireNonNull(context);
|
|
||||||
this.ctorMethod = Objects.requireNonNull(ctorMethod);
|
|
||||||
this.runMethod = Objects.requireNonNull(runMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return context.hasUpvalues() && runMethod.constFields().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodNode methodNode() {
|
|
||||||
|
|
||||||
MethodNode node = new MethodNode(ACC_STATIC, "<clinit>", "()V", null, null);
|
|
||||||
|
|
||||||
InsnList il = node.instructions;
|
|
||||||
|
|
||||||
LabelNode begin = new LabelNode();
|
|
||||||
LabelNode end = new LabelNode();
|
|
||||||
|
|
||||||
il.add(begin);
|
|
||||||
|
|
||||||
if (!context.hasUpvalues()) {
|
|
||||||
il.add(new TypeInsnNode(NEW, context.thisClassType().getInternalName()));
|
|
||||||
il.add(new InsnNode(DUP));
|
|
||||||
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKESPECIAL,
|
|
||||||
context.thisClassType().getInternalName(),
|
|
||||||
"<init>",
|
|
||||||
ctorMethod.methodType().getDescriptor(),
|
|
||||||
false));
|
|
||||||
|
|
||||||
il.add(new FieldInsnNode(
|
|
||||||
PUTSTATIC,
|
|
||||||
context.thisClassType().getInternalName(),
|
|
||||||
context.instanceFieldName(),
|
|
||||||
context.thisClassType().getDescriptor()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!runMethod.constFields().isEmpty()) {
|
|
||||||
for (RunMethod.ConstFieldInstance cfi : runMethod.constFields()) {
|
|
||||||
il.add(cfi.instantiateInsns());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
il.add(new InsnNode(RETURN));
|
|
||||||
il.add(end);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.BIPUSH;
|
|
||||||
import static org.objectweb.asm.Opcodes.CHECKCAST;
|
|
||||||
import static org.objectweb.asm.Opcodes.DCONST_0;
|
|
||||||
import static org.objectweb.asm.Opcodes.DCONST_1;
|
|
||||||
import static org.objectweb.asm.Opcodes.F_SAME;
|
|
||||||
import static org.objectweb.asm.Opcodes.F_SAME1;
|
|
||||||
import static org.objectweb.asm.Opcodes.ICONST_0;
|
|
||||||
import static org.objectweb.asm.Opcodes.ICONST_1;
|
|
||||||
import static org.objectweb.asm.Opcodes.ICONST_2;
|
|
||||||
import static org.objectweb.asm.Opcodes.ICONST_3;
|
|
||||||
import static org.objectweb.asm.Opcodes.ICONST_4;
|
|
||||||
import static org.objectweb.asm.Opcodes.ICONST_5;
|
|
||||||
import static org.objectweb.asm.Opcodes.ICONST_M1;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.LCONST_0;
|
|
||||||
import static org.objectweb.asm.Opcodes.LCONST_1;
|
|
||||||
import static org.objectweb.asm.Opcodes.SIPUSH;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.FrameNode;
|
|
||||||
import org.objectweb.asm.tree.InsnNode;
|
|
||||||
import org.objectweb.asm.tree.IntInsnNode;
|
|
||||||
import org.objectweb.asm.tree.LdcInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.TypeInsnNode;
|
|
||||||
|
|
||||||
public abstract class ASMUtils {
|
|
||||||
|
|
||||||
private ASMUtils() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Type arrayTypeFor(Class<?> clazz) {
|
|
||||||
return arrayTypeFor(clazz, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Type arrayTypeFor(Class<?> clazz, int dimensions) {
|
|
||||||
Objects.requireNonNull(clazz);
|
|
||||||
if (dimensions < 1) {
|
|
||||||
throw new IllegalArgumentException("dimensions must be at least 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
String prefix = "[";
|
|
||||||
for (int i = 1; i < dimensions; i++) {
|
|
||||||
prefix = prefix + "[";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Type.getType(prefix + Type.getType(clazz).getDescriptor());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Type typeForClassName(String className) {
|
|
||||||
Objects.requireNonNull(className);
|
|
||||||
return Type.getType("L" + className.replace(".", "/") + ";");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Type[] fillTypes(Type t, int n) {
|
|
||||||
Type[] result = new Type[n];
|
|
||||||
Arrays.fill(result, t);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FrameNode frameSame() {
|
|
||||||
return new FrameNode(F_SAME, 0, null, 0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FrameNode frameSame1(Class clazz) {
|
|
||||||
return new FrameNode(F_SAME1, 0, null, 1, new Object[]{Type.getInternalName(clazz)});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode checkCast(Class clazz) {
|
|
||||||
return new TypeInsnNode(CHECKCAST, Type.getInternalName(clazz));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode loadInt(int i) {
|
|
||||||
switch (i) {
|
|
||||||
case -1:
|
|
||||||
return new InsnNode(ICONST_M1);
|
|
||||||
case 0:
|
|
||||||
return new InsnNode(ICONST_0);
|
|
||||||
case 1:
|
|
||||||
return new InsnNode(ICONST_1);
|
|
||||||
case 2:
|
|
||||||
return new InsnNode(ICONST_2);
|
|
||||||
case 3:
|
|
||||||
return new InsnNode(ICONST_3);
|
|
||||||
case 4:
|
|
||||||
return new InsnNode(ICONST_4);
|
|
||||||
case 5:
|
|
||||||
return new InsnNode(ICONST_5);
|
|
||||||
default: {
|
|
||||||
if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
|
|
||||||
return new IntInsnNode(BIPUSH, i);
|
|
||||||
} else if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
|
|
||||||
return new IntInsnNode(SIPUSH, i);
|
|
||||||
} else {
|
|
||||||
return new LdcInsnNode(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode loadLong(long l) {
|
|
||||||
if (l == 0L) {
|
|
||||||
return new InsnNode(LCONST_0);
|
|
||||||
} else if (l == 1L) {
|
|
||||||
return new InsnNode(LCONST_1);
|
|
||||||
} else {
|
|
||||||
return new LdcInsnNode(l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode loadDouble(double d) {
|
|
||||||
// We want to distinguish -0.0 from 0.0, but -0.0 == 0.0;
|
|
||||||
// luckily, Double.equals() distinguishes these two cases.
|
|
||||||
if (Double.valueOf(d).equals(0.0)) {
|
|
||||||
return new InsnNode(DCONST_0);
|
|
||||||
} else if (d == 1.0) {
|
|
||||||
return new InsnNode(DCONST_1);
|
|
||||||
} else {
|
|
||||||
return new LdcInsnNode(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodInsnNode ctor(Type of, Type... args) {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESPECIAL,
|
|
||||||
of.getInternalName(),
|
|
||||||
"<init>",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
args),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodInsnNode ctor(Class clazz, Class... args) {
|
|
||||||
Type[] argTypes = new Type[args.length];
|
|
||||||
for (int i = 0; i < args.length; i++) {
|
|
||||||
argTypes[i] = Type.getType(args[i]);
|
|
||||||
}
|
|
||||||
return ctor(Type.getType(clazz), argTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.ACONST_NULL;
|
|
||||||
import static org.objectweb.asm.Opcodes.CHECKCAST;
|
|
||||||
import static org.objectweb.asm.Opcodes.GETSTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.FieldInsnNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.InsnNode;
|
|
||||||
import org.objectweb.asm.tree.LdcInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
import org.objectweb.asm.tree.TypeInsnNode;
|
|
||||||
|
|
||||||
public class BoxedPrimitivesMethods {
|
|
||||||
|
|
||||||
private BoxedPrimitivesMethods() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode loadNull() {
|
|
||||||
return new InsnNode(ACONST_NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode loadBoxedBoolean(boolean value) {
|
|
||||||
return new FieldInsnNode(
|
|
||||||
GETSTATIC,
|
|
||||||
Type.getInternalName(Boolean.class),
|
|
||||||
value ? "TRUE" : "FALSE",
|
|
||||||
Type.getDescriptor(Boolean.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode booleanValue() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(Boolean.class),
|
|
||||||
"booleanValue",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.BOOLEAN_TYPE),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode intValue(Class clazz) {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(clazz),
|
|
||||||
"intValue",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.INT_TYPE),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode longValue(Class clazz) {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(clazz),
|
|
||||||
"longValue",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.LONG_TYPE),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode doubleValue(Class clazz) {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(clazz),
|
|
||||||
"doubleValue",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.DOUBLE_TYPE),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodInsnNode box(Type from, Type to) {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
to.getInternalName(),
|
|
||||||
"valueOf",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
to,
|
|
||||||
from),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodInsnNode box(Type from, Class to) {
|
|
||||||
return box(from, Type.getType(to));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode unbox(Class clazz, Type requiredType) {
|
|
||||||
if (requiredType.equals(Type.LONG_TYPE)) {
|
|
||||||
return BoxedPrimitivesMethods.longValue(clazz);
|
|
||||||
} else if (requiredType.equals(Type.INT_TYPE)) {
|
|
||||||
return BoxedPrimitivesMethods.intValue(clazz);
|
|
||||||
} else if (requiredType.equals(Type.DOUBLE_TYPE)) {
|
|
||||||
return BoxedPrimitivesMethods.doubleValue(clazz);
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException("Unsupported primitive type: " + requiredType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InsnList loadBoxedConstant(Object k, Class<?> castTo) {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
|
|
||||||
if (k == null) {
|
|
||||||
il.add(loadNull());
|
|
||||||
} else if (k instanceof Boolean) {
|
|
||||||
il.add(BoxedPrimitivesMethods.loadBoxedBoolean((Boolean) k));
|
|
||||||
} else if (k instanceof Double || k instanceof Float) {
|
|
||||||
il.add(ASMUtils.loadDouble(((Number) k).doubleValue()));
|
|
||||||
il.add(BoxedPrimitivesMethods.box(Type.DOUBLE_TYPE, Type.getType(Double.class)));
|
|
||||||
} else if (k instanceof Number) {
|
|
||||||
il.add(ASMUtils.loadLong(((Number) k).longValue()));
|
|
||||||
il.add(BoxedPrimitivesMethods.box(Type.LONG_TYPE, Type.getType(Long.class)));
|
|
||||||
} else if (k instanceof String) {
|
|
||||||
il.add(new LdcInsnNode(k));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException("Illegal constant type: " + k.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (castTo != null) {
|
|
||||||
Objects.requireNonNull(k);
|
|
||||||
if (!castTo.isAssignableFrom(k.getClass())) {
|
|
||||||
il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(castTo)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InsnList loadBoxedConstant(Object k) {
|
|
||||||
return loadBoxedConstant(k, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode loadNumericValue(Number n, Type requiredType) {
|
|
||||||
if (n instanceof Double || n instanceof Float) {
|
|
||||||
if (requiredType.equals(Type.LONG_TYPE)) {
|
|
||||||
return ASMUtils.loadLong(n.longValue());
|
|
||||||
} else if (requiredType.equals(Type.INT_TYPE)) {
|
|
||||||
return ASMUtils.loadInt(n.intValue());
|
|
||||||
} else if (requiredType.equals(Type.DOUBLE_TYPE)) {
|
|
||||||
return ASMUtils.loadDouble(n.doubleValue());
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException("Unsupported required type: " + requiredType);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (requiredType.equals(Type.LONG_TYPE)) {
|
|
||||||
return ASMUtils.loadLong(n.longValue());
|
|
||||||
} else if (requiredType.equals(Type.INT_TYPE)) {
|
|
||||||
return ASMUtils.loadInt(n.intValue());
|
|
||||||
} else if (requiredType.equals(Type.DOUBLE_TYPE)) {
|
|
||||||
return ASMUtils.loadDouble(n.doubleValue());
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException("Unsupported required type: " + requiredType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.classdump.luna.Conversions;
|
|
||||||
import org.classdump.luna.LuaFormat;
|
|
||||||
import org.classdump.luna.util.Check;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.LdcInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public abstract class ConversionMethods {
|
|
||||||
|
|
||||||
private ConversionMethods() {
|
|
||||||
// not to be instantiated or extended
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InsnList toNumericalValue(String what) {
|
|
||||||
Objects.requireNonNull(what);
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
|
|
||||||
il.add(new LdcInsnNode(what));
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(Conversions.class),
|
|
||||||
"toNumericalValue",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(Number.class),
|
|
||||||
Type.getType(Object.class),
|
|
||||||
Type.getType(String.class)),
|
|
||||||
false));
|
|
||||||
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode floatValueOf() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(Conversions.class),
|
|
||||||
"floatValueOf",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(Double.class),
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode booleanValueOf() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(Conversions.class),
|
|
||||||
"booleanValueOf",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.BOOLEAN_TYPE,
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode unboxedNumberToLuaFormatString(Type tpe) {
|
|
||||||
Check.isTrue(tpe.equals(Type.DOUBLE_TYPE) || tpe.equals(Type.LONG_TYPE));
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(LuaFormat.class),
|
|
||||||
"toString",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(String.class),
|
|
||||||
tpe),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode boxedNumberToLuaFormatString() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(Conversions.class),
|
|
||||||
"stringValueOf",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(String.class),
|
|
||||||
Type.getType(Number.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import org.classdump.luna.runtime.Dispatch;
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public class DispatchMethods {
|
|
||||||
|
|
||||||
public static final String OP_ADD = "add";
|
|
||||||
public static final String OP_SUB = "sub";
|
|
||||||
public static final String OP_MUL = "mul";
|
|
||||||
public static final String OP_DIV = "div";
|
|
||||||
public static final String OP_MOD = "mod";
|
|
||||||
public static final String OP_POW = "pow";
|
|
||||||
public static final String OP_UNM = "unm";
|
|
||||||
public static final String OP_IDIV = "idiv";
|
|
||||||
public static final String OP_BAND = "band";
|
|
||||||
public static final String OP_BOR = "bor";
|
|
||||||
public static final String OP_BXOR = "bxor";
|
|
||||||
public static final String OP_BNOT = "bnot";
|
|
||||||
public static final String OP_SHL = "shl";
|
|
||||||
public static final String OP_SHR = "shr";
|
|
||||||
public static final String OP_CONCAT = "concat";
|
|
||||||
public static final String OP_LEN = "len";
|
|
||||||
public static final String OP_EQ = "eq";
|
|
||||||
public static final String OP_NEQ = "neq";
|
|
||||||
public static final String OP_LT = "lt";
|
|
||||||
public static final String OP_LE = "le";
|
|
||||||
public static final String OP_INDEX = "index";
|
|
||||||
public static final String OP_SETINDEX = "setindex";
|
|
||||||
public static final String OP_CALL = "call";
|
|
||||||
public final static int MAX_CALL_KIND;
|
|
||||||
|
|
||||||
static {
|
|
||||||
int k = 1;
|
|
||||||
while (call_method(k).exists()) {
|
|
||||||
k += 1;
|
|
||||||
}
|
|
||||||
MAX_CALL_KIND = k - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DispatchMethods() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode dynamic(String methodName, int numArgs) {
|
|
||||||
ArrayList<Type> args = new ArrayList<>();
|
|
||||||
args.add(Type.getType(ExecutionContext.class));
|
|
||||||
for (int i = 0; i < numArgs; i++) {
|
|
||||||
args.add(Type.getType(Object.class));
|
|
||||||
}
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(Dispatch.class),
|
|
||||||
methodName,
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
args.toArray(new Type[0])),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode numeric(String methodName, int numArgs) {
|
|
||||||
Type[] args = new Type[numArgs];
|
|
||||||
Arrays.fill(args, Type.getType(Number.class));
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(Dispatch.class),
|
|
||||||
methodName,
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(Number.class),
|
|
||||||
args),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode index() {
|
|
||||||
return dynamic(OP_INDEX, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode setindex() {
|
|
||||||
return dynamic(OP_SETINDEX, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int adjustKind_call(int kind) {
|
|
||||||
return kind > 0 ? (call_method(kind).exists() ? kind : 0) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ReflectionUtils.Method call_method(int kind) {
|
|
||||||
return ReflectionUtils.staticArgListMethodFromKind(
|
|
||||||
Dispatch.class, OP_CALL, new Class[]{ExecutionContext.class, Object.class}, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode call(int kind) {
|
|
||||||
return call_method(kind).toMethodInsnNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode continueLoop() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(Dispatch.class),
|
|
||||||
"signed_le",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.BOOLEAN_TYPE,
|
|
||||||
Type.getType(Number.class),
|
|
||||||
Type.getType(Number.class),
|
|
||||||
Type.getType(Number.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
|
|
||||||
|
|
||||||
import org.classdump.luna.Table;
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public abstract class ExecutionContextMethods {
|
|
||||||
|
|
||||||
private ExecutionContextMethods() {
|
|
||||||
// not to be instantiated or extended
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Type selfTpe() {
|
|
||||||
return Type.getType(ExecutionContext.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodInsnNode registerTicks() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEINTERFACE,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
"registerTicks",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.INT_TYPE),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MethodInsnNode checkCallYield() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEINTERFACE,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
"pauseIfRequested",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InsnList newTable(int array, int hash) {
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
|
|
||||||
il.add(ASMUtils.loadInt(array));
|
|
||||||
il.add(ASMUtils.loadInt(hash));
|
|
||||||
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKEINTERFACE,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
"newTable",
|
|
||||||
Type.getMethodType(
|
|
||||||
Type.getType(Table.class),
|
|
||||||
Type.INT_TYPE,
|
|
||||||
Type.INT_TYPE).getDescriptor(),
|
|
||||||
true));
|
|
||||||
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import org.classdump.luna.runtime.ExecutionContext;
|
|
||||||
import org.classdump.luna.runtime.LuaFunction;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
|
|
||||||
public class InvokableMethods {
|
|
||||||
|
|
||||||
public static int adjustKind_invoke(int kind) {
|
|
||||||
return kind > 0 ? (invoke_method(kind).exists() ? kind : 0) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ReflectionUtils.Method invoke_method(int kind) {
|
|
||||||
return ReflectionUtils.virtualArgListMethodFromKind(
|
|
||||||
LuaFunction.class, "invoke", new Class[]{ExecutionContext.class}, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode invoke(int kind) {
|
|
||||||
return invoke_method(kind).toMethodInsnNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import org.classdump.luna.runtime.AbstractFunction0;
|
|
||||||
import org.classdump.luna.runtime.AbstractFunction1;
|
|
||||||
import org.classdump.luna.runtime.AbstractFunction2;
|
|
||||||
import org.classdump.luna.runtime.AbstractFunction3;
|
|
||||||
import org.classdump.luna.runtime.AbstractFunction4;
|
|
||||||
import org.classdump.luna.runtime.AbstractFunction5;
|
|
||||||
import org.classdump.luna.runtime.AbstractFunctionAnyArg;
|
|
||||||
import org.classdump.luna.runtime.LuaFunction;
|
|
||||||
|
|
||||||
public abstract class InvokeKind {
|
|
||||||
|
|
||||||
private InvokeKind() {
|
|
||||||
// not to be instantiated or extended
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0 means variable number of parameters packed in an array
|
|
||||||
// n > 0 means exactly (n - 1) parameters
|
|
||||||
public static int encode(int numOfFixedArgs, boolean vararg) {
|
|
||||||
return vararg ? 0 : numOfFixedArgs + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int adjust_nativeKind(int kind) {
|
|
||||||
return kind > 0 ? (nativeClassForKind(kind) != null ? kind : 0) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Class<? extends LuaFunction> nativeClassForKind(int kind) {
|
|
||||||
switch (kind) {
|
|
||||||
case 0:
|
|
||||||
return AbstractFunctionAnyArg.class;
|
|
||||||
case 1:
|
|
||||||
return AbstractFunction0.class;
|
|
||||||
case 2:
|
|
||||||
return AbstractFunction1.class;
|
|
||||||
case 3:
|
|
||||||
return AbstractFunction2.class;
|
|
||||||
case 4:
|
|
||||||
return AbstractFunction3.class;
|
|
||||||
case 5:
|
|
||||||
return AbstractFunction4.class;
|
|
||||||
case 6:
|
|
||||||
return AbstractFunction5.class;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
|
|
||||||
import org.classdump.luna.LuaMathOperators;
|
|
||||||
import org.classdump.luna.Table;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public class OperatorMethods {
|
|
||||||
|
|
||||||
public static final String RAW_OP_MOD = "rawmod";
|
|
||||||
public static final String RAW_OP_POW = "rawpow";
|
|
||||||
public static final String RAW_OP_IDIV = "rawidiv";
|
|
||||||
|
|
||||||
private OperatorMethods() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode rawBinaryOperator(String methodName, Type returnType,
|
|
||||||
Type argType) {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(LuaMathOperators.class),
|
|
||||||
methodName,
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
returnType,
|
|
||||||
argType,
|
|
||||||
argType),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode stringLen() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESTATIC,
|
|
||||||
Type.getInternalName(LuaMathOperators.class),
|
|
||||||
"stringLen",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.INT_TYPE,
|
|
||||||
Type.getType(String.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode tableRawSetIntKey() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(Table.class),
|
|
||||||
"rawset",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.INT_TYPE,
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Objects;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public class ReflectionUtils {
|
|
||||||
|
|
||||||
private static Method argListMethodFromKind(boolean isStatic, Class<?> owner, String name,
|
|
||||||
Class<?>[] prefix, int kind) {
|
|
||||||
ArrayList<Class<?>> args = new ArrayList<>();
|
|
||||||
if (prefix != null) {
|
|
||||||
Collections.addAll(args, prefix);
|
|
||||||
}
|
|
||||||
if (kind > 0) {
|
|
||||||
for (int i = 0; i < kind - 1; i++) {
|
|
||||||
args.add(Object.class);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args.add(Object[].class);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Method(owner, name, isStatic, Void.TYPE, args.toArray(new Class<?>[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Method staticArgListMethodFromKind(Class<?> owner, String name, Class<?>[] prefix,
|
|
||||||
int kind) {
|
|
||||||
return argListMethodFromKind(true, owner, name, prefix, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Method virtualArgListMethodFromKind(Class<?> owner, String name, Class<?>[] prefix,
|
|
||||||
int kind) {
|
|
||||||
return argListMethodFromKind(false, owner, name, prefix, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Method {
|
|
||||||
|
|
||||||
public final Class<?> owner;
|
|
||||||
public final String name;
|
|
||||||
public final boolean isStatic;
|
|
||||||
public final Class<?> returnType;
|
|
||||||
public final Class<?>[] args;
|
|
||||||
|
|
||||||
public Method(Class<?> owner, String name, boolean isStatic, Class<?> returnType,
|
|
||||||
Class<?>[] args) {
|
|
||||||
this.owner = Objects.requireNonNull(owner);
|
|
||||||
this.name = name;
|
|
||||||
this.isStatic = isStatic;
|
|
||||||
this.returnType = Objects.requireNonNull(returnType);
|
|
||||||
this.args = args != null ? args : new Class[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean exists() {
|
|
||||||
try {
|
|
||||||
owner.getMethod(name, args);
|
|
||||||
return true;
|
|
||||||
} catch (NoSuchMethodException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type getMethodType() {
|
|
||||||
Type[] ts = new Type[args.length];
|
|
||||||
for (int i = 0; i < args.length; i++) {
|
|
||||||
ts[i] = Type.getType(args[i]);
|
|
||||||
}
|
|
||||||
return Type.getMethodType(
|
|
||||||
Type.getType(returnType),
|
|
||||||
ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodInsnNode toMethodInsnNode() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
isStatic ? INVOKESTATIC : (owner.isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL),
|
|
||||||
Type.getInternalName(owner),
|
|
||||||
name,
|
|
||||||
getMethodType().getDescriptor(),
|
|
||||||
owner.isInterface());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
|
|
||||||
|
|
||||||
import org.classdump.luna.runtime.ReturnBuffer;
|
|
||||||
import org.classdump.luna.util.Check;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.InsnList;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public class ReturnBufferMethods {
|
|
||||||
|
|
||||||
public final static int MAX_SETTO_KIND;
|
|
||||||
public final static int MAX_TAILCALL_KIND;
|
|
||||||
|
|
||||||
static {
|
|
||||||
int k = 1;
|
|
||||||
while (setTo_method(k).exists()) {
|
|
||||||
k += 1;
|
|
||||||
}
|
|
||||||
MAX_SETTO_KIND = k - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
int k = 1;
|
|
||||||
while (tailCall_method(k).exists()) {
|
|
||||||
k += 1;
|
|
||||||
}
|
|
||||||
MAX_TAILCALL_KIND = k - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReturnBufferMethods() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Type selfTpe() {
|
|
||||||
return Type.getType(ReturnBuffer.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode size() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEINTERFACE,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
"size",
|
|
||||||
Type.getMethodType(
|
|
||||||
Type.INT_TYPE).getDescriptor(),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode get() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEINTERFACE,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
"get",
|
|
||||||
Type.getMethodType(
|
|
||||||
Type.getType(Object.class),
|
|
||||||
Type.INT_TYPE).getDescriptor(),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InsnList get(int index) {
|
|
||||||
Check.nonNegative(index);
|
|
||||||
|
|
||||||
InsnList il = new InsnList();
|
|
||||||
|
|
||||||
if (index <= 4) {
|
|
||||||
String methodName = "get" + index;
|
|
||||||
il.add(new MethodInsnNode(
|
|
||||||
INVOKEINTERFACE,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
methodName,
|
|
||||||
Type.getMethodType(
|
|
||||||
Type.getType(Object.class)).getDescriptor(),
|
|
||||||
true));
|
|
||||||
} else {
|
|
||||||
il.add(ASMUtils.loadInt(index));
|
|
||||||
il.add(get());
|
|
||||||
}
|
|
||||||
|
|
||||||
return il;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ReflectionUtils.Method setTo_method(int kind) {
|
|
||||||
String methodName = kind > 0 ? "setTo" : "setToContentsOf";
|
|
||||||
return ReflectionUtils.virtualArgListMethodFromKind(ReturnBuffer.class, methodName, null, kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ReflectionUtils.Method tailCall_method(int kind) {
|
|
||||||
String methodName = kind > 0 ? "setToCall" : "setToCallWithContentsOf";
|
|
||||||
return ReflectionUtils
|
|
||||||
.virtualArgListMethodFromKind(ReturnBuffer.class, methodName, new Class[]{Object.class},
|
|
||||||
kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode setTo(int kind) {
|
|
||||||
return setTo_method(kind).toMethodInsnNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode tailCall(int kind) {
|
|
||||||
return tailCall_method(kind).toMethodInsnNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode toArray() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEINTERFACE,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
"getAsArray",
|
|
||||||
Type.getMethodType(
|
|
||||||
ASMUtils.arrayTypeFor(Object.class)).getDescriptor(),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
|
|
||||||
import org.classdump.luna.Table;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public class TableMethods {
|
|
||||||
|
|
||||||
private TableMethods() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode rawset_int() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(Table.class),
|
|
||||||
"rawset",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.LONG_TYPE,
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode rawset() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(Table.class),
|
|
||||||
"rawset",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.getType(Object.class),
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public class UtilMethods {
|
|
||||||
|
|
||||||
private UtilMethods() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode StringBuilder_append(Type t) {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(StringBuilder.class),
|
|
||||||
"append",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(StringBuilder.class),
|
|
||||||
t),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode StringBuilder_toString() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(StringBuilder.class),
|
|
||||||
"toString",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(String.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode String_compareTo() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(String.class),
|
|
||||||
"compareTo",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.INT_TYPE,
|
|
||||||
Type.getType(String.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
||||||
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
|
|
||||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
|
||||||
|
|
||||||
import org.classdump.luna.Variable;
|
|
||||||
import org.objectweb.asm.Type;
|
|
||||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
|
||||||
import org.objectweb.asm.tree.MethodInsnNode;
|
|
||||||
|
|
||||||
public class VariableMethods {
|
|
||||||
|
|
||||||
private VariableMethods() {
|
|
||||||
// not to be instantiated
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Type selfTpe() {
|
|
||||||
return Type.getType(Variable.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode constructor() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKESPECIAL,
|
|
||||||
selfTpe().getInternalName(),
|
|
||||||
"<init>",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode get() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(Variable.class),
|
|
||||||
"get",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AbstractInsnNode set() {
|
|
||||||
return new MethodInsnNode(
|
|
||||||
INVOKEVIRTUAL,
|
|
||||||
Type.getInternalName(Variable.class),
|
|
||||||
"set",
|
|
||||||
Type.getMethodDescriptor(
|
|
||||||
Type.VOID_TYPE,
|
|
||||||
Type.getType(Object.class)),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper classes for Java bytecode generation using ASM.
|
|
||||||
*/
|
|
||||||
package org.classdump.luna.compiler.gen.asm.helpers;
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Java bytecode generation using the ASM library.
|
|
||||||
*/
|
|
||||||
package org.classdump.luna.compiler.gen.asm;
|
|
@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Core classes for Java bytecode generation.
|
|
||||||
*/
|
|
||||||
package org.classdump.luna.compiler.gen;
|
|
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.ir;
|
|
||||||
|
|
||||||
public abstract class AbstractVal {
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.ir;
|
|
||||||
|
|
||||||
public abstract class AbstractVar {
|
|
||||||
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.ir;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class BasicBlock {
|
|
||||||
|
|
||||||
private final Label label;
|
|
||||||
private final List<BodyNode> body;
|
|
||||||
private final BlockTermNode end;
|
|
||||||
|
|
||||||
public BasicBlock(Label label, List<BodyNode> body, BlockTermNode end) {
|
|
||||||
this.label = Objects.requireNonNull(label);
|
|
||||||
this.body = Objects.requireNonNull(body);
|
|
||||||
this.end = Objects.requireNonNull(end);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BasicBlock that = (BasicBlock) o;
|
|
||||||
return this.label.equals(that.label) &&
|
|
||||||
this.body.equals(that.body) &&
|
|
||||||
this.end.equals(that.end);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(label, body, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Label label() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<BodyNode> body() {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockTermNode end() {
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 Miroslav Janíček
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.classdump.luna.compiler.ir;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class BinOp extends BodyNode {
|
|
||||||
|
|
||||||
private final Op op;
|
|
||||||
private final Val dest;
|
|
||||||
private final Val left;
|
|
||||||
private final Val right;
|
|
||||||
public BinOp(Op op, Val dest, Val left, Val right) {
|
|
||||||
this.op = Objects.requireNonNull(op);
|
|
||||||
this.dest = Objects.requireNonNull(dest);
|
|
||||||
this.left = Objects.requireNonNull(left);
|
|
||||||
this.right = Objects.requireNonNull(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Op op() {
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Val dest() {
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Val left() {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Val right() {
|
|
||||||
return right;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void accept(IRVisitor visitor) {
|
|
||||||
visitor.visit(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Op {
|
|
||||||
ADD,
|
|
||||||
SUB,
|
|
||||||
MUL,
|
|
||||||
DIV,
|
|
||||||
MOD,
|
|
||||||
IDIV,
|
|
||||||
POW,
|
|
||||||
CONCAT,
|
|
||||||
BAND,
|
|
||||||
BOR,
|
|
||||||
BXOR,
|
|
||||||
SHL,
|
|
||||||
SHR,
|
|
||||||
EQ,
|
|
||||||
NEQ,
|
|
||||||
LT,
|
|
||||||
LE
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user