Skip to content

Commit 7cf27a5

Browse files
authored
Merge pull request #130 from BlazingTwist/spi-fix-1
Fix classloader and parser handling for libraries, add tests
2 parents 1edb277 + 0117f50 commit 7cf27a5

File tree

9 files changed

+185
-44
lines changed

9 files changed

+185
-44
lines changed

build-scripts/run-aya-tests-build.xml

+11-5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@
3939
<mkdir dir="${work.dir}"/>
4040
<mkdir dir="${work.dir}/fs_test"/>
4141

42+
<path id="cp.aya">
43+
<fileset dir="${build-dir}/libs" includes="**/*.jar"/>
44+
<file file="${test.dir}"/>
45+
</path>
46+
47+
<!-- running the lib test requires the example library to be built -->
48+
<ant antfile="${test.dir}/test/lib/example/build.xml" usenativebasedir="true" inheritall="false" inheritrefs="true">
49+
<property name="aya.classpath" value="cp.aya"/>
50+
</ant>
51+
4252
<run_aya work.dir="${work.dir}/fs_test" test.dir="${test.dir}" test.aya="filesystem.aya"/>
4353
<run_aya work.dir="${work.dir}" test.dir="${test.dir}" test.aya="test.aya"/>
4454
</target>
@@ -60,12 +70,8 @@
6070
classname="ui.AyaIDE"
6171
failonerror="true"
6272
errorproperty="error.log.str"
73+
classpathref="cp.aya"
6374
>
64-
<classpath>
65-
<fileset dir="${build-dir}/libs" includes="**/*.jar"/>
66-
<file file="@{test.dir}"/>
67-
</classpath>
68-
6975
<arg value="@{work.dir}"/>
7076
<arg value="@{test.dir}/test/@{test.aya}"/>
7177
</java>

src/aya/StaticData.java

+12-14
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,18 @@ public ArrayList<NamedInstructionStore> loadLibrary(File path) {
165165

166166
try {
167167
URL[] urls = {path.toURI().toURL()};
168-
169-
try (URLClassLoader libClassLoader = new URLClassLoader(urls)) {
170-
StreamSupport.stream(
171-
ServiceLoader.load(NamedInstructionStore.class, libClassLoader).spliterator(),
172-
false
173-
).forEach(store -> {
174-
//IO.out().println("found store: " + store.getClass().getName());
175-
addNamedInstructionStore(store);
176-
loaded.add(store);
177-
});
178-
} catch (IOException e) {
179-
throw new IOError("library.load", path.getPath(), e);
180-
}
181-
168+
169+
// Do not release the classloader, otherwise the library will fail to access its own classes later.
170+
URLClassLoader libClassLoader = new URLClassLoader(urls);
171+
StreamSupport.stream(
172+
ServiceLoader.load(NamedInstructionStore.class, libClassLoader).spliterator(),
173+
false
174+
).forEach(store -> {
175+
//IO.out().println("found store: " + store.getClass().getName());
176+
addNamedInstructionStore(store);
177+
loaded.add(store);
178+
});
179+
182180
} catch (MalformedURLException e) {
183181
throw new IOError("library.load", path.getPath(), e);
184182
}
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
11
package aya.instruction.named;
22

33
import aya.ReprStream;
4+
import aya.StaticData;
45
import aya.eval.BlockEvaluator;
6+
import aya.exceptions.runtime.InternalAyaRuntimeException;
57
import aya.instruction.Instruction;
8+
import aya.obj.symbol.SymbolConstants;
69
import aya.parser.SourceStringRef;
710

811
public class NamedOperatorInstruction extends Instruction {
912

10-
private NamedOperator op;
11-
12-
public NamedOperatorInstruction(SourceStringRef source, NamedOperator op) {
13-
super(source);
14-
this.op = op;
15-
}
16-
17-
@Override
18-
public void execute(BlockEvaluator blockEvaluator) {
19-
this.op.execute(blockEvaluator);
20-
21-
}
22-
23-
@Override
24-
public ReprStream repr(ReprStream stream) {
25-
return this.op.repr(stream);
26-
}
13+
private final String opName;
14+
private NamedOperator op = null;
15+
16+
public NamedOperatorInstruction(SourceStringRef source, String opName) {
17+
super(source);
18+
this.opName = opName;
19+
}
20+
21+
private void loadOp() {
22+
if (op != null) {
23+
return;
24+
}
25+
26+
op = StaticData.getInstance().getNamedInstruction(opName);
27+
if (op == null) {
28+
throw new InternalAyaRuntimeException(SymbolConstants.NOT_AN_OP_ERROR, "Named instruction :(" + opName + ") does not exist");
29+
}
30+
}
31+
32+
@Override
33+
public void execute(BlockEvaluator blockEvaluator) {
34+
this.loadOp();
35+
op.execute(blockEvaluator);
36+
}
37+
38+
@Override
39+
public ReprStream repr(ReprStream stream) {
40+
// repr doesn't need to load the OP-instance, we already know the opName.
41+
stream.print(":(" + opName + ")");
42+
return stream;
43+
}
2744
}

src/aya/parser/tokens/NamedOpToken.java

+1-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package aya.parser.tokens;
22

3-
import aya.StaticData;
43
import aya.exceptions.parser.ParserException;
54
import aya.instruction.Instruction;
6-
import aya.instruction.named.NamedOperator;
75
import aya.instruction.named.NamedOperatorInstruction;
86
import aya.parser.SourceStringRef;
97

@@ -15,12 +13,7 @@ public NamedOpToken(String data, SourceStringRef source) {
1513

1614
@Override
1715
public Instruction getInstruction() throws ParserException {
18-
NamedOperator instruction = StaticData.getInstance().getNamedInstruction(data);
19-
if (instruction != null) {
20-
return new NamedOperatorInstruction(this.getSourceStringRef(), instruction);
21-
} else {
22-
throw new ParserException("Named instruction :(" + data + ") does not exist", source);
23-
}
16+
return new NamedOperatorInstruction(this.getSourceStringRef(), data);
2417
}
2518

2619
@Override

test/lib/example/ExampleStore.java

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package example;
2+
3+
import aya.eval.BlockEvaluator;
4+
import aya.instruction.named.NamedInstructionStore;
5+
import aya.instruction.named.NamedOperator;
6+
import aya.obj.list.Str;
7+
8+
import java.util.Collection;
9+
import java.util.List;
10+
11+
public class ExampleStore implements NamedInstructionStore {
12+
private static DataStorage data;
13+
14+
@Override
15+
public Collection<NamedOperator> getNamedInstructions() {
16+
return List.of(
17+
new OpPut(),
18+
new OpGet()
19+
);
20+
}
21+
22+
private static class OpPut extends NamedOperator {
23+
public OpPut() {
24+
super("example.put");
25+
}
26+
27+
@Override
28+
public void execute(BlockEvaluator blockEvaluator) {
29+
data = new DataStorage(blockEvaluator.pop().str());
30+
}
31+
}
32+
33+
private static class OpGet extends NamedOperator {
34+
public OpGet() {
35+
super("example.get");
36+
}
37+
38+
@Override
39+
public void execute(BlockEvaluator blockEvaluator) {
40+
blockEvaluator.push(aya.obj.list.List.fromStr(new Str(data.value)));
41+
}
42+
}
43+
44+
/**
45+
* Use an extra class for this to verify that the class (/classloader) is not unloaded
46+
*/
47+
private static class DataStorage {
48+
String value;
49+
50+
public DataStorage(String value) {
51+
this.value = value;
52+
}
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
example.ExampleStore

test/lib/example/build.xml

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<project name="example-lib" default="jar" basedir=".">
2+
<!-- verify that all parameters were passed -->
3+
<fail unless="aya.classpath"/>
4+
5+
<!-- re-define the parameters, so that they can be used with autocompletion -->
6+
<property name="aya.classpath" value="ALREADY_DEFINED"/>
7+
8+
<property name="target.dir" location="${basedir}/target/"/>
9+
<property name="target.manifest.file" location="${target.dir}/manifest.mf"/>
10+
<property name="target.jar.file" location="${target.dir}/example.jar"/>
11+
12+
<target name="check_modified">
13+
<!-- if the source files were not modified after the jar file, set 'is_uptodate' -->
14+
<uptodate targetfile="${target.jar.file}" property="is_uptodate">
15+
<srcfiles dir="${basedir}">
16+
<include name="aya.instruction.named.NamedInstructionStore"/>
17+
<include name="build.xml"/>
18+
<include name="ExampleStore.java"/>
19+
</srcfiles>
20+
</uptodate>
21+
</target>
22+
23+
<target name="jar" depends="check_modified" unless="is_uptodate">
24+
<!-- reset the target directory -->
25+
<delete failonerror="false" dir="${target.dir}"/>
26+
<mkdir dir="${target.dir}"/>
27+
28+
<!-- compile and jar the example library -->
29+
<javac destdir="${target.dir}" debug="true" target="11" source="11"
30+
srcdir="${basedir}" includeantruntime="false" includes="ExampleStore.java" classpathref="${aya.classpath}">
31+
</javac>
32+
33+
<manifest file="${target.manifest.file}"/>
34+
<copy file="aya.instruction.named.NamedInstructionStore" todir="${target.dir}/META-INF/services/"/>
35+
<jar jarfile="${target.jar.file}" manifest="${target.manifest.file}">
36+
<fileset dir="${target.dir}">
37+
<include name="**/*.class"/>
38+
<include name="META-INF/**"/>
39+
</fileset>
40+
</jar>
41+
</target>
42+
43+
</project>

test/lib/lib.aya

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[
2+
3+
.# check loaded namedOps match expected
4+
{
5+
"$(:(sys.ad))/test/lib/example/target/example.jar" :lib_path;
6+
"load example.jar from $(lib_path)" :P
7+
lib_path :(library.load) :ops;
8+
"example.jar ops: $(ops)" :P
9+
ops [":(example.put)" ":(example.get)"]
10+
}
11+
12+
.# verify that accessing namedOp that does not exist still causes an error
13+
{
14+
{ :(does.not.exist) "ok" } {"failed"} .K :call_result;
15+
"calling undefined namedOp: $(call_result)" :P
16+
call_result "failed"
17+
}
18+
19+
.# verify that basic library usage works
20+
{
21+
"my-data" :(example.put)
22+
:(example.get) :lib_output;
23+
"obtained data from lib: $(lib_output)":P
24+
lib_output "my-data"
25+
}
26+
27+
] :# { test.test }

test/test.aya

+2
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,5 @@
7474

7575
.# Also load and auto-run many examples
7676
"test/examples" load_test
77+
78+
"test/lib/lib" load_test

0 commit comments

Comments
 (0)