diff --git a/Makefile b/Makefile index 5a6c10ab..79a66c4b 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/bjforth/src/main/java/bjforth/primitives/ATLANGLE.java b/bjforth/src/main/java/bjforth/primitives/ATLANGLE.java index 7d22d2a9..74dfdf66 100644 --- a/bjforth/src/main/java/bjforth/primitives/ATLANGLE.java +++ b/bjforth/src/main/java/bjforth/primitives/ATLANGLE.java @@ -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 { diff --git a/bjforth/src/main/java/bjforth/primitives/COMMALANGLE.java b/bjforth/src/main/java/bjforth/primitives/COMMALANGLE.java index 5b8a5551..77ddc338 100644 --- a/bjforth/src/main/java/bjforth/primitives/COMMALANGLE.java +++ b/bjforth/src/main/java/bjforth/primitives/COMMALANGLE.java @@ -98,12 +98,26 @@ 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); @@ -111,7 +125,13 @@ public void execute(Machine machine) { 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("."); diff --git a/bjforth/src/main/java/bjforth/primitives/DOTLANGLE.java b/bjforth/src/main/java/bjforth/primitives/DOTLANGLE.java index c9794235..d81e501b 100644 --- a/bjforth/src/main/java/bjforth/primitives/DOTLANGLE.java +++ b/bjforth/src/main/java/bjforth/primitives/DOTLANGLE.java @@ -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; diff --git a/bjforth/src/main/java/bjforth/primitives/RANGLEDOT.java b/bjforth/src/main/java/bjforth/primitives/RANGLEDOT.java index 8e017fc9..629279af 100644 --- a/bjforth/src/main/java/bjforth/primitives/RANGLEDOT.java +++ b/bjforth/src/main/java/bjforth/primitives/RANGLEDOT.java @@ -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)); diff --git a/bjforth/src/main/java/bjforth/primitives/lib/ClassCache.java b/bjforth/src/main/java/bjforth/primitives/lib/ClassCache.java index 1350a49a..83117a50 100644 --- a/bjforth/src/main/java/bjforth/primitives/lib/ClassCache.java +++ b/bjforth/src/main/java/bjforth/primitives/lib/ClassCache.java @@ -149,4 +149,67 @@ public static Class forNameVararg(String typeName) { } } } + + /** + * Tries to load the array class from cache if exists. + * + *

If the type name contains '.', tries to look for the array with type name verbatim. + * + *

Otherwise looks in 'java.lang', 'java.util' and 'java.io' in order. + * + *

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()); + } + } + } + } + } } diff --git a/bjforth/src/test/java/bjforth/primitives/ATLANGLETest.java b/bjforth/src/test/java/bjforth/primitives/ATLANGLETest.java index 190ba3cb..36f2d53c 100644 --- a/bjforth/src/test/java/bjforth/primitives/ATLANGLETest.java +++ b/bjforth/src/test/java/bjforth/primitives/ATLANGLETest.java @@ -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); + } } diff --git a/bjforth/src/test/java/bjforth/primitives/COMMALANGLETest.java b/bjforth/src/test/java/bjforth/primitives/COMMALANGLETest.java index a4cc310d..c095f714 100644 --- a/bjforth/src/test/java/bjforth/primitives/COMMALANGLETest.java +++ b/bjforth/src/test/java/bjforth/primitives/COMMALANGLETest.java @@ -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); + } } diff --git a/bjforth/src/test/java/bjforth/primitives/DOTLANGLETest.java b/bjforth/src/test/java/bjforth/primitives/DOTLANGLETest.java index 2a91162c..3bb59b6a 100644 --- a/bjforth/src/test/java/bjforth/primitives/DOTLANGLETest.java +++ b/bjforth/src/test/java/bjforth/primitives/DOTLANGLETest.java @@ -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); + } } diff --git a/bjforth/src/test/java/bjforth/primitives/RANGLEDOTTest.java b/bjforth/src/test/java/bjforth/primitives/RANGLEDOTTest.java index dd3ada8c..65690117 100644 --- a/bjforth/src/test/java/bjforth/primitives/RANGLEDOTTest.java +++ b/bjforth/src/test/java/bjforth/primitives/RANGLEDOTTest.java @@ -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; @@ -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(); + 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()); + } } diff --git a/bjforth/src/test/java/bjforth/primitives/lib/ClassCacheTest.java b/bjforth/src/test/java/bjforth/primitives/lib/ClassCacheTest.java index 2095c0e1..449c7497 100644 --- a/bjforth/src/test/java/bjforth/primitives/lib/ClassCacheTest.java +++ b/bjforth/src/test/java/bjforth/primitives/lib/ClassCacheTest.java @@ -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); + } }