-
Notifications
You must be signed in to change notification settings - Fork 40
Adding a new programming language
This page shortly describes what's needed to add a new programming language to the PLM.
Please bear in mind that this procedure is not tested every day and that things will certainly differ a bit from what's described here.
Also before starting working this, you should consider that this is a rather long task: Adding the language itself can be fast (depending on the language), but then, you have to port all existing exercises to that new language, including the mission texts. There is over 170 exercises, and all missions represent almost 500kb of raw text. You will have to review all of that, and add specific parts to your language.
If you're still interested, here we go.
The whole logic concerning a given programming language is usually entirely described in a class of the plm.core.lang.* that extends ProgrammingLanguage. Any such class is supposed to be a singleton, created in the plm.core.model.Game class.
If you want to add support for Fortran, just add a plm.core.lang.LangFortran class, and add that line to the top of Game:
public static final ProgrammingLanguage FORTRAN = new LangFortran();
Also add your language to the list of known languages, in the field Game.programmingLanguages below.
You will need to add some implementations of the abstract methods of ProgrammingLanguage. We will fill them up later, but for now you can leave them empty.
The constructor of your language should give some basic information about the language to the PLM. For Python, it reads as follows. The first argument is the name of your language. The second one is the file extension usually integrated to your language while the third argument is a graphical logo associated to your language.
public LangPython() {
super("Python","py",ResourcesCache.getIcon("img/lang_python.png"));
}
Once you've created and registered your language this way, the PLM should be able to pick it up. Try adding a file like src/lessons/welcome/environment/EnvironmentEntity.f (changing the extension with the one that you gave to your programming language), restart the PLM, and enjoy having your language appearing to the list of proposed languages in the mini-combo-box down right.
We now have to fill the methods of your ProgrammingLanguage so that the PLM can use stuff in your language.
That's the role of the compileExo method, of course.
compileExo(Exercise exercise, LogWriter out, StudentOrCorrection whatToCompile) throws PLMCompilerException;
You will probably only use the first parameter, which is the exercise currently compiled. The last argument is passed as is the methods that you will use, as shown below.
It's probably a good idea to check how things are done in the other languages to get the idea. In short, scripting languages (such as python) do nothing in that method. Java and Scala both leverage the in-memory compilers provided by their runtime to compile a string into a bytecode class, and C starts an external compiler to compile on disk (Fortran will probably be rather similar to C, if that's really what you are doing).
The source code can be retrieved with exo.getSourceFilesList(this), which returns the list of all source files associated to the language "this". Get the compilable content for each of these files with something as:
for (plm.core.model.session.SourceFile sf : exo.getSourceFilesList(this)) {
System.out.println("content of file "+sf.getName()+": "+
sf.getCompilableContent(runtimePatterns,whatToCompile)+"; offset:"+
sf.getOffset());
}
The method getCompilableContent() is in charge of some black magic allowing to compile the solution to run the demo, and the student's code when running it. The offset is the amount of lines that got masked before the student code so that we can fix the error message: when Java reports an error at line 21 of a file containing 20 masked line, we should inform the user that there is an error at the first line of what s/he wrote.
List<Entity> mutateEntities(Exercise exercise, List<Entity> old, StudentOrCorrection whatToMutate)
Once we compiled the code that we had to compile, we should mutate the entities, ie recreate any entity of the exercise using the class written by the student. Again, there is nothing to do for scripting language, as we use the regular entities in that case. Same for the C programming language. Only the languages that are compiled into bytecode should do something in that method. Check the examples to understand what's going on, that's maybe unusual but not very complex.
void runEntity(Entity ent, ExecutionProgress progress)
That method is in charge of executing the provided entity, as you had guessed. It usually runs in a separate thread so that all entities of a given world can run in parallel.
The content of that method depends again of your language, so make sure to check what the other languages do. Java and Scala entities are launched by just executing their run() method that was redefined by the student (possibly with some templating). Scripting languages start a script engine, inject the entity into it, run the World.setupBinding() so that the world bindings are injected to the engine, and then launch the script in the engine. C starts an external program and read in a pipe the commands that the student code runs, and reflect them in the JVM. In Lightbot, the run() method of the entity interprets the "source code" without compilation.
Your mileage will certainly vary here, as no language are exactly the same. If possible, try to factorize code between the languages as it is currently done: The class inheritance tree is as follows, actually:
ProgrammingLanguage
+--- JVMCompiledLang
| +--- LangJava
| +--- LangScala
+--- ScriptingLanguage
| +--- LangPython
| +--- LangRuby (yeah, the PLM can do exercises in Ruby, but nobody updated the lessons yet)
|--- LangC
If you were to add Fortran, you should create a new abstract class ExternalLanguage to factorize some code with LangC. LangJS should become a child of ScriptingLanguage (it would actually work out of the box as every JVM contains a working javascript interpreter -- someone has to update the lessons).