From b4faf325d02d0b30e30d513f14c3f6115311ec8f Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Mon, 13 Jul 2026 20:39:08 +0200 Subject: [PATCH 1/2] Use ValueSerializer for rpc communication --- lib/resources/langages/java/Remote.java | 26 +-- src/plm/core/lang/LangJava.java | 45 +----- .../lang/primitives/PrimitiveParameter.java | 5 + src/plm/universe/CommandExecutor.java | 149 ++++++++++-------- 4 files changed, 110 insertions(+), 115 deletions(-) diff --git a/lib/resources/langages/java/Remote.java b/lib/resources/langages/java/Remote.java index 32b7558d2..571557d00 100644 --- a/lib/resources/langages/java/Remote.java +++ b/lib/resources/langages/java/Remote.java @@ -8,6 +8,9 @@ import java.util.Locale; import java.util.Vector; +import java.awt.Color; +import static ValueSerializer.*; + public abstract class Remote { /* @@ -50,31 +53,36 @@ private static void getAnswerLine() { public static int getAnswerInt() { getAnswerLine(); - return Integer.parseInt(answerBuffer); + return (int) deserialize(answerBuffer); } public static boolean getAnswerBoolean() { - return getAnswerInt() == 1; + getAnswerLine(); + return (boolean) deserialize(answerBuffer); } - public static double getAnswerDouble() { getAnswerLine(); - return Double.parseDouble(answerBuffer); + return (double) deserialize(answerBuffer); + } + + public static Color getAnswerColor() { + getAnswerLine(); + return (Color) deserialize(answerBuffer); } public static String getAnswerString() { getAnswerLine(); - return answerBuffer; + return (String) deserialize(answerBuffer); } public static char getAnswerChar() { - getAnswerLine(); - return answerBuffer.charAt(0); + getAnswerLine(); + return (char) deserialize(answerBuffer); } - public static void sendCommand(String format, Object... args) { - String command = String.format(Locale.ENGLISH, format, args); + public static void sendCommand(String opCode, String name, Object... args) { + String command = opCode+" "+serialize(args)+" "+name; // System.out.println("Student sends: " + command); System.out.flush(); diff --git a/src/plm/core/lang/LangJava.java b/src/plm/core/lang/LangJava.java index 522768422..63c1b2e7d 100644 --- a/src/plm/core/lang/LangJava.java +++ b/src/plm/core/lang/LangJava.java @@ -319,7 +319,8 @@ public void compileExo(Exercise exo, LogWriter out, StudentOrCorrection whatToCo Map runtimePatterns = new TreeMap(); runtimePatterns.put("\\$package", "package " + packageNameCache + ";"); - String mainRemoteContent = getRemoteJavaFile(null, packageNameCache); + String mainRemoteContent = getRemoteJavaFile(null, packageNameCache) + .replace("import static ValueSerializer.*;", "import static " + packageNameCache + ".ValueSerializer.*;");; DiagnosticCollector diagnostic = new DiagnosticCollector(); try { @@ -342,6 +343,7 @@ public void compileExo(Exercise exo, LogWriter out, StudentOrCorrection whatToCo runtimePatterns.put("\\$run", runFunction); runtimePatterns.put("\\$dependency", dependency); runtimePatterns.put("\\$imports", ("import static " + packageNameCache + ".ValueSerializer.*;\n" + + "import java.awt.Color;\n" + "import static " + packageNameCache + ".Remote.*;\n" + "import static " + packageNameCache + "." + remote + ".*;\n" + imports) .replace('\n', ' ')); @@ -351,7 +353,6 @@ public void compileExo(Exercise exo, LogWriter out, StudentOrCorrection whatToCo String entityCode = sf.getCompilableContent(runtimePatterns, whatToCompile); - entityCode = Pattern.compile("([^a-zA-Z])(Color)([^a-zA-Z.])").matcher(entityCode).replaceAll("$1int$3"); entityCode = Pattern.compile("([^a-zA-Z])(Direction)([^a-zA-Z.])").matcher(entityCode).replaceAll("$1int$3"); entityCode = Pattern.compile("this.").matcher(entityCode).replaceAll(""); @@ -621,7 +622,7 @@ public void run() public static class LangJavaExternalPrimitiveGenerator implements ExternalPrimitiveLanguage { String getLanguageType(CommandArgumentType type) { - if (type == CommandArgumentType.COLOR) return "int"; + if (type == CommandArgumentType.COLOR) return "Color"; if (type == CommandArgumentType.DIRECTION) return "int"; if (type == CommandArgumentType.DOUBLE) return "double"; if (type == CommandArgumentType.INT) return "int"; @@ -642,36 +643,6 @@ String getTypeDeclaration(CommandArgumentType type) { "\tstatic final int WEST = 3;\n" + "}"; } - if (type == CommandArgumentType.COLOR) { - return "public static class Color {\n" + - "\tstatic final int white = " + ColorMapper.color2int(Color.white) + ";\n" + - "\tstatic final int WHITE = " + ColorMapper.color2int(Color.WHITE) + ";\n" + - "\tstatic final int black = " + ColorMapper.color2int(Color.black) + ";\n" + - "\tstatic final int BLACK = " + ColorMapper.color2int(Color.BLACK) + ";\n" + - "\tstatic final int blue = " + ColorMapper.color2int(Color.blue) + ";\n" + - "\tstatic final int BLUE = " + ColorMapper.color2int(Color.BLUE) + ";\n" + - "\tstatic final int cyan = " + ColorMapper.color2int(Color.cyan) + ";\n" + - "\tstatic final int CYAN = " + ColorMapper.color2int(Color.CYAN) + ";\n" + - "\tstatic final int darkGray = " + ColorMapper.color2int(Color.darkGray) + ";\n" + - "\tstatic final int DARK_GRAY = " + ColorMapper.color2int(Color.DARK_GRAY) + ";\n" + - "\tstatic final int gray = " + ColorMapper.color2int(Color.gray) + ";\n" + - "\tstatic final int GRAY = " + ColorMapper.color2int(Color.GRAY) + ";\n" + - "\tstatic final int green = " + ColorMapper.color2int(Color.green) + ";\n" + - "\tstatic final int GREEN = " + ColorMapper.color2int(Color.GREEN) + ";\n" + - "\tstatic final int lightGray = " + ColorMapper.color2int(Color.lightGray) + ";\n" + - "\tstatic final int LIGHT_GRAY = " + ColorMapper.color2int(Color.LIGHT_GRAY) + ";\n" + - "\tstatic final int magenta = " + ColorMapper.color2int(Color.magenta) + ";\n" + - "\tstatic final int MAGENTA = " + ColorMapper.color2int(Color.MAGENTA) + ";\n" + - "\tstatic final int orange = " + ColorMapper.color2int(Color.orange) + ";\n" + - "\tstatic final int ORANGE = " + ColorMapper.color2int(Color.ORANGE) + ";\n" + - "\tstatic final int pink = " + ColorMapper.color2int(Color.pink) + ";\n" + - "\tstatic final int PINK = " + ColorMapper.color2int(Color.PINK) + ";\n" + - "\tstatic final int red = " + ColorMapper.color2int(Color.red) + ";\n" + - "\tstatic final int RED = " + ColorMapper.color2int(Color.RED) + ";\n" + - "\tstatic final int yellow = " + ColorMapper.color2int(Color.yellow) + ";\n" + - "\tstatic final int YELLOW = " + ColorMapper.color2int(Color.YELLOW) + ";\n" + - "}"; - } return ""; } @@ -697,7 +668,7 @@ String getReturning(CommandArgumentType type) { if (type == CommandArgumentType.STRING) return "getAnswerString()"; if (type == CommandArgumentType.DOUBLE) return "getAnswerDouble()"; if (type == CommandArgumentType.CHAR) return "getAnswerChar()"; - if (type == CommandArgumentType.COLOR) return "getAnswerInt()"; + if (type == CommandArgumentType.COLOR) return "getAnswerColor()"; if (type == CommandArgumentType.DIRECTION) return "getAnswerInt()"; if (type == CommandArgumentType.INT) return "getAnswerInt()"; if (type == CommandArgumentType.BOOLEAN) @@ -732,10 +703,8 @@ String getImplementation(PrimitiveMethod method) { int id = method.id(); String name = method.name(); - String formats = method.parameters().stream().map(PrimitiveParameter::type) - .map(this::getTemplatingForType).map(s -> s + " ").collect(Collectors.joining()); - String command = "\tsendCommand(\"" + id + " " + formats + name + "\"" + + String command = "\tsendCommand(\"" + id + "\", \"" + name + "\"" + method.parameters().stream().map(this::getArgumentExpression).map(s -> ", " + s).collect(Collectors.joining()) + ");"; String returning = method.output() != null ? "\treturn " + getReturning(method.output()) + ";" : ""; @@ -758,7 +727,7 @@ String getImplementation(PrimitiveMethod method) { body += "\n" + extraCode; final String code = - "/* THIS FILE IS GENERATED. DO NOT EDIT */\nimport static Remote.*;\n\npublic class " + name + " {" + body.replace("\n", "\n\t") + "\n}"; + "/* THIS FILE IS GENERATED. DO NOT EDIT */\nimport static Remote.*;\nimport java.awt.Color;\n\npublic class " + name + " {" + body.replace("\n", "\n\t") + "\n}"; System.err.println("XXX Generating " + folder + "/" + name + ".java"); Files.writeString(new File(folder, name + ".java").toPath(), code); diff --git a/src/plm/core/lang/primitives/PrimitiveParameter.java b/src/plm/core/lang/primitives/PrimitiveParameter.java index 7b1de0496..479702753 100644 --- a/src/plm/core/lang/primitives/PrimitiveParameter.java +++ b/src/plm/core/lang/primitives/PrimitiveParameter.java @@ -6,9 +6,11 @@ public final class PrimitiveParameter { private final String name; private final CommandArgumentType type; + private final Class rawType; public PrimitiveParameter(Parameter parameter) { this.name = parameter.getName(); + this.rawType = parameter.getType(); this.type = CommandArgumentType.getCommandArgumentTypeFromClass(parameter.getType()); } @@ -38,4 +40,7 @@ public String toString() { return name + ": " + type; } + public Class getRawType() { + return rawType; + } } diff --git a/src/plm/universe/CommandExecutor.java b/src/plm/universe/CommandExecutor.java index e6f6ddf0b..1ee71da31 100644 --- a/src/plm/universe/CommandExecutor.java +++ b/src/plm/universe/CommandExecutor.java @@ -1,36 +1,45 @@ package plm.universe; -import plm.core.lang.primitives.CommandArgumentType; +import plm.core.ValueSerializer; import plm.core.lang.primitives.PrimitiveMethod; -import plm.core.lang.primitives.PrimitiveParameter; import plm.core.lang.primitives.PrimitiveRegistration; import java.io.BufferedWriter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public final class CommandExecutor { + private static final Map, Map> getMinimalPrimitiveForEntity_Cache = new HashMap<>(); + private CommandExecutor() { } - private static final Map, Map> getMinimalPrimitiveForEntity_Cache = new HashMap<>(); - public synchronized static void command(Entity entity, String command, BufferedWriter out) throws InvocationTargetException, IllegalAccessException { if (command.contains("AddressSanitizer")) { if (!command.equals("AddressSanitizer:DEADLYSIGNAL")) System.err.println(command); return; } + int firstSpace = command.indexOf(' '); + int lastSpace = command.lastIndexOf(' '); - int id = Integer.parseInt(command.substring(0, command.indexOf(' '))); - String primitive = command.substring(command.lastIndexOf(' ') + 1); - PrimitiveMethod method = getMinimalPrimitiveForEntity_Cache.computeIfAbsent(entity.getClass(), PrimitiveRegistration::getMinimalPrimitiveForEntity) - .get(id); + String opCodeSegment = command.substring(0, firstSpace); + String opArgsSegment = firstSpace <= lastSpace + 1 ? command.substring(firstSpace + 1, lastSpace) : ""; + String opNameSegment = command.substring(lastSpace + 1); - if (!method.name().equals(primitive)) { - System.err.println("Primitive real name do not match provided name. (expected: " + method.name() + ", provided: " + primitive + ")"); + int opCode = Integer.parseInt(opCodeSegment); + Map primitiveMethodMap = getMinimalPrimitiveForEntity_Cache + .computeIfAbsent(entity.getClass(), PrimitiveRegistration::getMinimalPrimitiveForEntity); + PrimitiveMethod method = primitiveMethodMap.get(opCode); + + if (!method.name().equals(opNameSegment)) { + System.err.println("Primitive real name do not match provided name. " + + "(expected: " + method.name() + ", provided: " + opNameSegment + ")"); return; } @@ -38,28 +47,33 @@ public synchronized static void command(Entity entity, String command, BufferedW // But a serialized String argument may itself contain spaces (e.g. "Oh Boy!"), using a simple split(" ") would ruin the parameter. // Instead, tokenize the middle part while respecting quoted strings and bracketed arrays. // FIXME: we should use ValueSerializer for the whole array of parameters, but this requires to implement this logic in C too - int firstSpace = command.indexOf(' '); - int lastSpace = command.lastIndexOf(' '); - String argsPart = (firstSpace < lastSpace) ? command.substring(firstSpace + 1, lastSpace) : ""; - String[] args = splitArgsRespectingQuotesAndBrackets(argsPart); + Object[] args = (Object[]) ValueSerializer.deserialize(opArgsSegment); + + for (int i = 0; i < args.length; i++) { + Object rawArg = args[i]; + Class argType = method.parameters().get(i).getRawType(); + + if (argType.isEnum()) { + try { + args[i] = ((Object[]) argType.getMethod("values").invoke(null))[(int) rawArg]; + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } Method javaMethod = method.getMethod(); - List parameters = method.parameters(); - List javaParameters = new ArrayList<>(); + Object returnValue = javaMethod.invoke(entity, args); + boolean hasReturn = method.output() != null; - for (int i = 0; i < parameters.size(); i++) { - PrimitiveParameter parameter = parameters.get(i); - - Object value = parameter.type().deserialize(args[i]); - javaParameters.add(value); - } + try { + if (hasReturn) { - Object returnValue = javaMethod.invoke(entity, javaParameters.toArray()); - @SuppressWarnings("unchecked") CommandArgumentType returnType = (CommandArgumentType) method.output(); + if (returnValue instanceof Enum) { + returnValue = ((Enum) returnValue).ordinal(); + } - try { - if (returnType != null) { - String serialize = returnType.serialize(returnValue); + String serialize = ValueSerializer.serialize(returnValue); out.write(serialize); out.write("\n"); out.flush(); @@ -74,48 +88,47 @@ public synchronized static void command(Entity entity, String command, BufferedW * string (e.g. "a b") or inside a bracketed array (e.g. [2:"a b":i3]). Respects backslash-escaping of quotes as produced by * ValueSerializer.serialize (\\ and \"). */ - private static String[] splitArgsRespectingQuotesAndBrackets(String argsPart) - { - if (argsPart.isEmpty()) - return new String[0]; - - List tokens = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - boolean inQuotes = false; - int bracketDepth = 0; - - for (int i = 0; i < argsPart.length(); i++) { - char c = argsPart.charAt(i); - - if (inQuotes) { - current.append(c); - if (c == '\\' && i + 1 < argsPart.length()) { - // Keep the escaped character glued to its backslash to differentiate an escaped quote from for the string end - current.append(argsPart.charAt(++i)); - } else if (c == '"') { - inQuotes = false; - } - continue; - } + private static String[] splitArgsRespectingQuotesAndBrackets(String argsPart) { + if (argsPart.isEmpty()) + return new String[0]; + + List tokens = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + int bracketDepth = 0; + + for (int i = 0; i < argsPart.length(); i++) { + char c = argsPart.charAt(i); + + if (inQuotes) { + current.append(c); + if (c == '\\' && i + 1 < argsPart.length()) { + // Keep the escaped character glued to its backslash to differentiate an escaped quote from for the string end + current.append(argsPart.charAt(++i)); + } else if (c == '"') { + inQuotes = false; + } + continue; + } - if (c == '"') { - inQuotes = true; - current.append(c); - } else if (c == '[') { - bracketDepth++; - current.append(c); - } else if (c == ']') { - bracketDepth--; - current.append(c); - } else if (c == ' ' && bracketDepth == 0) { - tokens.add(current.toString()); - current.setLength(0); - } else { - current.append(c); + if (c == '"') { + inQuotes = true; + current.append(c); + } else if (c == '[') { + bracketDepth++; + current.append(c); + } else if (c == ']') { + bracketDepth--; + current.append(c); + } else if (c == ' ' && bracketDepth == 0) { + tokens.add(current.toString()); + current.setLength(0); + } else { + current.append(c); + } } - } - tokens.add(current.toString()); + tokens.add(current.toString()); - return tokens.toArray(new String[0]); + return tokens.toArray(new String[0]); } } From 4e01d3bd374ec980693a874629f1472f141a26bf Mon Sep 17 00:00:00 2001 From: Wyrdix Date: Mon, 13 Jul 2026 21:03:17 +0200 Subject: [PATCH 2/2] Remove CommandArgumentType.java --- src/plm/core/lang/LangC.java | 88 ++-- src/plm/core/lang/LangJava.java | 480 +++++++++--------- .../lang/primitives/CommandArgumentType.java | 156 ------ .../primitives/ExternalPrimitiveLanguage.java | 4 +- .../core/lang/primitives/PrimitiveMethod.java | 21 +- .../lang/primitives/PrimitiveParameter.java | 16 +- src/plm/universe/CommandExecutor.java | 7 +- src/plm/universe/EntityPrimitivesBase.java | 8 +- 8 files changed, 304 insertions(+), 476 deletions(-) delete mode 100644 src/plm/core/lang/primitives/CommandArgumentType.java diff --git a/src/plm/core/lang/LangC.java b/src/plm/core/lang/LangC.java index d8b8e4f7f..5935e363e 100644 --- a/src/plm/core/lang/LangC.java +++ b/src/plm/core/lang/LangC.java @@ -1,7 +1,6 @@ package plm.core.lang; import plm.core.PLMCompilerException; -import plm.core.lang.primitives.CommandArgumentType; import plm.core.lang.primitives.ExternalPrimitiveLanguage; import plm.core.lang.primitives.PrimitiveMethod; import plm.core.lang.primitives.PrimitiveParameter; @@ -13,8 +12,10 @@ import plm.core.model.session.SourceFile; import plm.core.ui.ResourcesCache; import plm.universe.CommandExecutor; +import plm.universe.Direction; import plm.universe.Entity; +import java.awt.*; import java.io.*; import java.nio.file.Files; import java.util.List; @@ -185,13 +186,13 @@ private void compile(String code, String executable, Exercise exo) throws PLMCom arg1[0] = "cmd.exe"; arg1[1] = "/c"; arg1[2] = - "gcc -g -x c -Wall -lm -lpthread -fsanitize=address -o \"" + exec + "\" " + compiled_code_name; + "gcc -g -x c -Wall -lm -lpthread -fsanitize=address -o \"" + exec + "\" " + compiled_code_name; } else { arg1 = new String[3]; arg1[0] = "/bin/sh"; arg1[1] = "-c"; arg1[2] = - "gcc -g -x c -Wall -lm -lpthread -fsanitize=address -o \"" + exec + "\" " + compiled_code_name; + "gcc -g -x c -Wall -lm -lpthread -fsanitize=address -o \"" + exec + "\" " + compiled_code_name; } final Process process = runtime.exec(arg1); @@ -384,21 +385,21 @@ public void run() { public static class LangCExternalPrimitiveGenerator implements ExternalPrimitiveLanguage { - String getLanguageType(CommandArgumentType type) { - if (type == CommandArgumentType.COLOR) return "Color"; - if (type == CommandArgumentType.DIRECTION) return "Direction"; - if (type == CommandArgumentType.DOUBLE) return "double"; - if (type == CommandArgumentType.INT) return "int"; - if (type == CommandArgumentType.STRING) return "char*"; - if (type == CommandArgumentType.CHAR) return "char"; - if (type == CommandArgumentType.BOOLEAN) - return "int"; + String getLanguageType(Class type) { + if (type == Color.class) return "Color"; + if (type == Direction.class) return "Direction"; + if (type == Double.class || type == double.class) return "double"; + if (type == Integer.class || type == int.class) return "int"; + if (type == String.class) return "char*"; + if (type == Character.class || type == char.class) return "char"; + if (type == Boolean.class || type == boolean.class) + return "int"; throw new IllegalStateException("Unknown type: " + type); } - String getTypeDeclaration(CommandArgumentType type) { - if (type == CommandArgumentType.DIRECTION) { + String getTypeDeclaration(Class type) { + if (type == Direction.class) { return "typedef enum{\n" + " NORTH,\n" + " EAST,\n" + @@ -406,7 +407,7 @@ String getTypeDeclaration(CommandArgumentType type) { " WEST\n" + "} Direction;"; } - if (type == CommandArgumentType.COLOR) { + if (type == Color.class) { return "typedef enum{\n" + " white,\n" + " black,\n" + @@ -433,39 +434,38 @@ String getParameter(PrimitiveParameter parameter) { String getPrototype(PrimitiveMethod method) { String name = method.name(); List parameters = method.parameters(); - CommandArgumentType output = method.output(); - + Class output = method.output(); final String outputString = Optional.ofNullable(output).map(this::getLanguageType).orElse("void"); return outputString + " " + name + "(" + parameters.stream().map(this::getParameter).collect(Collectors.joining(", ")) + ");"; } - String getReturning(CommandArgumentType type) { + String getReturning(Class type) { if (type == null) return ""; - if (type == CommandArgumentType.STRING) return "get_answer_string()"; - if (type == CommandArgumentType.DOUBLE) return "get_answer_double()"; - if (type == CommandArgumentType.CHAR) return "get_answer_char()"; - if (type == CommandArgumentType.COLOR) return "get_answer_int()"; - if (type == CommandArgumentType.DIRECTION) return "get_answer_int()"; - if (type == CommandArgumentType.INT) return "get_answer_int()"; - if (type == CommandArgumentType.BOOLEAN) - return "get_answer_int()"; + if (type == String.class) return "get_answer_string()"; + if (type == Double.class || type == double.class) return "get_answer_double()"; + if (type == Character.class || type == char.class) return "get_answer_char()"; + if (type == Color.class) return "get_answer_int()"; + if (type == Direction.class) return "get_answer_int()"; + if (type == Integer.class || type == int.class) return "get_answer_int()"; + if (type == Boolean.class || type == boolean.class) + return "get_answer_int()"; throw new IllegalStateException("Unknown type: " + type); } - String getTemplatingForType(CommandArgumentType type) { - if (type == CommandArgumentType.STRING) return "%s"; - if (type == CommandArgumentType.DOUBLE) return "%lf"; - if (type == CommandArgumentType.CHAR) return "%c"; - if (type == CommandArgumentType.COLOR) return "%d"; - if (type == CommandArgumentType.DIRECTION) return "%d"; - if (type == CommandArgumentType.INT) return "%d"; - if (type == CommandArgumentType.BOOLEAN) - return "%d"; + String getTemplatingForType(Class type) { + if (type == String.class) return "%s"; + if (type == Double.class || type == double.class) return "%lf"; + if (type == Character.class || type == char.class) return "%c"; + if (type == Color.class) return "%d"; + if (type == Direction.class) return "%d"; + if (type == Integer.class || type == int.class) return "%d"; + if (type == Boolean.class || type == boolean.class) + return "%d"; throw new IllegalStateException("Unknown type: " + type); } @@ -482,26 +482,26 @@ String getImplementation(PrimitiveMethod method) { String command = "\tsend_command(\"" + id + " " + formats + name - + "\"" + method.parameters().stream().map(PrimitiveParameter::name).map(s -> ", "+s).collect(Collectors.joining()) + ");"; + + "\"" + method.parameters().stream().map(PrimitiveParameter::name).map(s -> ", " + s).collect(Collectors.joining()) + ");"; - String returning = method.output() != null ? "\treturn " + getReturning(method.output()) + ";" : ""; + String returning = method.hasReturn() ? "\treturn " + getReturning(method.output()) + ";" : ""; return prototype + "{\n" + command + "\n" + returning + "\n}"; } @Override public void generate(File folder, String name, List methods) throws IOException { - Set> involved = ExternalPrimitiveLanguage.involved(methods); + Set> involved = ExternalPrimitiveLanguage.involved(methods); final String guard = name.toUpperCase() + "_H"; final String header_prefix = "/* THIS FILE IS GENERATED. DO NOT EDIT */\n#ifndef " + guard + "\n" - + "#define " + guard + "\n" - + "\n" - + "#include \n" - + "#include \n" - + "#include \n" - + "#include "; + + "#define " + guard + "\n" + + "\n" + + "#include \n" + + "#include \n" + + "#include \n" + + "#include "; final String header_suffix = "#endif"; final String type_declarations = involved.stream().map(this::getTypeDeclaration) diff --git a/src/plm/core/lang/LangJava.java b/src/plm/core/lang/LangJava.java index 63c1b2e7d..bef8a9e35 100644 --- a/src/plm/core/lang/LangJava.java +++ b/src/plm/core/lang/LangJava.java @@ -1,26 +1,7 @@ package plm.core.lang; -import java.awt.*; -import java.io.*; -import java.net.StandardProtocolFamily; -import java.net.UnixDomainSocketAddress; -import java.nio.channels.Channels; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.tools.DiagnosticCollector; -import javax.tools.JavaFileObject; import org.checkerframework.checker.nullness.qual.NonNull; import plm.core.PLMCompilerException; -import plm.core.lang.primitives.CommandArgumentType; import plm.core.lang.primitives.ExternalPrimitiveLanguage; import plm.core.lang.primitives.PrimitiveMethod; import plm.core.lang.primitives.PrimitiveParameter; @@ -31,38 +12,56 @@ import plm.core.model.lesson.RunOutcome; import plm.core.model.session.SourceFile; import plm.core.ui.ResourcesCache; -import plm.core.utils.ColorMapper; import plm.universe.CommandExecutor; +import plm.universe.Direction; import plm.universe.Entity; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaFileObject; +import java.awt.*; +import java.io.*; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + public class LangJava extends JVMCompiledLang { + /** + * Extra source files to be copied alongside the student's code + */ + private static final Map> remoteExtraSourceFiles = Map.of("RemoteCons", List.of("src/lessons/recursion/cons/universe/RecList.java")); /* Language detection logic */ private static String brokenLanguageMessage; private static BrokenLanguageState brokenLanguageState = BrokenLanguageState.Unitialized; File tempFolder = new File(System.getProperty("java.io.tmpdir"), "plm_java_toremove"); - /** Extra source files to be copied alongside the student's code */ - private static final Map> remoteExtraSourceFiles = Map.of("RemoteCons", List.of("src/lessons/recursion/cons/universe/RecList.java")); - - /** e.g. "src/lessons/recursion/cons/universe/RecList.java" -> "lessons.recursion.cons.universe.RecList" */ - private static String fqcnFromSourcePath(String sourcePath) - { - String withoutSrcPrefix = sourcePath.startsWith("src/") ? sourcePath.substring("src/".length()) : sourcePath; - String withoutExtension = - withoutSrcPrefix.endsWith(".java") ? withoutSrcPrefix.substring(0, withoutSrcPrefix.length() - ".java".length()) : withoutSrcPrefix; - return withoutExtension.replace('/', '.'); + public LangJava() { + super("Java", "java", ResourcesCache.getIcon("img/lang_java.png")); } - /** e.g. "src/lessons/recursion/cons/universe/RecList.java" -> "RecList" */ - private static String fileNameWithoutExtension(String path) - { - String name = new File(path).getName(); - int dot = name.lastIndexOf('.'); - return dot < 0 ? name : name.substring(0, dot); + /** + * e.g. "src/lessons/recursion/cons/universe/RecList.java" -> "lessons.recursion.cons.universe.RecList" + */ + private static String fqcnFromSourcePath(String sourcePath) { + String withoutSrcPrefix = sourcePath.startsWith("src/") ? sourcePath.substring("src/".length()) : sourcePath; + String withoutExtension = + withoutSrcPrefix.endsWith(".java") ? withoutSrcPrefix.substring(0, withoutSrcPrefix.length() - ".java".length()) : withoutSrcPrefix; + return withoutExtension.replace('/', '.'); } - public LangJava() { - super("Java", "java", ResourcesCache.getIcon("img/lang_java.png")); + /** + * e.g. "src/lessons/recursion/cons/universe/RecList.java" -> "RecList" + */ + private static String fileNameWithoutExtension(String path) { + String name = new File(path).getName(); + int dot = name.lastIndexOf('.'); + return dot < 0 ? name : name.substring(0, dot); } private static @NonNull String getCorrectedTemplate(String correction) { @@ -136,32 +135,31 @@ private static String extractImportDependency(String code) { return section.toString(); } - private static String getRemote(String code) - { - if (code.contains("plm.test.simple")) - return "RemoteSimple"; - if (code.contains(".bat.")) - return "RemoteBat"; - if (code.contains(".cons.")) - return "RemoteCons"; - if (code.contains("Buggle") || code.contains("Langton") || code.contains("Turmite")) - return "RemoteBuggle"; - if (code.contains("Turtle")) - return "RemoteTurtle"; - if (code.contains("Flag")) - return "RemoteFlag"; - if (code.contains("Baseball")) - return "RemoteBaseball"; - if (code.contains("Pancake")) - return "RemotePancake"; - if (code.contains("Hanoi")) - return "RemoteHanoi"; - if (code.contains("Sort")) - return "RemoteSort"; - // if (code.contains("Lander")) - // return "RemoteLander"; - - return null; + private static String getRemote(String code) { + if (code.contains("plm.test.simple")) + return "RemoteSimple"; + if (code.contains(".bat.")) + return "RemoteBat"; + if (code.contains(".cons.")) + return "RemoteCons"; + if (code.contains("Buggle") || code.contains("Langton") || code.contains("Turmite")) + return "RemoteBuggle"; + if (code.contains("Turtle")) + return "RemoteTurtle"; + if (code.contains("Flag")) + return "RemoteFlag"; + if (code.contains("Baseball")) + return "RemoteBaseball"; + if (code.contains("Pancake")) + return "RemotePancake"; + if (code.contains("Hanoi")) + return "RemoteHanoi"; + if (code.contains("Sort")) + return "RemoteSort"; + // if (code.contains("Lander")) + // return "RemoteLander"; + + return null; } private static void compileJavaFiles(DiagnosticCollector diagnostic, File packageFolder, File... files) throws PLMCompilerException { @@ -303,11 +301,10 @@ public String getRemoteJavaFile(String remoteName, String packageName) { return remoteCode; } - private void copyFile(File name, String path, String packageName) throws IOException - { - String content = Files.readString(new File(path).toPath(), StandardCharsets.UTF_8); - content = content.replaceFirst("package .*;", "package " + packageName + ";\n"); - Files.writeString(name.toPath(), content); + private void copyFile(File name, String path, String packageName) throws IOException { + String content = Files.readString(new File(path).toPath(), StandardCharsets.UTF_8); + content = content.replaceFirst("package .*;", "package " + packageName + ";\n"); + Files.writeString(name.toPath(), content); } public void compileExo(Exercise exo, LogWriter out, StudentOrCorrection whatToCompile) throws PLMCompilerException { @@ -320,7 +317,8 @@ public void compileExo(Exercise exo, LogWriter out, StudentOrCorrection whatToCo runtimePatterns.put("\\$package", "package " + packageNameCache + ";"); String mainRemoteContent = getRemoteJavaFile(null, packageNameCache) - .replace("import static ValueSerializer.*;", "import static " + packageNameCache + ".ValueSerializer.*;");; + .replace("import static ValueSerializer.*;", "import static " + packageNameCache + ".ValueSerializer.*;"); + ; DiagnosticCollector diagnostic = new DiagnosticCollector(); try { @@ -343,10 +341,10 @@ public void compileExo(Exercise exo, LogWriter out, StudentOrCorrection whatToCo runtimePatterns.put("\\$run", runFunction); runtimePatterns.put("\\$dependency", dependency); runtimePatterns.put("\\$imports", ("import static " + packageNameCache + ".ValueSerializer.*;\n" - + "import java.awt.Color;\n" - + "import static " + packageNameCache + ".Remote.*;\n" - + "import static " + packageNameCache + "." + remote + ".*;\n" + imports) - .replace('\n', ' ')); + + "import java.awt.Color;\n" + + "import static " + packageNameCache + ".Remote.*;\n" + + "import static " + packageNameCache + "." + remote + ".*;\n" + imports) + .replace('\n', ' ')); String template = getCorrectedTemplate(correction); sf.setTemplate(template); @@ -370,59 +368,59 @@ public void compileExo(Exercise exo, LogWriter out, StudentOrCorrection whatToCo File mainFile = new File(workspace, "Main.java"); String mainContent = "package " + packageNameCache + ";\n" - + "import " + packageNameCache + ".Entity;\n" - + "import " + packageNameCache + ".Remote;\n" - + "\n" - + "public class Main {\n" - + " public static void main(String[] args) {\n" - + " try {\n" - + " Remote.connect(args[0]);\n" - + " new Entity().run();\n" - + " } catch (Exception e) {\n" - + " e.printStackTrace();\n" - + " System.exit(1);\n" - + " }\n" - + " System.exit(0);\n" - + " }\n" - + "}\n"; + + "import " + packageNameCache + ".Entity;\n" + + "import " + packageNameCache + ".Remote;\n" + + "\n" + + "public class Main {\n" + + " public static void main(String[] args) {\n" + + " try {\n" + + " Remote.connect(args[0]);\n" + + " new Entity().run();\n" + + " } catch (Exception e) {\n" + + " e.printStackTrace();\n" + + " System.exit(1);\n" + + " }\n" + + " System.exit(0);\n" + + " }\n" + + "}\n"; try { - File valueSerializer = new File(workspace, "ValueSerializer.java"); - copyFile(valueSerializer, "src/plm/core/ValueSerializer.java", packageNameCache); - - List extraFiles = new ArrayList<>(); - for (String sourcePath : remoteExtraSourceFiles.getOrDefault(remote, List.of())) { - File extraFile = new File(workspace, new File(sourcePath).getName()); - copyFile(extraFile, sourcePath, packageNameCache); - extraFiles.add(extraFile); - - // Change the existing own source imports (e.g. "lessons.recursion.cons.universe.RecList") to the local one we just copied. - String originalFqcn = fqcnFromSourcePath(sourcePath); - String simpleName = fileNameWithoutExtension(sourcePath); - entityCode = entityCode.replace("import " + originalFqcn + ";", "import " + packageNameCache + "." + simpleName + ";"); - } - - Files.writeString(new File(workspace, "Template.txt").toPath(), template); - Files.writeString(new File(workspace, "Correction.txt").toPath(), correction); - Files.writeString(mainRemote.toPath(), mainRemoteContent); - Files.writeString(entityRemote.toPath(), entityRemoteContent); - Files.writeString(entityFile.toPath(), entityCode); - Files.writeString(mainFile.toPath(), mainContent); - - List filesToCompile = new ArrayList<>(List.of(mainFile, mainRemote, entityRemote, entityFile, valueSerializer)); - filesToCompile.addAll(extraFiles); - compileJavaFiles(diagnostic, workspace, filesToCompile.toArray(File[] ::new)); - - File jarFile = new File(workspace, "Code.jar"); - List filesToJar = - new ArrayList<>(List.of(entityFile, mainRemote, entityRemote, valueSerializer, new File(workspace, "ValueSerializer$Parser.class"))); - filesToJar.addAll(extraFiles); - createJarFile(diagnostic, tempFolder, workspace, jarFile, mainFile, filesToJar.toArray(File[] ::new)); - - sf.meta.put("JAVA", jarFile.toPath().toString()); + File valueSerializer = new File(workspace, "ValueSerializer.java"); + copyFile(valueSerializer, "src/plm/core/ValueSerializer.java", packageNameCache); + + List extraFiles = new ArrayList<>(); + for (String sourcePath : remoteExtraSourceFiles.getOrDefault(remote, List.of())) { + File extraFile = new File(workspace, new File(sourcePath).getName()); + copyFile(extraFile, sourcePath, packageNameCache); + extraFiles.add(extraFile); + + // Change the existing own source imports (e.g. "lessons.recursion.cons.universe.RecList") to the local one we just copied. + String originalFqcn = fqcnFromSourcePath(sourcePath); + String simpleName = fileNameWithoutExtension(sourcePath); + entityCode = entityCode.replace("import " + originalFqcn + ";", "import " + packageNameCache + "." + simpleName + ";"); + } + + Files.writeString(new File(workspace, "Template.txt").toPath(), template); + Files.writeString(new File(workspace, "Correction.txt").toPath(), correction); + Files.writeString(mainRemote.toPath(), mainRemoteContent); + Files.writeString(entityRemote.toPath(), entityRemoteContent); + Files.writeString(entityFile.toPath(), entityCode); + Files.writeString(mainFile.toPath(), mainContent); + + List filesToCompile = new ArrayList<>(List.of(mainFile, mainRemote, entityRemote, entityFile, valueSerializer)); + filesToCompile.addAll(extraFiles); + compileJavaFiles(diagnostic, workspace, filesToCompile.toArray(File[]::new)); + + File jarFile = new File(workspace, "Code.jar"); + List filesToJar = + new ArrayList<>(List.of(entityFile, mainRemote, entityRemote, valueSerializer, new File(workspace, "ValueSerializer$Parser.class"))); + filesToJar.addAll(extraFiles); + createJarFile(diagnostic, tempFolder, workspace, jarFile, mainFile, filesToJar.toArray(File[]::new)); + + sf.meta.put("JAVA", jarFile.toPath().toString()); } catch (IOException e) { - throw new RuntimeException(e); + throw new RuntimeException(e); } } } catch (PLMCompilerException e) { @@ -484,19 +482,19 @@ public void runEntity(final Entity ent, final RunOutcome progress) { String cmd = executable; File exec = new File(cmd); if (!exec.exists()) - throw new RuntimeException(Game.i18n.tr("Error, please recompile the exercise: {0} does not exist", exec.getName())); + throw new RuntimeException(Game.i18n.tr("Error, please recompile the exercise: {0} does not exist", exec.getName())); // Set up the protocol socket (AF_UNIX) that the child JVM will connect to. // Its path is unique per run and is passed to the child as args[0]. - Path socketDir = Files.createTempDirectory("plm-java-sock-"); - Path socketPath = socketDir.resolve("protocol.sock"); + Path socketDir = Files.createTempDirectory("plm-java-sock-"); + Path socketPath = socketDir.resolve("protocol.sock"); ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); serverChannel.bind(UnixDomainSocketAddress.of(socketPath)); serverChannel.configureBlocking(false); Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); - ProcessBuilder pb = new ProcessBuilder("java", "-jar", cmd, socketPath.toString()); + ProcessBuilder pb = new ProcessBuilder("java", "-jar", cmd, socketPath.toString()); final Process process = pb.start(); final int ACCEPT_TIMEOUT_MS = 10000; @@ -506,88 +504,85 @@ public void runEntity(final Entity ent, final RunOutcome progress) { serverChannel.close(); if (protocolChannel == null) { - process.destroyForcibly(); - Files.deleteIfExists(socketPath); - Files.deleteIfExists(socketDir); - progress.outcome = RunOutcome.kind.FAIL; - progress.executionError = Game.i18n.tr("Protocol connection failed: the program never connected to the PLM."); - return; + process.destroyForcibly(); + Files.deleteIfExists(socketPath); + Files.deleteIfExists(socketDir); + progress.outcome = RunOutcome.kind.FAIL; + progress.executionError = Game.i18n.tr("Protocol connection failed: the program never connected to the PLM."); + return; } final SocketChannel finalProtocolChannel = protocolChannel; final BufferedWriter bwriter = new BufferedWriter(new OutputStreamWriter(Channels.newOutputStream(finalProtocolChannel), StandardCharsets.UTF_8)); Thread stdoutReader = new Thread() { - public void run() - { - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - try { - String str; - while ((str = reader.readLine()) != null) - System.out.println(str); - } finally { - reader.close(); - } - } catch (Throwable t) { - t.printStackTrace(); - progress.outcome = RunOutcome.kind.FAIL; - progress.executionError = t.getMessage(); - process.destroyForcibly(); + public void run() { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + try { + String str; + while ((str = reader.readLine()) != null) + System.out.println(str); + } finally { + reader.close(); + } + } catch (Throwable t) { + t.printStackTrace(); + progress.outcome = RunOutcome.kind.FAIL; + progress.executionError = t.getMessage(); + process.destroyForcibly(); + } } - } }; Thread stderrReader = new Thread() { - public void run() - { - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); - try { - String str; - while ((str = reader.readLine()) != null) - System.err.println(str); - } finally { - reader.close(); - } - } catch (Throwable t) { - t.printStackTrace(); + public void run() { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + try { + String str; + while ((str = reader.readLine()) != null) + System.err.println(str); + } finally { + reader.close(); + } + } catch (Throwable t) { + t.printStackTrace(); + } } - } }; Thread commandReader = new Thread() { - public void run() - { - BufferedReader reader = new BufferedReader(new InputStreamReader(Channels.newInputStream(finalProtocolChannel), StandardCharsets.UTF_8)); - Exception parseError = null; - String str = ""; - try { - while ((str = reader.readLine()) != null) { - // System.out.println("EXECUTING COMMAND: " + str); - CommandExecutor.command(ent, str, bwriter); - // System.out.println("COMMAND EXECUTED"); - } - } catch (Exception e) { - parseError = e; - e.printStackTrace(); - progress.outcome = RunOutcome.kind.FAIL; - progress.executionError = e.getMessage(); - process.destroyForcibly(); - } - if (parseError != null) { - StringBuffer sb = new StringBuffer(str + "\n"); - try { - while ((str = reader.readLine()) != null) - sb.append(str + "\n"); - } catch (IOException ioe) { - System.err.println("Exception while handling the exception. Bailing out"); - parseError.printStackTrace(); - ioe.printStackTrace(); - } - throw new RuntimeException("Parse error while reading the command: " + sb.toString(), parseError); + public void run() { + BufferedReader reader = new BufferedReader(new InputStreamReader(Channels.newInputStream(finalProtocolChannel), StandardCharsets.UTF_8)); + Exception parseError = null; + String str = ""; + try { + while ((str = reader.readLine()) != null) { + // System.out.println("EXECUTING COMMAND: " + str); + CommandExecutor.command(ent, str, bwriter); + // System.out.println("COMMAND EXECUTED"); + } + } catch (Exception e) { + parseError = e; + e.printStackTrace(); + progress.outcome = RunOutcome.kind.FAIL; + progress.executionError = e.getMessage(); + process.destroyForcibly(); + } + if (parseError != null) { + StringBuffer sb = new StringBuffer(str + "\n"); + try { + while ((str = reader.readLine()) != null) + sb.append(str + "\n"); + } catch (IOException ioe) { + System.err.println("Exception while handling the exception. Bailing out"); + parseError.printStackTrace(); + ioe.printStackTrace(); + } + throw new RuntimeException("Parse error while reading the command: " + sb.toString(), parseError); + } } - } }; stdoutReader.start(); @@ -606,7 +601,7 @@ public void run() Files.deleteIfExists(socketDir); if (retcode != 0) - progress.setExecutionError("An issue occured in the executed code. Check the output in the log panel for more info"); + progress.setExecutionError("An issue occured in the executed code. Check the output in the log panel for more info"); if (resEvaluationError.length() > 0) { System.err.println(resEvaluationError.toString()); @@ -621,21 +616,21 @@ public void run() public static class LangJavaExternalPrimitiveGenerator implements ExternalPrimitiveLanguage { - String getLanguageType(CommandArgumentType type) { - if (type == CommandArgumentType.COLOR) return "Color"; - if (type == CommandArgumentType.DIRECTION) return "int"; - if (type == CommandArgumentType.DOUBLE) return "double"; - if (type == CommandArgumentType.INT) return "int"; - if (type == CommandArgumentType.STRING) return "String"; - if (type == CommandArgumentType.CHAR) return "char"; - if (type == CommandArgumentType.BOOLEAN) + String getLanguageType(Class type) { + if (type == Color.class) return "Color"; + if (type == Direction.class) return "int"; + if (type == Double.class || type == double.class) return "double"; + if (type == Integer.class || type == int.class) return "int"; + if (type == String.class) return "String"; + if (type == Character.class || type == char.class) return "char"; + if (type == Boolean.class || type == boolean.class) return "boolean"; throw new IllegalStateException("Unknown type: " + type); } - String getTypeDeclaration(CommandArgumentType type) { - if (type == CommandArgumentType.DIRECTION) { + String getTypeDeclaration(Class type) { + if (type == Direction.class) { return "public static class Direction {\n" + "\tstatic final int NORTH = 0;\n" + "\tstatic final int EAST = 1;\n" + @@ -653,7 +648,7 @@ String getParameter(PrimitiveParameter parameter) { String getPrototype(PrimitiveMethod method) { String name = method.name(); List parameters = method.parameters(); - CommandArgumentType output = method.output(); + Class output = method.output(); final String outputString = Optional.ofNullable(output).map(this::getLanguageType).orElse("void"); @@ -661,41 +656,27 @@ String getPrototype(PrimitiveMethod method) { return "public static " + outputString + " " + name + "(" + parameters.stream().map(this::getParameter).collect(Collectors.joining(", ")) + ")"; } - String getReturning(CommandArgumentType type) { + String getReturning(Class type) { if (type == null) return ""; - if (type == CommandArgumentType.STRING) return "getAnswerString()"; - if (type == CommandArgumentType.DOUBLE) return "getAnswerDouble()"; - if (type == CommandArgumentType.CHAR) return "getAnswerChar()"; - if (type == CommandArgumentType.COLOR) return "getAnswerColor()"; - if (type == CommandArgumentType.DIRECTION) return "getAnswerInt()"; - if (type == CommandArgumentType.INT) return "getAnswerInt()"; - if (type == CommandArgumentType.BOOLEAN) + if (type == String.class) return "getAnswerString()"; + if (type == Double.class || type == double.class) return "getAnswerDouble()"; + if (type == Character.class || type == char.class) return "getAnswerChar()"; + if (type == Color.class) return "getAnswerColor()"; + if (type == Direction.class) return "getAnswerInt()"; + if (type == Integer.class || type == int.class) return "getAnswerInt()"; + if (type == Boolean.class || type == boolean.class) return "getAnswerBoolean()"; throw new IllegalStateException("Unknown type: " + type); } - String getTemplatingForType(CommandArgumentType type) { - if (type == CommandArgumentType.STRING) return "%s"; - if (type == CommandArgumentType.DOUBLE) return "%f"; - if (type == CommandArgumentType.CHAR) return "%c"; - if (type == CommandArgumentType.COLOR) return "%d"; - if (type == CommandArgumentType.DIRECTION) return "%d"; - if (type == CommandArgumentType.INT) return "%d"; - if (type == CommandArgumentType.BOOLEAN) - return "%d"; - - throw new IllegalStateException("Unknown type: " + type); - } - - String getArgumentExpression(PrimitiveParameter parameter) - { - // BOOLEAN is templated as "%d" over the wire, so we must convert any boolean to an int, or String.format will raise an error - if (parameter.type() == CommandArgumentType.BOOLEAN) - return "(" + parameter.name() + " ? 1 : 0)"; - return parameter.name(); + String getArgumentExpression(PrimitiveParameter parameter) { + // BOOLEAN is templated as "%d" over the wire, so we must convert any boolean to an int, or String.format will raise an error + if (parameter.type() == Boolean.class || parameter.type() == boolean.class) + return "(" + parameter.name() + " ? 1 : 0)"; + return parameter.name(); } String getImplementation(PrimitiveMethod method) { @@ -705,32 +686,35 @@ String getImplementation(PrimitiveMethod method) { String name = method.name(); String command = "\tsendCommand(\"" + id + "\", \"" + name + "\"" + - method.parameters().stream().map(this::getArgumentExpression).map(s -> ", " + s).collect(Collectors.joining()) + ");"; + method.parameters().stream().map(this::getArgumentExpression).map(s -> ", " + s).collect(Collectors.joining()) + ");"; - String returning = method.output() != null ? "\treturn " + getReturning(method.output()) + ";" : ""; + String returning = method.hasReturn() ? "\treturn " + getReturning(method.output()) + ";" : ""; return prototype + "{\n" + command + "\n" + returning + "\n}"; } - @Override public void generate(File folder, String name, List methods) throws IOException { generate(folder, name, methods, ""); } + @Override + public void generate(File folder, String name, List methods) throws IOException { + generate(folder, name, methods, ""); + } - @Override public void generate(File folder, String name, List methods, String extraCode) throws IOException - { - Set> involved = ExternalPrimitiveLanguage.involved(methods); + @Override + public void generate(File folder, String name, List methods, String extraCode) throws IOException { + Set> involved = ExternalPrimitiveLanguage.involved(methods); - final String type_declarations = involved.stream().map(this::getTypeDeclaration).filter(o -> !o.isBlank()).collect(Collectors.joining("\n\n")); + final String type_declarations = involved.stream().map(this::getTypeDeclaration).filter(o -> !o.isBlank()).collect(Collectors.joining("\n\n")); - final String implementations = methods.stream().map(this::getImplementation).collect(Collectors.joining("\n\n")); + final String implementations = methods.stream().map(this::getImplementation).collect(Collectors.joining("\n\n")); - String body = "\n" + type_declarations + "\n" + implementations; - if (!extraCode.isBlank()) - body += "\n" + extraCode; + String body = "\n" + type_declarations + "\n" + implementations; + if (!extraCode.isBlank()) + body += "\n" + extraCode; - final String code = - "/* THIS FILE IS GENERATED. DO NOT EDIT */\nimport static Remote.*;\nimport java.awt.Color;\n\npublic class " + name + " {" + body.replace("\n", "\n\t") + "\n}"; + final String code = + "/* THIS FILE IS GENERATED. DO NOT EDIT */\nimport static Remote.*;\nimport java.awt.Color;\n\npublic class " + name + " {" + body.replace("\n", "\n\t") + "\n}"; - System.err.println("XXX Generating " + folder + "/" + name + ".java"); - Files.writeString(new File(folder, name + ".java").toPath(), code); + System.err.println("XXX Generating " + folder + "/" + name + ".java"); + Files.writeString(new File(folder, name + ".java").toPath(), code); } } diff --git a/src/plm/core/lang/primitives/CommandArgumentType.java b/src/plm/core/lang/primitives/CommandArgumentType.java deleted file mode 100644 index c14c00110..000000000 --- a/src/plm/core/lang/primitives/CommandArgumentType.java +++ /dev/null @@ -1,156 +0,0 @@ -package plm.core.lang.primitives; - -import plm.core.utils.ColorMapper; -import plm.core.utils.InvalidColorNameException; -import plm.universe.Direction; - -import java.awt.*; -import java.util.Arrays; -import java.util.Objects; - -public abstract class CommandArgumentType { - public static final CommandArgumentType INT = new CommandArgumentType<>(0, "INT", int.class) { - @Override - public String serialize(Integer value) { - return String.valueOf(value); - } - - @Override - public Integer deserialize(String value) { - return Integer.parseInt(value); - } - }; - public static final CommandArgumentType DOUBLE = new CommandArgumentType<>(1, "DOUBLE", double.class) { - @Override - public String serialize(Double value) { - return String.valueOf(value); - } - - @Override - public Double deserialize(String value) { - return Double.parseDouble(value); - } - }; - public static final CommandArgumentType STRING = new CommandArgumentType<>(2, "STRING", String.class) { - @Override - public String serialize(String value) { - return value; - } - - @Override - public String deserialize(String value) { - return value; - } - }; - public static final CommandArgumentType CHAR = new CommandArgumentType<>(3, "CHAR", char.class) { - @Override - public String serialize(Character value) { - return String.valueOf(value); - } - - @Override - public Character deserialize(String value) { - return value.charAt(0); - } - }; - public static final CommandArgumentType BOOLEAN = new CommandArgumentType<>(4, "BOOLEAN", boolean.class) { - @Override - public String serialize(Boolean value) { - return value ? "1" : "0"; - } - - @Override - public Boolean deserialize(String value) { - return value.equals("1"); - } - }; - - public static final CommandArgumentType COLOR = new CommandArgumentType<>(5, "COLOR", Color.class) { - @Override - public String serialize(Color value) { - return String.valueOf(ColorMapper.color2int(value)); - } - - @Override - public Color deserialize(String value) { - if (value.indexOf('/') >= 0) { - try { - return ColorMapper.name2color(value); - } catch (InvalidColorNameException e) { - throw new RuntimeException(e); - } - } else { - try { - return ColorMapper.int2color(Integer.parseInt(value)); - } catch (InvalidColorNameException e) { - throw new RuntimeException(e); - } - } - } - }; - - public static final CommandArgumentType DIRECTION = - new CommandArgumentType<>(6, "DIRECTION", Direction.class) { - @Override - public String serialize(Direction value) { - return String.valueOf(value.ordinal()); - } - - @Override - public Direction deserialize(String value) { - - int nb = Integer.parseInt(value); - return Direction.values()[nb]; - } - }; - - private final int ordinal; - private final String name; - private final Class clazz; - - CommandArgumentType(int ordinal, String name, Class clazz) { - this.ordinal = ordinal; - this.name = name; - this.clazz = clazz; - } - - public static CommandArgumentType[] values() { - return new CommandArgumentType[]{INT, DOUBLE, STRING, COLOR, DIRECTION, CHAR, BOOLEAN}; - } - - public static CommandArgumentType getCommandArgumentTypeFromClass(Class clazz) { - return Arrays.stream(values()).filter(type -> type.clazz == clazz || clazz.isAssignableFrom(type.clazz)).findFirst().orElse(null); - } - - public static String findTypeAndSerialize(T value) { - - @SuppressWarnings("unchecked") CommandArgumentType type = (CommandArgumentType) getCommandArgumentTypeFromClass(value.getClass()); - if (type == null) return value.toString(); - return type.serialize(value); - } - - public abstract String serialize(T value); - - public abstract T deserialize(String value); - - public int ordinal() { - return ordinal; - } - - public String name() { - return name; - } - - @Override - public String toString() { - return name.charAt(0) + name.toLowerCase().substring(1); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (CommandArgumentType) obj; - return Objects.equals(this.ordinal, that.ordinal); - } -} diff --git a/src/plm/core/lang/primitives/ExternalPrimitiveLanguage.java b/src/plm/core/lang/primitives/ExternalPrimitiveLanguage.java index 774e36112..62bc76336 100644 --- a/src/plm/core/lang/primitives/ExternalPrimitiveLanguage.java +++ b/src/plm/core/lang/primitives/ExternalPrimitiveLanguage.java @@ -8,8 +8,8 @@ public interface ExternalPrimitiveLanguage { - static Set> involved(List methods) { - Set> types = new HashSet<>(); + static Set> involved(List methods) { + Set> types = new HashSet<>(); for (PrimitiveMethod method : methods) { types.add(method.output()); for (PrimitiveParameter parameter : method.parameters()) { diff --git a/src/plm/core/lang/primitives/PrimitiveMethod.java b/src/plm/core/lang/primitives/PrimitiveMethod.java index b790000ef..657d16a5f 100644 --- a/src/plm/core/lang/primitives/PrimitiveMethod.java +++ b/src/plm/core/lang/primitives/PrimitiveMethod.java @@ -1,5 +1,6 @@ package plm.core.lang.primitives; +import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; @@ -9,10 +10,10 @@ public final class PrimitiveMethod { private final List parameters; - private final CommandArgumentType output; private final String name; private final String location; private final Method method; + private final Class output; public int id; public PrimitiveMethod(Primitive primitive, Method method) { @@ -20,9 +21,13 @@ public PrimitiveMethod(Primitive primitive, Method method) { this.name = primitive.name().isEmpty() ? method.getName() : primitive.name(); this.location = method.getDeclaringClass().getSimpleName() + "::" + name(); this.method = method; - parameters = Arrays.stream(method.getParameters()).map(PrimitiveParameter::new).toList(); - output = Optional.of(method.getReturnType()) - .map(CommandArgumentType::getCommandArgumentTypeFromClass).orElse(null); + this.parameters = Arrays.stream(method.getParameters()).map(PrimitiveParameter::new).toList(); + this.output = method.getReturnType(); + } + + @Nullable + public Class output() { + return output; } public int id() { @@ -41,8 +46,8 @@ public List parameters() { return parameters; } - public CommandArgumentType output() { - return output; + public boolean hasReturn() { + return output != null; } @Override @@ -60,10 +65,10 @@ public int hashCode() { @Override public String toString() { - return name() + "(" + parameters.stream().map(PrimitiveParameter::toString).collect(Collectors.joining(",")) + ")" + ":" + Optional.ofNullable(output).map(CommandArgumentType::toString).orElse("void"); + return name() + "(" + parameters.stream().map(PrimitiveParameter::toString).collect(Collectors.joining(",")) + ")" + ":" + Optional.ofNullable(output).map(Class::getSimpleName).orElse("void"); } - public Method getMethod() { + public Method method() { return method; } } diff --git a/src/plm/core/lang/primitives/PrimitiveParameter.java b/src/plm/core/lang/primitives/PrimitiveParameter.java index 479702753..7a3669526 100644 --- a/src/plm/core/lang/primitives/PrimitiveParameter.java +++ b/src/plm/core/lang/primitives/PrimitiveParameter.java @@ -5,23 +5,17 @@ public final class PrimitiveParameter { private final String name; - private final CommandArgumentType type; - private final Class rawType; + private final Class type; public PrimitiveParameter(Parameter parameter) { this.name = parameter.getName(); - this.rawType = parameter.getType(); - this.type = CommandArgumentType.getCommandArgumentTypeFromClass(parameter.getType()); + this.type = parameter.getType(); } public String name() { return this.name; } - public CommandArgumentType type() { - return type; - } - @Override public boolean equals(Object obj) { if (obj == this) return true; @@ -37,10 +31,10 @@ public int hashCode() { @Override public String toString() { - return name + ": " + type; + return name + ": " + type.getSimpleName(); } - public Class getRawType() { - return rawType; + public Class type() { + return type; } } diff --git a/src/plm/universe/CommandExecutor.java b/src/plm/universe/CommandExecutor.java index 1ee71da31..55f4e81ff 100644 --- a/src/plm/universe/CommandExecutor.java +++ b/src/plm/universe/CommandExecutor.java @@ -51,7 +51,7 @@ public synchronized static void command(Entity entity, String command, BufferedW for (int i = 0; i < args.length; i++) { Object rawArg = args[i]; - Class argType = method.parameters().get(i).getRawType(); + Class argType = method.parameters().get(i).type(); if (argType.isEnum()) { try { @@ -62,12 +62,11 @@ public synchronized static void command(Entity entity, String command, BufferedW } } - Method javaMethod = method.getMethod(); + Method javaMethod = method.method(); Object returnValue = javaMethod.invoke(entity, args); - boolean hasReturn = method.output() != null; try { - if (hasReturn) { + if (method.hasReturn()) { if (returnValue instanceof Enum) { returnValue = ((Enum) returnValue).ordinal(); diff --git a/src/plm/universe/EntityPrimitivesBase.java b/src/plm/universe/EntityPrimitivesBase.java index 281116c3b..bc4fd530f 100644 --- a/src/plm/universe/EntityPrimitivesBase.java +++ b/src/plm/universe/EntityPrimitivesBase.java @@ -1,7 +1,6 @@ package plm.universe; import plm.core.ValueSerializer; -import plm.core.lang.primitives.CommandArgumentType; import plm.core.lang.primitives.Primitive; public interface EntityPrimitivesBase { @@ -32,8 +31,11 @@ default boolean getParamBoolean(int i) { @Primitive(404) default String getParamString(int i) { - return CommandArgumentType.findTypeAndSerialize(getParam(i)); + return (String) getParam(i); } - @Primitive(406) default String getParamSerialized(int i) { return ValueSerializer.serialize(getParam(i)); } + @Primitive(406) + default String getParamSerialized(int i) { + return ValueSerializer.serialize(getParam(i)); + } }