I want to allow some Java objects to be translated into a string representation which matches Python or JavaScript objects.
I thought that I could tag all compatible classes with compatibilising interfaces:
public interface Pythonable {
public String toPython();
}
public interface JavaScriptable {
public String toJavaScript();
}
If I were manually creating classes that can be compiled to either of the two languages, I would implement those methods directly:
public class Point implements Pythonable, JavaScriptable {
double x;
double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toPython() {
return String.format("(%f, %f)", x, y);
}
@Override
public String toJavaScript() {
return String.format("[%f, %f]", x, y);
}
}
But I don't own java.util.Set
for example, so how can I implement these translation targets on it?
Mocking up (Rust traits style), something like
implement Pythonable for java.util.Set {
@Override
public String toPython() {
return String.format("{%s}", String.join(",", this));
}
}
I would like to do that so that I can use Java's own objects on the Java side (don't have to manually make Proxy objects or wrapper objects for everything I may want to use myself and end up with disgusting levels of nesting), and the implementations of the compilation targets can still remain member-type-agnostic:
public class SetDifference<T1 extends Pythonable & JavaScriptable, T2 extends Pythonable & JavaScriptable> implements Pythonable, JavaScriptable {
T1 minuend;
T2 subtrahend;
public SetDifference(T1 minuend, T2 subtrahend) {
this.minuend = minuend;
this.subtrahend = subtrahend;
}
@Override
public String toPython() {
return String.format("%s - %s", minuend.toPython(), subtrahend.toPython());
}
@Override
public String toJavaScript() {
return String.format("new Set([...(%s)].filter((x) => !(%s).has(x)))", minuend.toJavaScript(), subtrahend.toJavaScript());
}
}
import java.util.Set;
var setDifference = new SetDifference(Set.of({1, 2, 3}), Set.of({2}));
setDifference.toPython() // produces "{1, 2, 3} - {2}"
And what if a user later wants to add a new compilation target? They make their own
public interface Swiftable {
public String toSwift();
}
How can they add translations to all the existing objects that I've defined?
This doesn't seem very Java-supported.
How can I make this work, or else what's a more Java way to provide many different toString
functions (essentially) for built-in Java types?
I've had an idea to use Transpiler classes that have methods to transpile supported types:
public class PythonTranspiler {
public String toPython(Pythonable pythonable) {
return pythonable.toPython();
}
public String toPython(Set<?> set) {
return String.format(
"set(%s)",
String.join(", ", set.stream().map(e -> this.toPython(e))));
}
}
public class JavaScriptTranspiler {
public String toJavaScript(JavaScriptable javaScriptable) {
return javaScriptable.toJavaScript();
}
public String toJavaScript(Set<?> set) {
return String.format(
"new Set([%s])",
String.join(", ", set.stream().map(e -> this.toJavaScript(e))));
}
}
But I'm not sure how to define a type for those that are translatable altogether (all the <?>
s in the example below), and I guess things like map(e -> this.toJavaScript(e))
won't actually work as-written, because it will select the Set
's generic type parameter method from JavaScriptTranspiler
, not the actual object's type.
How can I make this work
implementing a compiler? I mean, are not you, perhaps, mixing up concerns meant to be unaware of one from another?