Skip to content

Commit

Permalink
Method body of non-overload methods
Browse files Browse the repository at this point in the history
  • Loading branch information
squid233 committed Dec 9, 2023
1 parent 6dffaee commit 540e80c
Show file tree
Hide file tree
Showing 17 changed files with 994 additions and 42 deletions.
5 changes: 5 additions & 0 deletions demo/src/main/java/overrun/marshal/test/CMarshalTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.lang.foreign.MemorySegment;

/**
* Test basic features
*
* @author squid233
* @since 0.1.0
*/
Expand Down Expand Up @@ -111,6 +113,9 @@ public interface CMarshalTest {
*/
@Access(AccessModifier.PROTECTED)
@Default("MemorySegment.NULL")
// @Default("""
// MemorySegment.
// NULL""")
@Entrypoint("testAllFeatures")
MemorySegment testAll(MemorySegment segment);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.lang.foreign.MemorySegment;

/**
* Test with selector
*
* @author squid233
* @since 0.1.0
*/
Expand Down
136 changes: 103 additions & 33 deletions src/main/java/overrun/marshal/NativeApiProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@

package overrun.marshal;

import overrun.marshal.gen.FieldSpec;
import overrun.marshal.gen.SourceFile;
import overrun.marshal.gen.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
Expand Down Expand Up @@ -124,9 +123,9 @@ private void writeFile(
return;
}
classSpec.addField(new FieldSpec(toTypeName(e.asType().toString()),
e.getSimpleName().toString(),
processingEnv.getElementUtils().getConstantExpression(constantValue)),
fieldSpec -> fieldSpec.setDocument(getDocument(e)));
e.getSimpleName().toString(),
Spec.literal(getConstExp(constantValue)))
.setDocument(getDocument(e)));
});
if (!methods.isEmpty()) {
// loader
Expand All @@ -142,14 +141,15 @@ private void writeFile(
final String libname = nativeApi.libname();
classSpec.addField(new FieldSpec("SymbolLookup",
"_LOOKUP",
"SymbolLookup.libraryLookup(" + (
selector == null ?
("\"" + libname + "\"" + ", Arena.global()") :
("new " + selector + "().select(\"" + libname + "\"), Arena.global()")
) + ")"), fieldSpec -> fieldSpec.setAccessModifier(AccessModifier.PRIVATE));
new InvokeSpec("SymbolLookup", "libraryLookup")
.addArgument(selector == null ?
Spec.literal(getConstExp(libname)) :
new InvokeSpec(new ConstructSpec(selector), "select").addArgument(getConstExp(libname)))
.addArgument(new InvokeSpec("Arena", "global"))
).setAccessModifier(AccessModifier.PRIVATE));
classSpec.addField(new FieldSpec("Linker",
"_LINKER",
"Linker.nativeLinker()"), fieldSpec -> fieldSpec.setAccessModifier(AccessModifier.PRIVATE));
new InvokeSpec("Linker", "nativeLinker")).setAccessModifier(AccessModifier.PRIVATE));
// method handles
methods.stream().collect(Collectors.toMap(NativeApiProcessor::methodEntrypoint, Function.identity(), (e1, e2) -> {
final Overload o1 = e1.getAnnotation(Overload.class);
Expand Down Expand Up @@ -187,28 +187,26 @@ private void writeFile(
// overwrite it.
return e2;
}, LinkedHashMap::new)).forEach((k, v) -> {
final StringBuilder sb = new StringBuilder(256);
final TypeMirror returnType = v.getReturnType();
final Default defaulted = v.getAnnotation(Default.class);
sb.append("_LOOKUP.find(\"").append(k).append("\").map(_s -> _LINKER.downcallHandle(_s, FunctionDescriptor.of");
if (returnType.getKind() == TypeKind.VOID) {
sb.append("Void(");
} else {
sb.append('(').append(toValueLayout(returnType));
if (!v.getParameters().isEmpty()) {
sb.append(", ");
}
}
sb.append(v.getParameters().stream()
.map(e -> toValueLayout(e.asType()))
.collect(Collectors.joining(", ")));
sb.append("))).orElse");
if (defaulted != null) {
sb.append("(null)");
} else {
sb.append("Throw()");
}
classSpec.addField(new FieldSpec("MethodHandle", k, sb.toString()), fieldSpec -> {
final boolean defaulted = v.getAnnotation(Default.class) != null;
classSpec.addField(new FieldSpec("MethodHandle", k,
new InvokeSpec(new InvokeSpec(
new InvokeSpec("_LOOKUP", "find").addArgument(getConstExp(k)),
"map"
).addArgument(new LambdaSpec("_s")
.addStatementThis(new InvokeSpec("_LINKER", "downcallHandle")
.addArgument("_s")
.addArgument(new InvokeSpec("FunctionDescriptor",
returnType.getKind() == TypeKind.VOID ? "ofVoid" : "of").also(invokeSpec -> {
if (returnType.getKind() != TypeKind.VOID) {
invokeSpec.addArgument(toValueLayout(returnType));
}
v.getParameters().forEach(e -> invokeSpec.addArgument(toValueLayout(e.asType())));
})))), defaulted ? "orElse" : "orElseThrow").also(invokeSpec -> {
if (defaulted) {
invokeSpec.addArgument("null");
}
})), fieldSpec -> {
final Access access = v.getAnnotation(Access.class);
if (access != null) {
fieldSpec.setAccessModifier(access.value());
Expand All @@ -217,11 +215,79 @@ private void writeFile(
});
// method declarations
methods.forEach(e -> {
final TypeMirror returnType = e.getReturnType();
final Overload overload = e.getAnnotation(Overload.class);
final String overloadValue = overload != null ? overload.value() : null;
final String javaReturnType = toTargetType(returnType, overloadValue);

classSpec.addMethod(new MethodSpec(javaReturnType, e.getSimpleName().toString()), methodSpec -> {
final var parameters = e.getParameters();
final boolean shouldInsertAllocator = parameters.stream().anyMatch(p -> {
final TypeMirror t = p.asType();
return isArray(t) || (t.getKind() == TypeKind.DECLARED && isString(t));
// TODO: 2023/12/9 Struct
});
final Access access = e.getAnnotation(Access.class);
final Custom custom = e.getAnnotation(Custom.class);
final Default defaultAnnotation = e.getAnnotation(Default.class);
final boolean isDefaulted = defaultAnnotation != null;

methodSpec.setDocument(getDocument(e));
if (isDefaulted) {
methodSpec.addAnnotation(new AnnotationSpec(Default.class.getSimpleName()).also(annotationSpec -> {
if (!defaultAnnotation.value().isBlank()) {
annotationSpec.addArgument("value", getConstExp(defaultAnnotation.value()));
}
}));
}
if (access != null) {
methodSpec.setAccessModifier(access.value());
}
if (shouldInsertAllocator) {
methodSpec.addParameter("SegmentAllocator", "_segmentAllocator");
}
parameters.forEach(p -> methodSpec.addParameter(toTargetType(p.asType(), overloadValue), p.getSimpleName().toString()));

if (custom != null) {
methodSpec.addStatement(Spec.indented(custom.value()));
return;
}
if (overload == null) {
final boolean notVoid = returnType.getKind() != TypeKind.VOID;
if (isDefaulted && notVoid && defaultAnnotation.value().isBlank()) {
printError(type + "::" + e + ": Default non-void method must have a default return value");
}
final String entrypoint = methodEntrypoint(e);
methodSpec.addStatement(new TryCatchStatement().also(tryCatchStatement -> {
StatementBlock targetStatement = tryCatchStatement;
if (isDefaulted) {
final IfStatement ifStatement = new IfStatement(ConditionSpec.notNull(entrypoint));
if (notVoid) {
ifStatement.addElseClause(ElseClause.of(), elseClause ->
elseClause.addStatement(Spec.returnStatement(Spec.indentedExceptFirstLine(defaultAnnotation.value())))
);
}
targetStatement = ifStatement;
tryCatchStatement.addStatement(ifStatement);
}
final InvokeSpec invokeExact = new InvokeSpec(entrypoint, "invokeExact")
.addArguments(e.getParameters().stream()
.map(p -> Spec.literal(p.getSimpleName().toString()))
.collect(Collectors.toList()));
targetStatement.addStatement(notVoid ? Spec.returnStatement(Spec.cast(javaReturnType, invokeExact)) : Spec.statement(invokeExact));
tryCatchStatement.addCatchClause(new CatchClause("Throwable", "_ex"), catchClause ->
catchClause.addStatement(Spec.literalStatement("throw new AssertionError(\"should not reach here\", " + catchClause.name() + ')'))
);
}));
return;
}
;
});
});
}
});

final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + "." + simpleClassName);
final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + '.' + simpleClassName);
try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) {
file.write(out);
}
Expand All @@ -231,6 +297,10 @@ private String getDocument(Element element) {
return processingEnv.getElementUtils().getDocComment(element);
}

private String getConstExp(Object value) {
return processingEnv.getElementUtils().getConstantExpression(value);
}

private static String toTypeName(String rawClassName) {
if (rawClassName.equals(String.class.getName())) {
return String.class.getSimpleName();
Expand Down
79 changes: 79 additions & 0 deletions src/main/java/overrun/marshal/gen/AnnotationSpec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* MIT License
*
* Copyright (c) 2023 Overrun Organization
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*/

package overrun.marshal.gen;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
* Annotation
*
* @author squid233
* @since 0.1.0
*/
public final class AnnotationSpec implements Spec {
private final String type;
private final Map<String, String> arguments = new LinkedHashMap<>();

/**
* Constructor
*
* @param type type
*/
public AnnotationSpec(String type) {
this.type = type;
}

/**
* Add a argument
*
* @param name name
* @param value value
*/
public void addArgument(String name, String value) {
arguments.put(name, value);
}

/**
* Also runs the action
*
* @param consumer the action
* @return this
*/
public AnnotationSpec also(Consumer<AnnotationSpec> consumer) {
consumer.accept(this);
return this;
}

@Override
public void append(StringBuilder builder, int indent) {
builder.append('@').append(type);
if (!arguments.isEmpty()) {
builder.append('(');
if (arguments.size() == 1 && "value".equals(arguments.keySet().stream().findFirst().orElse(null))) {
builder.append(arguments.get("value"));
} else {
builder.append(arguments.entrySet().stream()
.map(e -> e.getKey() + " = " + e.getValue())
.collect(Collectors.joining(", ")));
}
builder.append(')');
}
}
}
68 changes: 68 additions & 0 deletions src/main/java/overrun/marshal/gen/CatchClause.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* MIT License
*
* Copyright (c) 2023 Overrun Organization
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*/

package overrun.marshal.gen;

import java.util.ArrayList;
import java.util.List;

/**
* catch clause
*
* @author squid233
* @since 0.1.0
*/
public final class CatchClause implements Spec, StatementBlock {
private final String type;
private final String name;
private final List<Spec> statements = new ArrayList<>();

/**
* Constructor
*
* @param type exception type
* @param name variable name
*/
public CatchClause(String type, String name) {
this.type = type;
this.name = name;
}

/**
* Add a statement
*
* @param spec statement
*/
@Override
public void addStatement(Spec spec) {
statements.add(spec);
}

/**
* {@return name}
*/
public String name() {
return name;
}

@Override
public void append(StringBuilder builder, int indent) {
final String indentString = Spec.indentString(indent);
builder.append("catch (").append(type).append(' ').append(name).append(") {\n");
statements.forEach(spec -> spec.append(builder, indent + 4));
builder.append(indentString).append('}');
}
}
15 changes: 14 additions & 1 deletion src/main/java/overrun/marshal/gen/ClassSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class ClassSpec implements Spec {
private AccessModifier accessModifier = AccessModifier.PUBLIC;
private boolean isFinal = false;
private final List<FieldSpec> fieldSpecs = new ArrayList<>();
private final List<MethodSpec> methodSpecs = new ArrayList<>();

/**
* Constructor
Expand Down Expand Up @@ -91,6 +92,17 @@ public void addField(FieldSpec fieldSpec, Consumer<FieldSpec> consumer) {
addField(fieldSpec);
}

/**
* Add a method and perform the action
*
* @param methodSpec method
* @param consumer action
*/
public void addMethod(MethodSpec methodSpec, Consumer<MethodSpec> consumer) {
consumer.accept(methodSpec);
methodSpecs.add(methodSpec);
}

@Override
public void append(StringBuilder builder, int indent) {
final String indentString = Spec.indentString(indent);
Expand All @@ -105,7 +117,8 @@ public void append(StringBuilder builder, int indent) {
// body
fieldSpecs.forEach(fieldSpec -> fieldSpec.append(builder, indent + 4));
builder.append('\n');
methodSpecs.forEach(methodSpec -> methodSpec.append(builder, indent + 4));
// end
builder.append("}\n");
builder.append(indentString).append("}\n");
}
}
Loading

0 comments on commit 540e80c

Please sign in to comment.