Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions courseInstances/Part4/assessments/DL4/infoAssessment.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
{ "id": "Gallery/PromotionalProduct", "points": 10 },
{ "id": "Gallery/Invoice", "points": 10 },
{ "id": "Gallery/RandomCProgram", "points": 10 },
{ "id": "Gallery/AstOfCProgram", "points": 10 },
{ "id": "Gallery/LinkedListRemove", "points": 10 }
],
"comment": "This zone contains questions demonstrating the basic functionality of external autograder, as well as official autograders for Java and C. For more info, contact: Jonatan Schroeder jonatan@yorku.ca"
Expand Down
15 changes: 15 additions & 0 deletions questions/Gallery/AstOfCProgram/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"uuid": "34292487-5019-47ab-8175-49e9dada8790",
"title": "Sale Price",
"topic": "Variables",
"singleVariant": true,
"tags": [ "jgfoster" ],
"type": "v3",
"gradingMethod": "External",
"externalGradingOptions": {
"enabled": true,
"image": "prairielearn/grader-c",
"entrypoint": "/grade/tests/test.sh",
"serverFilesCourse": ["clang/"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line and the serverFilesCourse content become unnecessary once PrairieLearn/PrairieLearn#11913 is handled. Also, the entrypoint can become the Python file again if the path does not need to be updated.

}
}
25 changes: 25 additions & 0 deletions questions/Gallery/AstOfCProgram/question.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<pl-question-panel>
<p>Jimmy is at Hollister and he wants to buy a hoodie.
The hoodie costs $65, but it has a discount of 15%.</p>
<p>Write a program to help Jimmy calculate the sale price of the hoodie.
The program must ask Jimmy for the item price, and then it will display
the sale price with a precision of 2 decimal places.</p>
<p>You <b>must</b> declare a double constant variable and assign it with the sale percentage.</p>
<p>You <b>must</b> declare the sale percentage using scientific notation.
(No, this isn't how you would normally write a program like this, but it is a convenient demo.)
</p>
<p>Below is an example of the program:</p>

<pl-code language="output">
Enter an item price: $65.00
The sale price is $55.25

</pl-code>
</pl-question-panel>

<pl-file-editor source-file-name="source.cpp" file-name="student.cpp" ace-mode="ace/mode/c_cpp"></pl-file-editor>

<pl-submission-panel>
<pl-external-grader-results></pl-external-grader-results>
<pl-file-preview></pl-file-preview>
</pl-submission-panel>
13 changes: 13 additions & 0 deletions questions/Gallery/AstOfCProgram/solution.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
const double DISCOUNT = 1.5e-1;
double price;

cout << "Enter an item price: $";
cin >> price;
cout << fixed << setprecision(2) << price * (1.0 - DISCOUNT) << endl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example includes a text, the solution should probably include it too, even if it's not enforced in the autograder.

Suggested change
cout << fixed << setprecision(2) << price * (1.0 - DISCOUNT) << endl;
cout << "The sale price is " << fixed << setprecision(2) << price * (1.0 - DISCOUNT) << endl;

return 0;
}
9 changes: 9 additions & 0 deletions questions/Gallery/AstOfCProgram/source.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <iostream>
using namespace std;

int main() {

// Your solution goes here

return 0;
}
93 changes: 93 additions & 0 deletions questions/Gallery/AstOfCProgram/tests/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#! /usr/bin/python3

import cgrader
import clang.cindex

clang.cindex.Config.set_library_path("/usr/lib/")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


def print_ast(node, indent=0):
print(" " * indent + str(node.spelling) + " (" + str(node.kind) + ")")
for child in node.get_children():
print_ast(child, indent + 2)

def find_const_double(node):
if (
node.kind == clang.cindex.CursorKind.VAR_DECL
and "double" in node.type.spelling
and node.type.is_const_qualified()
):
return node
for child in node.get_children():
found = find_const_double(child)
if found is not None:
return found
return None

def var_name_is_valid(node):
return not any(c.islower() for c in node.spelling)

def find_double_initializer(node):
child = list(node.get_children())[0]
return child if child.kind == clang.cindex.CursorKind.FLOATING_LITERAL else None

def initializer_is_scientific_notation(node):
tokens = list(node.get_tokens())
if tokens:
literal = tokens[0].spelling
return "e" in literal or "E" in literal
return False

class QuestionGrader(cgrader.CPPGrader):
def tests(self):
index = clang.cindex.Index.create()
translation_unit = index.parse("student.cpp", args=["-x", "c++"])
# print_ast(translation_unit.cursor) # Uncomment to print the AST (be sure to comment out includes!)
decl = find_const_double(translation_unit.cursor)
if decl is None:
self.add_test_result("const double not found", points=0, max_points=1)
return
self.add_test_result("const double found")
if not var_name_is_valid(decl):
self.add_test_result("variable name is not using the capitalization expected for a constant variable", points=0, max_points=1)
return
self.add_test_result("variable name is valid")
initializer = find_double_initializer(decl)
if initializer is None:
self.add_test_result("variable does not have a double initializer", points=0, max_points=1)
return
self.add_test_result("variable has a double initializer")
if not initializer_is_scientific_notation(initializer):
self.add_test_result("initializer is not in scientific notation", points=0, max_points=1)
return
self.add_test_result("initializer is in scientific notation")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of conditional tests, especially since they mess up with the total points and don't give students a sense of what other tests are coming up. Also, this method allows some tests to run together (e.g., show both the initializer test and the var name test if either fails).

Suggested change
decl = find_const_double(translation_unit.cursor)
if decl is None:
self.add_test_result("const double not found", points=0, max_points=1)
return
self.add_test_result("const double found")
if not var_name_is_valid(decl):
self.add_test_result("variable name is not using the capitalization expected for a constant variable", points=0, max_points=1)
return
self.add_test_result("variable name is valid")
initializer = find_double_initializer(decl)
if initializer is None:
self.add_test_result("variable does not have a double initializer", points=0, max_points=1)
return
self.add_test_result("variable has a double initializer")
if not initializer_is_scientific_notation(initializer):
self.add_test_result("initializer is not in scientific notation", points=0, max_points=1)
return
self.add_test_result("initializer is in scientific notation")
decl = find_const_double(translation_unit.cursor)
self.add_test_result("const double found", points=(decl is not None))
var_name_result = decl is not None and var_name_is_valid(decl)
self.add_test_result("variable name is valid",
points=1 if var_name_result else 0,
output=None if var_name_result else "variable name is not using the capitalization expected for a constant variable"
)
self.add_test_result("variable has a double initializer",
points=1 if decl is not None and find_double_initializer(decl) else 0
)
self.add_test_result("initializer is in scientific notation",
points=1 if decl is not None and initializer_is_scientific_notation(initializer) else 0
)

self.test_compile_file(
"student.cpp", "student", flags="-Wall -Wextra -pedantic -Werror"
)
self.test_run(
"./student",
input="65.00\n",
exp_output="55.25",
must_match_all_outputs="partial",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed when there is a single output.

)
self.test_run(
"./student",
input="35.00\n",
exp_output="29.75",
must_match_all_outputs="partial",
)
self.test_run(
"./student",
input="77.00\n",
exp_output="65.45",
must_match_all_outputs="partial",
)
self.test_run(
"./student",
input="48.00\n",
exp_output="40.8",
must_match_all_outputs="partial",
)
self.add_test_result("code runs correctly")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the ast tests pass, this will always return true even if the code doesn't print anything valid.


g = QuestionGrader()
g.start()
4 changes: 4 additions & 0 deletions questions/Gallery/AstOfCProgram/tests/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#! /bin/bash
export PATH=/grade/serverFilesCourse:$PATH
ln -s /usr/lib/llvm-18/lib/libclang.so.1 /usr/lib/libclang.so
python3 /grade/tests/test.py
12 changes: 0 additions & 12 deletions questions/Scratch/block_position_1/info.json

This file was deleted.

10 changes: 0 additions & 10 deletions questions/Scratch/block_position_1/question.html

This file was deleted.

Empty file.
12 changes: 0 additions & 12 deletions questions/Scratch/block_position_2/info.json

This file was deleted.

10 changes: 0 additions & 10 deletions questions/Scratch/block_position_2/question.html

This file was deleted.

Empty file.
13 changes: 0 additions & 13 deletions questions/Scratch/category_for_conditionals/info.json

This file was deleted.

5 changes: 0 additions & 5 deletions questions/Scratch/category_for_conditionals/question.html

This file was deleted.

Empty file.
13 changes: 0 additions & 13 deletions questions/Scratch/category_for_input/info.json

This file was deleted.

5 changes: 0 additions & 5 deletions questions/Scratch/category_for_input/question.html

This file was deleted.

Empty file.
13 changes: 0 additions & 13 deletions questions/Scratch/category_for_loops/info.json

This file was deleted.

5 changes: 0 additions & 5 deletions questions/Scratch/category_for_loops/question.html

This file was deleted.

Empty file.
13 changes: 0 additions & 13 deletions questions/Scratch/category_for_say/info.json

This file was deleted.

5 changes: 0 additions & 5 deletions questions/Scratch/category_for_say/question.html

This file was deleted.

Empty file.
Binary file not shown.
12 changes: 0 additions & 12 deletions questions/Scratch/loop_choice/info.json

This file was deleted.

9 changes: 0 additions & 9 deletions questions/Scratch/loop_choice/question.html

This file was deleted.

22 changes: 0 additions & 22 deletions questions/Scratch/loop_choice/server.py

This file was deleted.

10 changes: 0 additions & 10 deletions questions/Scratch/project_1/info.json

This file was deleted.

20 changes: 0 additions & 20 deletions questions/Scratch/project_1/question.html

This file was deleted.

Loading