Python with C question type and escaping characters

Python with C question type and escaping characters

by Richard Lobb -
Number of replies: 0

A teacher posted the following question as a github issue, but I'm answering it here.

====== Question begins ======

Hello,

I am tryin to create a question for a C program which uses certain modules such as a stack (stack.h and stack.c). I have managed to get it working with the template leveraging make provided in the documentation--which is what I was aiming for. However, in that template the complete student answer-main file-is provided in a separate file, along with the required module. I have also had success when uploading the files in the question configuration and providing the full answer from the student in the Answer box. This way, the template box only has the following code:

import subprocess
import sys

student_answer = """{{ STUDENT_ANSWER | e('py') }}"""

with open("prog.c", "w") as src:
    print(student_answer, file=src)

# Call make
make_result = subprocess.run(['make'], stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT, encoding='utf-8')
if make_result.returncode != 0:
    print(make_result.stdout)
    print("** Make failed. Testing aborted **", file=sys.stderr)
else:
    # If Make succeeded, run prog. Since this is a per-test template,
    # stdin is already set up for the stdin text specified in the test case,
    # so we can run the compiled program directly.
    try:
        output = subprocess.check_output(["./prog"], universal_newlines=True)
        print(output)
    except subprocess.CalledProcessError as e:
        if e.returncode > 0:
            # Ignore non-zero positive return codes
            if e.output:
                print(e.output)
        else:
            # But negative return codes are signals - abort
            if e.output:
                print(e.output, file=sys.stderr)
            if e.returncode < 0:
                print("Task failed with signal", -e.returncode, file=sys.stderr)
            print("** Further testing aborted **", file=sys.stderr)

The issue is that I am struggling creating a template that mixes some code from a student such as a function from the main file and some code provided. For instance, I am trying to create the template like this:

import subprocess
import sys

template = """
#include <stdio.h>
#include <string.h>
#include "stack.h"

{{ STUDENT_ANSWER | e('py') }}

void showPassengers(Stack *stack) {
    while (!STACK_isEmpty(*stack)) {
        Passenger passenger = STACK_top(stack);
        printf("%s %s %d %c\n", passenger.name, passenger.flight_no, passenger.row, passenger.seat);
        STACK_pop(stack);
    }
}

int main() {
    Passenger *passengers = NULL;
    int n_passengers = 0;
    Stack stack;

    passengers = loadPassengers("passengers", &n_passengers);

    stack = STACK_create();

    for (int i = 0; i < n_passengers; i++) {
        STACK_push(&stack, passengers[i]);
    }
    
    {{ TEST.testcode }}

    STACK_destroy(&stack);
    
    free(passengers);

    return 0;
}
"""

with open("prog.c", "w") as src:
    print(student_answer, file=src)

# Call make
make_result = subprocess.run(['make'], stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT, encoding='utf-8')
if make_result.returncode != 0:
    print(make_result.stdout)
    print("** Make failed. Testing aborted **", file=sys.stderr)
else:
    # If Make succeeded, run prog. Since this is a per-test template,
    # stdin is already set up for the stdin text specified in the test case,
    # so we can run the compiled program directly.
    try:
        output = subprocess.check_output(["./prog"], universal_newlines=True)
        print(output)
    except subprocess.CalledProcessError as e:
        if e.returncode > 0:
            # Ignore non-zero positive return codes
            if e.output:
                print(e.output)
        else:
            # But negative return codes are signals - abort
            if e.output:
                print(e.output, file=sys.stderr)
            if e.returncode < 0:
                print("Task failed with signal", -e.returncode, file=sys.stderr)
            print("** Further testing aborted **", file=sys.stderr)

As you can see, part of the code is mine, while a function just needs to implement a certain function. However, this is not working as expected apparently because Python is escaping certain characters. I have tried several steps such as using double curly braces, etc. to no avail. Is there a particular way I should proceed? In other words, I want to have a program consisting of several modules in C where a user just needs to implement one function from a module. I am quite certain the issue is with the escape of characters in Python due to the output:

gcc -c prog.c
prog.c: In function ‘showPassengers’:
prog.c:41:16: warning: missing terminating " character
   41 |         printf("%s %s %d %c
      |                ^
prog.c:41:16: error: missing terminating " character
   41 |         printf("%s %s %d %c
      |                ^~~~~~~~~~~~
prog.c:42:1: warning: missing terminating " character
   42 | ", passenger.name, passenger.flight_no, passenger.row, passenger.seat);
      | ^
prog.c:42:1: error: missing terminating " character
   42 | ", passenger.name, passenger.flight_no, passenger.row, passenger.seat);
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.c:43:25: error: expected ‘)’ before ‘;’ token
   43 |         STACK_pop(stack);
      |                         ^
      |                         )
prog.c:41:15: note: to match this ‘(’
   41 |         printf("%s %s %d %c
      |               ^
prog.c:43:9: error: invalid use of void expression
   43 |         STACK_pop(stack);
      |         ^~~~~~~~~~~~~~~~
prog.c:43:26: error: expected ‘;’ before ‘}’ token
   43 |         STACK_pop(stack);
      |                          ^
      |                          ;
   44 |     }
      |     ~
make: *** [makefile:7: prog.o] Error 1

Thanks for your help,

==================== Question ends ==================

I'm pretty sure the problem here is with the line 

{{ TEST.testcode }}

That should be

{{ TEST.testcode | e('py') }}

in order to escape things like backslashes in the test code string.

It looks like you have a test of the form printf("..stuff.. \n"). But that's embedded within a Python string, so the two-character sequence '\n' will be converted to a single newline character, prematurely terminating the printf line.

A useful tip to help debug complex templates: click the template debugging checkbox. That will let you see how the template code is being expanded, i.e. to see just what source code is being run.