Skip to content

Commit

Permalink
Java inter-op: Support array arguments (#301)
Browse files Browse the repository at this point in the history
* Find the matching AND accessible method

* Add a target to run the latest version with library

* Support array parameters

* Support array parameters

* Support array classes

* Support array arguments

* Fix a bug
  • Loading branch information
bahmanm authored Dec 31, 2024
1 parent 9f3b89b commit 56c1d36
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 10 deletions.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,11 @@ package :

docs :
$(ROOT)docs/extract.pl $(ROOT)bjforth/src/main/forth/bjForth.forth $(ROOT)docs/bjForth.forth.md

####################################################################################################

.PHONY : run

run : bmakelib.error-if-blank( VERSION )
run : gradle( shadowJar )
cat $(root.forth)bjForth.forth - | java -jar $(root.build)bjForth-$(VERSION).jar
20 changes: 17 additions & 3 deletions bjforth/src/main/java/bjforth/primitives/ATLANGLE.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,27 @@ public void execute(Machine machine) {
if (" ".equals(s) || "\t".equals(s) || "\n".equals(s)) {
// Ignore whitespace
} else if (",".equals(s)) {
result.parameterTypes.add(ClassCache.forName(parameterType.toString()));
var rawParamType = parameterType.toString();
if (rawParamType.endsWith("[]")) {
var paramType = rawParamType.replace("[]", "");
result.parameterTypes.add(ClassCache.forNameArray(paramType));
} else if (rawParamType.isEmpty()) {
// Ignore
} else {
result.parameterTypes.add(ClassCache.forName(rawParamType));
}
parameterType = new StringBuilder();
} else if (".".equals(s)) {
state = State.IN_MAYBE_VARARG;
} else if (")".equals(s)) {
if (!parameterType.isEmpty()) {
result.parameterTypes.add(ClassCache.forName(parameterType.toString()));
var rawParamType = parameterType.toString();
if (rawParamType.endsWith("[]")) {
var paramType = rawParamType.replace("[]", "");
result.parameterTypes.add(ClassCache.forNameArray(paramType));
} else if (rawParamType.isEmpty()) {
// Ignore
} else {
result.parameterTypes.add(ClassCache.forName(rawParamType));
}
state = State.IN_ARITY;
} else {
Expand Down
26 changes: 23 additions & 3 deletions bjforth/src/main/java/bjforth/primitives/COMMALANGLE.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,40 @@ public void execute(Machine machine) {
if (" ".equals(s) || "\t".equals(s) || "\n".equals(s)) {
// Ignore whitespace
} else if (",".equals(s)) {
result.parameterTypes.add(ClassCache.forName(parameterType.toString()));
var rawParamType = parameterType.toString();
if (rawParamType.endsWith("[]")) {
var paramType = rawParamType.replace("[]", "");
result.parameterTypes.add(ClassCache.forNameArray(paramType));
} else {
result.parameterTypes.add(ClassCache.forName(rawParamType));
}
parameterType = new StringBuilder();
} else if (".".equals(s)) {
state = State.IN_MAYBE_VARARG;
} else if (")".equals(s)) {
result.parameterTypes.add(ClassCache.forName(parameterType.toString()));
var rawParamType = parameterType.toString();
if (rawParamType.endsWith("[]")) {
var paramType = rawParamType.replace("[]", "");
result.parameterTypes.add(ClassCache.forNameArray(paramType));
} else if (rawParamType.isEmpty()) {
// Ignore
} else {
result.parameterTypes.add(ClassCache.forName(rawParamType));
}
state = State.IN_ARITY;
} else {
parameterType.append(s);
}
break;
case State.IN_MAYBE_VARARG:
if (".".equals(s)) {
result.parameterTypes.add(ClassCache.forNameVararg(parameterType.toString()));
var rawParamType = parameterType.toString();
if (rawParamType.endsWith("[]")) {
var paramType = rawParamType.replace("[]", "");
result.parameterTypes.add(ClassCache.forNameArray(paramType));
} else {
result.parameterTypes.add(ClassCache.forNameVararg(rawParamType));
}
state = State.IN_VARARG;
} else {
parameterType.append(".");
Expand Down
18 changes: 15 additions & 3 deletions bjforth/src/main/java/bjforth/primitives/DOTLANGLE.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,26 @@ public void execute(Machine machine) {
break;
case State.IN_PARAM_TYPE:
if (")".equals(s)) {
if (!parameterType.isEmpty()) {
result.parameterTypes.add(ClassCache.forName(parameterType.toString()));
var rawParamType = parameterType.toString();
if (rawParamType.endsWith("[]")) {
var paramType = rawParamType.replace("[]", "");
result.parameterTypes.add(ClassCache.forNameArray(paramType));
} else if (rawParamType.isEmpty()) {
// Ignore
} else {
result.parameterTypes.add(ClassCache.forName(rawParamType));
}
state = State.IN_ARITY;
} else if (" ".equals(s) || "\t".equals(s) || "\n".equals(s)) {
// Ignore whitespace
} else if (",".equals(s)) {
result.parameterTypes.add(ClassCache.forName(parameterType.toString()));
var rawParamType = parameterType.toString();
if (rawParamType.endsWith("[]")) {
var paramType = rawParamType.replace("[]", "");
result.parameterTypes.add(ClassCache.forNameArray(paramType));
} else {
result.parameterTypes.add(ClassCache.forName(rawParamType));
}
parameterType = new StringBuilder();
} else if (".".equals(s)) {
state = State.IN_MAYBE_VARARG;
Expand Down
3 changes: 2 additions & 1 deletion bjforth/src/main/java/bjforth/primitives/RANGLEDOT.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public void execute(Machine machine) {
var paramTypes = new Class<?>[methodDescriptor.parameterTypes.size()];
methodDescriptor.parameterTypes.toArray(paramTypes);
var method =
MethodUtils.getAccessibleMethod(target.getClass(), methodDescriptor.name, paramTypes);
MethodUtils.getMatchingAccessibleMethod(
target.getClass(), methodDescriptor.name, paramTypes);
if (method == null) {
throw new MachineException(
"No such method found: %s/%d".formatted(methodDescriptor.name, methodDescriptor.arity));
Expand Down
63 changes: 63 additions & 0 deletions bjforth/src/main/java/bjforth/primitives/lib/ClassCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,67 @@ public static Class<?> forNameVararg(String typeName) {
}
}
}

/**
* Tries to load the array class from cache if exists.
*
* <p>If the type name contains '.', tries to look for the array with type name verbatim.
*
* <p>Otherwise looks in 'java.lang', 'java.util' and 'java.io' in order.
*
* <p>Throws if the type cannot be found.
*
* @throws MachineException
*/
public static Class<?> forNameArray(String elementTypeName) {
if (elementTypeName.contains(".")) {
var arrayTypeName = "[L%s;".formatted(elementTypeName);
try {
if (cache.containsKey(arrayTypeName)) {
return cache.get(arrayTypeName);
} else {
var clazz = Class.forName(arrayTypeName);
cache.put(arrayTypeName, clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
throw new MachineException(e.getMessage());
}
} else {
try {
var arrayTypeName = "[Ljava.lang.%s;".formatted(elementTypeName);
if (cache.containsKey(arrayTypeName)) {
return cache.get(arrayTypeName);
} else {
var clazz = Class.forName(arrayTypeName);
cache.put(arrayTypeName, clazz);
return clazz;
}
} catch (ClassNotFoundException _e) {
try {
var arrayTypeName = "[Ljava.util.%s;".formatted(elementTypeName);
if (cache.containsKey(arrayTypeName)) {
return cache.get(arrayTypeName);
} else {
var clazz = Class.forName(arrayTypeName);
cache.put(arrayTypeName, clazz);
return clazz;
}
} catch (ClassNotFoundException _ex) {
try {
var arrayTypeName = "[Ljava.io.%s;".formatted(elementTypeName);
if (cache.containsKey(arrayTypeName)) {
return cache.get(arrayTypeName);
} else {
var clazz = Class.forName(arrayTypeName);
cache.put(arrayTypeName, clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
throw new MachineException(e.getMessage());
}
}
}
}
}
}
27 changes: 27 additions & 0 deletions bjforth/src/test/java/bjforth/primitives/ATLANGLETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,31 @@ void worksOkUnqualified() {

assertThat(machine.getMemoryAt(Variables.get("HERE").getAddress())).isEqualTo(HEREvalue + 2);
}

@Test
void worksOkArrayParameters() {
// GIVEN
var str = "String(File[], String[])/2 ";
var inputStream = new ByteArrayInputStream(str.getBytes());
System.setIn(inputStream);

var ATLANGLEaddr = getPrimitiveAddress("@<");
var actualState = aMachineState().withInstrcutionPointer(ATLANGLEaddr).build();
var machine = aMachine().withState(actualState).build();
var HEREvalue = (Integer) machine.getMemoryAt(Variables.get("HERE").getAddress());

// WHEN
machine.step();

// THEN
assertThat(machine.getMemoryAt(HEREvalue)).isEqualTo(getPrimitiveAddress("LIT"));

var actualResult = (MethodDescriptor) machine.getMemoryAt(HEREvalue + 1);
assertThat(actualResult.clazz).isEqualTo(String.class);
assertThat(actualResult.arity).isEqualTo(2);
assertThat(actualResult.parameterTypes).isEqualTo(List.of(File[].class, String[].class));
assertThat(actualResult.varargFromArgumentNo).isEqualTo(-1);

assertThat(machine.getMemoryAt(Variables.get("HERE").getAddress())).isEqualTo(HEREvalue + 2);
}
}
27 changes: 27 additions & 0 deletions bjforth/src/test/java/bjforth/primitives/COMMALANGLETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,31 @@ void worksOkUnqualified() {

assertThat(machine.getMemoryAt(Variables.get("HERE").getAddress())).isEqualTo(HEREvalue + 2);
}

@Test
void worksOkArrayParameter() {
var str = "String/format(File[], String[])/2 ";
var inputStream = new ByteArrayInputStream(str.getBytes());
System.setIn(inputStream);

var COMMALANGLEaddr = getPrimitiveAddress(",<");
var actualState = aMachineState().withInstrcutionPointer(COMMALANGLEaddr).build();
var machine = aMachine().withState(actualState).build();
var HEREvalue = (Integer) machine.getMemoryAt(Variables.get("HERE").getAddress());

// WHEN
machine.step();

// THEN
assertThat(machine.getMemoryAt(HEREvalue)).isEqualTo(getPrimitiveAddress("LIT"));

var actualResult = (MethodDescriptor) machine.getMemoryAt(HEREvalue + 1);
assertThat(actualResult.target).isEqualTo(String.class);
assertThat(actualResult.arity).isEqualTo(2);
assertThat(actualResult.parameterTypes).isEqualTo(List.of(File[].class, String[].class));
assertThat(actualResult.name).isEqualTo("format");
assertThat(actualResult.varargFromArgumentNo).isEqualTo(-1);

assertThat(machine.getMemoryAt(Variables.get("HERE").getAddress())).isEqualTo(HEREvalue + 2);
}
}
26 changes: 26 additions & 0 deletions bjforth/src/test/java/bjforth/primitives/DOTLANGLETest.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,30 @@ void worksOkUnqualified() {

assertThat(machine.getMemoryAt(Variables.get("HERE").getAddress())).isEqualTo(HEREvalue + 2);
}

@Test
void worksOkArrayParameter() {
var str = "format(List[], File[])/2 ";
var inputStream = new ByteArrayInputStream(str.getBytes());
System.setIn(inputStream);

var DOTLANGLEaddr = getPrimitiveAddress(".<");
var actualState = aMachineState().withInstrcutionPointer(DOTLANGLEaddr).build();
var machine = aMachine().withState(actualState).build();
var HEREvalue = (Integer) machine.getMemoryAt(Variables.get("HERE").getAddress());

// WHEN
machine.step();

// THEN
assertThat(machine.getMemoryAt(HEREvalue)).isEqualTo(getPrimitiveAddress("LIT"));

var actualResult = (MethodDescriptor) machine.getMemoryAt(HEREvalue + 1);
assertThat(actualResult.name).isEqualTo("format");
assertThat(actualResult.varargFromArgumentNo).isEqualTo(-1);
assertThat(actualResult.parameterTypes).isEqualTo(List.of(List[].class, File[].class));
assertThat(actualResult.arity).isEqualTo(2);

assertThat(machine.getMemoryAt(Variables.get("HERE").getAddress())).isEqualTo(HEREvalue + 2);
}
}
29 changes: 29 additions & 0 deletions bjforth/src/test/java/bjforth/primitives/RANGLEDOTTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import static bjforth.machine.ParameterStackBuilder.aParameterStack;

import bjforth.primitives.DOTLANGLE.MethodDescriptor;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -112,4 +113,32 @@ void voidReturn() {
// THEN
assertThat(actualState).hasParameterStackEqualTo(aParameterStack().with((Object) null).build());
}

@Test
void worksOkInnerClasses() {
// GIVEN
var DOTDOTaddr = getPrimitiveAddress(">.");
// var target = List.of(10, 20);
var target = new ArrayList<Integer>();
target.add(10);
target.add(20);
var methodDescriptor = new MethodDescriptor();
methodDescriptor.parameterTypes = List.of(Integer.class);
methodDescriptor.arity = 1;
methodDescriptor.name = "get";
methodDescriptor.varargFromArgumentNo = -1;
var actualState =
aMachineState()
.withInstrcutionPointer(DOTDOTaddr)
.withParameterStack(
aParameterStack().with(0).with(target).with(methodDescriptor).build())
.build();
var machine = aMachine().withState(actualState).build();

// WHEN
machine.step();

// THEN
assertThat(actualState).hasParameterStackEqualTo(aParameterStack().with(10).build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,13 @@ void worksOkVariadic() {
// EXPECT
assertThat(ClassCache.forNameVararg(className)).isEqualTo(Object[].class);
}

@Test
void worksOkArray() {
// GIVEN
var className = "Object";

// EXPECT
assertThat(ClassCache.forNameArray(className)).isEqualTo(Object[].class);
}
}

0 comments on commit 56c1d36

Please sign in to comment.