Adding a new programming language - BuggleInc/PLM GitHub Wiki

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. If you start something, please make sure to send pull requests regularly. We'll be able to review your work in its early stage, and the risk of git merge conflicts are reduced if you send your work very often. Also, if you cannot finish that work, someone can continue it in the future instead of leaving it rotting on your hard disk.

Adding your language to the list of known ones

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.

Changing the PLM to execute stuff in your programming language

We now have to fill the methods of your ProgrammingLanguage so that the PLM can use stuff in your language.

Compiling code

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.

Mutating entities

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.

Executing the entity

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, but this is all automatic: don't start a new thread from your code as you already run in a separate thread.

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.

Conclusion on PLM engine changes

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).

Changing the mission editor

Writing mission texts in not trivial because your text has to be adapted to the programming language currently used by the student. This is done through pseudo-wiki markups (while the mission texts are in HTML for now). The following will write "Arrays" in either java or scala, "Lists" in python, and nothing in the other programming languages.

[!java|scala]Arrays[/!][!python]Lists[/!]

In practice, editing a text containing such conditional elements is very difficult. You should instead use the integrated mission text's editor, which main class is plm.core.ui.editor.MissionEditorApp. But before you can use it to your own language, you should update the editor to inform it about the new language (actually, that should be all automatic, but we are not here yet). This will display the parts specific to each languages in a given color, allowing to verify that the text fits in all languages in one glance.

Updating the missions

It's now time to update the 170+ exercises to port them to your language. It comes down to solving all exercises for your language. But you should do it in external files, not within the PLM itself.

Porting the world

Each kind of world exports some builtins that can be used by the entities. The BuggleWorld provides forward() and others, the SortingWorld provides swap(), and so on. You need to ensure that these builtins are available in your language. There is nothing to do for JVMCompiled languages, I guess.

For scripting languages, check the setupBindings() methods of each World class. In most case, you should propagate the orders that you get to the "object" entity, that is injected into your scripting engine.

For external languages, you should be able to reuse what was done for the C language, I guess.

Porting each exercise

You should write a (path)/(exercise)Entity.(extension) file (such as src/lessons/welcome/environment/EnvironmentEntity.f) containing the entity that can solve the exercise. It may seem useless to solve the exercise in your new programming language, but it actually helps checking that every needed builtins are injected in the engine, and that the mission text is clear enough.

As usual when writing an solutionEntity, the template (that is, the initial text provided to the user) should be between lines reading "BEGIN TEMPLATE" and "END TEMPLATE". These should probably be commented in an appropriate way for your programming language. The solution must be within the template, between lines reading "BEGIN SOLUTION" and "END SOLUTION". The text within these markers will be removed before presenting it as initial text to the user.

Everything outside the BEGIN/END TEMPLATE marker will remain as is in the compiled file, and you can use it to define new methods or variables. As usual, check what's done for the other languages to understand in all details how things are supposed to work.

Updating the mission texts

You should also update (path)/(exercise).html that contains the mission text of that lesson. You probably want to use our integrated editor to ensure that you don't break the other languages when adding your. Do not ever change the (path)/(exercise).(lang).html files, but translate the po files instead (please refer to the relevant documentation).

Please try to limit your changes to the mission texts, as they are fully translated in several languages: English, the original that you write, French, that I write and Brazilian, that Fred writes. An aging Italian translation awaits for a new volunteer, if you are interested. Each time you change a single letter in your text, every translator has to review your changes and update their own text. It means that you should change what's needed, but you should really try to limit the amount of changes. The worst that you can do is to fiddle endlessly with the mission text to fit your cosmetic tastes.

Unit testing your work

You should write a new test in plm.core.ExoTest, similar to the other test*Entity(). Don't forget the @Test annotation above it so that junit actually use it.

Once that infrastructure is in place, you have probably several days (weeks?) of work to ensure that all exercises are correctly ported to your language. Good luck -- remember that making your beloved language usable in the PLM will make it easier for others to learn that great language!

⚠️ **GitHub.com Fallback** ⚠️