Do you edit PG problems using the WeBWorK interface? Do you have trouble finding missing semi-colons because the error line numbers are wrong? Do you grind your teeth every time you try to tab in the editor and you lose focus on the text box? Then this post is for you!
The classic editing environment in all of the WeBWorK PG editors is nothing more than a HTML <textarea>. This leaves a lot to be desired. It doesn't have line numbers, it doesn't have any code coloring, and if you tab you leave the textarea. In an upcoming update we will replace the textarea editor with a CodeMirror based one. CodeMirror is a Javascript based editor that supports coloring, line numbers, regex based search and replace, and many other features. Rather than just posting a screen shot, I've included a functional version of the editor below.
Lets go through some of the new features. Feel free to experiment and explore with the demo above as you follow along.
First, notice that the editor supports code coloring. Most of the code coloring is perl based, however there are a couple of twists. You may notice that both the BEGIN_TEXT/END_TEXT block and the BEGIN_PGML_SOLUTION/END_PGML_SOLUTIION blocks are colored. There have been PG specific upgrades to the coloring code which highlight variables, latex, ascii math, as well as command subsitution in both the classical EV3 blocks as well as newer PGML blocks. The PGML coloring in particular is fancy since it correctly colors nested blocks.
One nice addition is line numbers. However, another behind the scenes upgrade is a fix to a long standing bug which caused the line number errors in PG error messages to not match the line numbers in the original code. So now if you miss a semicolin somewhere in your code, the perl error line number should be closer to the actual error.
Changing over to a Javascript editor means that the browser based find function no longer interacts with the editor. Luckily the Codemirror editor has its own search and replace functionality which is even more powerful than the browser search. For example, use Ctl-F and search for Text. You should see the words heighlighted as well as indications on the scroll bar. Next do a search replace with Shift-Ctl-F and search using the Regular Expression /\[`([^`]+)`\]/ and replace with [``$1``]. This will replace all regular PGML Latex with display style PGML Latex. These sorts of Regular Expression manipulations can be really powerful once you get used to them. You can read more here if you haven't seen them before.
In PGML spaces matter. You leave two trailing spaces on a line to create a newline and you leave three to have a new paragraph. Since you can't usually "see" trailing spaces when in a PGML block the editor will highlight trailing whitespace with a red line.
A couple of other common code editor features that are included are bracket matching and tabbing. When you type or move past brackets the matching bracket will be highlighted. When you press tab a soft tab consisting of four spaces will be generated. Finally when you are on a tabbed line and create a new line it will automatically start with the same indentation as the previous one.
There are other features that could be added, if there ends up being enough interest. The most interesting, but also the most complicated, would be a autocompletion. The main issue with this feature is it would have to be aware of what macros were loaded, and if the macros ever changed then the code completion might be wrong. Another potential edition would be true smart tabbing which would try to parse the code and only insert tabs when appropriate.
Do you dream of using WeBWorK to teach students Python? If so then you have very specific dreams. After the jump I will talk about a new set of macros recently created for use in CS courses at WCU. Rather than creating an automated program tester, of which there are many, these macros are really aimed at filling the gap between labs and projects that we have in our introductory computer science courses.
IntroductionIn our introductory computer science courses there seems to be something of a gap between lecture and labs, where students are introduced to new features and concepts in Python, and class projects, which are larger relatively involved programs meant to test and expand students knowledge of the concepts. Outside of weekly labs there isn't any opportunity for students to practice basic syntax and concepts, however. Our goal is to create weekly homework assignments which will help students improve their fundamentals. Of course we want to bring along the instantaneous grading and feedback that are a hallmark of WeBWorK. To accomplish this we have created a collection of PG macros. The macros provide access to two main types objects, the first is the PythonOutput object. The pg file will include a short Python script that will be used to create the object. The code is then run and the standard output of the script will be the correct answer for the problem. The basic idea is that you will present the code to students and ask them what the output will be. For example: This problem is testing if the student understands the formatting syntax for Python strings. The students answer will be compared exactly, using none of the smart comparisons you might usually expect from WeBWorK. (Of course students could just run the code and check the output, but that is not the absolute worst learning outcome.)
The second type of object is a PythonCode. The pg file will include a short Python script that is used to create the object, just as before. However, in this case the student's will provide their own python script. The output of the student's Python script will be compared to the output of the "correct" Python script. The basic template for this kind of problem is that students will be provided with a description of what their code should do and will need to write a script to fulfil that description. This problem asks students to create a script which reads the first argument provided to the script, and if it is equal to 1 then print out the contents of the standard input, and print "hello" otherwise. (You can specify the arguments and contents of stdin in the pg file; more on that later.) These types of problems will probably work better if students are asked to write relatively short scripts, however there is support for running fairly complicated scripts including running multiple test cases.
Of course, since you are running code provided by students via a web browser, security is going to be a concern. All Python code evaluated using this system is run in a code jail based on the EDX code jail. In particular the code is run as a separate user using a specially cordoned off python executable. Using AppArmor the python executable (python3 in our case) is only allowed to access the python libraries in the sand box and temporary files created by the jailing code. Because the enforcement happens at the kernel level via AppArmor, the system is reasonably secure
Object MethodsLets take a closer look at the methods available to these objects and then we will do a deeper dive in actually coding problems.
For both objects these constructor methods takes the Python code as a string input and returns the object. Generally you would generally do something like:
$python = PythonCode(<<EOP);
for i in range(0,10):
print("The number is {}".format(i))
EOP
This either sets or returns the code used to run the object.
This method takes in various options which can be used to provide the code things like standard input, command line arguments, and even files with text. The possibilities are:
This is used to provide the code with a (ref) list of hash references each containing one or more entries for "argv", "files", or "stdin" with the format described above. The correct code and the student code will be run once for each set of inputs and the output of the correct code and the student code will be compared. A correct answer is when the student code output matches the correct output in all of the test cases.
This will return the error type (e.g. "TypeError" or "SyntaxError") for the Python code, if there is one. This overrides the standard output as the correct answer, if there is an error.
This runs the jailed code. It returns the status of the jailed code. The stdout and stderr of the code are stored in the stdout and stderr attributes.
This returns the status of evaluated code.
This returns the stdout output of evaluated code.
This returns the stderr output of evaluated code.
This returns a comparator for the object. For PythonOutput the students answer is compared to the stdout of the code. If there is a runtime error then the correct answer is the class of the error (i.e SyntaxError). For PythonCode the students answer is run as python code and the two outputs are compared for equality. If there is data for multiple collections of test input, then the outputs will be compared for all of the collections.
Lets take a look at a couple of examples in greater depth. The following code is for a problem which tests if students can parse the logic of an if statement.
When the code is rendered it looks like the following. Here we have provided an incorrect answer and asked the correct answer to be displayed. It is useful to note that the numerical value for score can end up being bigger or smaller than 20. So the answer is not always the second string. What is more, because the answer is determined by the Python code itself, all of the quirks of the language will be faithfully recreated.
Next lets look at a more complicated example where students are asked to write a program. In particular we are going to ask students to read in a file name from the first argument, open the file and print its contents to stdout. The pg code looks like this:
Now lets take a look at what this looks like when run. In the following we have an example of a student submitting a script that opens the file and prints it, but does not check the number of arguments first. Notice that students are shown the results of each of their tests, as well as any errors that were reported during any tests. The "show correct answer" output shows the correct output, and the correct code is contained in the popover for the correct answer.
These features represent the basic foundation of our python macros. As we actually write problems and use them in class I'm sure we will come up with new features and best practices. Check back in a couple of semesters to see how things have progressed.