From cf8e0951bfb65116a84b448b72dc6e878fb8dfbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= <nicolas.thiery@universite-paris-saclay.fr> Date: Tue, 4 Feb 2025 00:51:41 +0100 Subject: [PATCH] Substitutions: refactoring and availability for C++; misc minor changes Protocol change: - Substitutions defined by a cell are sent to jupylates by printing a json dict rather than through the cell's value - The cell may print any number of such json dicts - Refinement of the spec: the json dict should map strings to string, to be substituted. Building on top of that to substitute literals is responsability of the language helpers / author. Misc: - Some more documentation for the examples - Fixed examples/04-guess_output_cpp.md - Two additional examples --- examples/01_pandas_select_columns.md | 3 +- examples/02-interpret_code.md | 16 ++++-- examples/02-interpret_code_cpp.md | 59 +++++++++++++++++++++ examples/03-write_code.md | 2 +- examples/04-guess_output_cpp.md | 4 +- examples/05-substitutions.md | 51 ++++++++++++++++++ install_files/include/jupylates_helpers.hpp | 19 +++++++ jupylates/jupylates.py | 17 ++++-- jupylates/jupylates_helpers.py | 4 +- 9 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 examples/02-interpret_code_cpp.md create mode 100644 examples/05-substitutions.md diff --git a/examples/01_pandas_select_columns.md b/examples/01_pandas_select_columns.md index be42327..78119c9 100644 --- a/examples/01_pandas_select_columns.md +++ b/examples/01_pandas_select_columns.md @@ -25,8 +25,9 @@ This exercise illustrates: - the use of arbitrary features of the underlying language and libraries to generate random values (here a Pandas DataFrame) and test them. -- specifying learning objectives as a narrative +- specifying learning objectives as notebook metadata and/or as narrative - the rich display of values +- requesting an expression as answer - one approach to structure the solution and answer enabling testing and displaying the solution diff --git a/examples/02-interpret_code.md b/examples/02-interpret_code.md index 7465218..4ec90f3 100644 --- a/examples/02-interpret_code.md +++ b/examples/02-interpret_code.md @@ -10,16 +10,24 @@ kernelspec: name: python3 --- ++++ {"tags": ["instructors"]} + +:::{hint} About this demo +:class: dropdown + +This exercise illustrates: +- the use of substitutions +- requesting an integer as answer +::: + ```{code-cell} :tags: [hide-cell, substitutions] import random from jupylates.jupylates_helpers import SUBSTITUTE, INPUT_INT, assertEqual -SUBSTITUTE( - I1 = random.randint(1, 7), - I2 = random.randint(8, 15) -) +SUBSTITUTE(I1 = random.randint(1, 7)) +SUBSTITUTE(I2 = random.randint(8, 15)) ``` :::{admonition} Instructions diff --git a/examples/02-interpret_code_cpp.md b/examples/02-interpret_code_cpp.md new file mode 100644 index 0000000..0663cf6 --- /dev/null +++ b/examples/02-interpret_code_cpp.md @@ -0,0 +1,59 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 +kernelspec: + display_name: C++17 + language: C++17 + name: xcpp17 +--- + ++++ {"tags": ["instructors"]} + +:::{hint} About this demo +:class: dropdown + +This exercise illustrates: +- an exercise in C++ +- substitutions of random literals in C++ +- requesting an integer as answer +::: + +```{code-cell} +:tags: [substitutions, hide-cell] + +#include <jupylates_helpers.hpp> + +int I1 = RANDOM_INT(1, 7); +int I2 = RANDOM_INT(8, 15); + +SUBSTITUTE_LITERAL("I1", I1); +SUBSTITUTE_LITERAL("I2", I2); +``` + +:::{admonition} Instructions +What's the value of `r` after executing the following code? +::: + +```{code-cell} +int X = I1; +int Y = I2; + +int Z; + +Z = X; +X = Y; +Y = Z; +int r = Y; +``` + +```{code-cell} +:tags: [answer, solution, test, hide-output] + +int answer, solution; +INPUT_INT("r", solution, answer, r); + +CHECK(answer == solution) +``` diff --git a/examples/03-write_code.md b/examples/03-write_code.md index b8efd64..ed254c0 100644 --- a/examples/03-write_code.md +++ b/examples/03-write_code.md @@ -16,7 +16,7 @@ kernelspec: :class: dropdown This exercise illustrates: -- requesting a code answer +- requesting a code answer in Python ::: :::{admonition} Learning objective diff --git a/examples/04-guess_output_cpp.md b/examples/04-guess_output_cpp.md index c94e253..8e5cdb8 100644 --- a/examples/04-guess_output_cpp.md +++ b/examples/04-guess_output_cpp.md @@ -30,7 +30,7 @@ Understand the step by step execution of a for loop. :tags: [hide-cell] #include <sstream> -#include "jupylates_helpers.hpp" +#include <jupylates_helpers.hpp> CONST I1 = RANDOM_INT(0, 6); CONST I2 = I1 + RANDOM_INT(1, 4); @@ -59,5 +59,5 @@ for (int I = I1; I <= I2 ; I = I + 1 ) { std::string answer, solution; INPUT_TEXT("", solution, answer, cout.str()); -assertEqual(answer, solution); +CHECK(answer == solution); ``` diff --git a/examples/05-substitutions.md b/examples/05-substitutions.md new file mode 100644 index 0000000..7551daf --- /dev/null +++ b/examples/05-substitutions.md @@ -0,0 +1,51 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 +kernelspec: + display_name: C++17 + language: C++17 + name: xcpp17 +--- + ++++ {"tags": ["learning objectives"]} + +:::{hint} About this demo +:class: dropdown + +This exercise illustrates: +- an exercise in C++ +- substitutions of random code +::: + +```{code-cell} +:tags: [substitutions, hide-cell] + +#include <jupylates_helpers.hpp> + +SUBSTITUTE("VAL_OR_REF ", RANDOM_CHOICE("", "&")); +SUBSTITUTE("PLUS_OR_MINUS", RANDOM_CHOICE("+", "-")); +SUBSTITUTE("A", RANDOM_CHOICE("a", "b", "c")); +SUBSTITUTE("D", RANDOM_CHOICE("d", "e", "f")); +``` + +:::{admonition} Instructions +What's the value of `A` after executing the following code: +::: + +```{code-cell} +int A = 1 PLUS_OR_MINUS 1; +int VAL_OR_REF D = A; +D = 4; +``` + +```{code-cell} +:tags: [answer, solution, test, hide-output] + +int answer, solution; +INPUT_INT("A", solution, answer, A); + +CHECK(answer == solution); +``` diff --git a/install_files/include/jupylates_helpers.hpp b/install_files/include/jupylates_helpers.hpp index c29fc12..105e094 100644 --- a/install_files/include/jupylates_helpers.hpp +++ b/install_files/include/jupylates_helpers.hpp @@ -5,6 +5,7 @@ #include <cstdlib> #include <vector> #include <functional> +#include <sstream> /** Infrastructure minimale de test **/ #ifndef ASSERT @@ -101,4 +102,22 @@ void INPUT_EXPR(std::string description, T& answer, T& solution, T solution_valu #define INPUT_FLOAT INPUT_EXPR<float> #define INPUT_BOOL INPUT_EXPR<bool> +template<class T> +std::string to_literal(T i) { + std::ostringstream s; + s << i; + return s.str(); +} +// Will need to be overriden for vectors, ... + + +void SUBSTITUTE(std::string name, std::string value) { + std::cout << "{\"" << name << "\": \"" << value << "\"}" << std::endl; +} + +template<class T> +void SUBSTITUTE_LITERAL(std::string name, T value) { + SUBSTITUTE(name, to_literal(value)); +} + #endif diff --git a/jupylates/jupylates.py b/jupylates/jupylates.py index 428e4c6..bd2d5aa 100644 --- a/jupylates/jupylates.py +++ b/jupylates/jupylates.py @@ -88,7 +88,7 @@ def execute_code( assert False if msg["msg_type"] == "execute_input": continue - if msg["msg_type"] in ["execute_result", "error"]: + if msg["msg_type"] in ["stream", "execute_result", "error"]: content = copy.copy(msg["content"]) content["output_type"] = msg["msg_type"] outputs.append(content) @@ -99,6 +99,8 @@ def display_outputs(outputs: List[dict]) -> None: for output in outputs: if output["output_type"] == "error": display(output["ename"]) + elif output["output_type"] == "stream": + print(output["text"], end='') elif "text/html" in output["data"]: display(HTML(output["data"]["text/plain"])) elif "text/plain" in output["data"]: @@ -1043,10 +1045,15 @@ class Exerciser(ipywidgets.HBox): else: outputs = [] if "substitutions" in cell_tags: - assert len(outputs) == 1 - output = outputs[0]["data"]["text/plain"] - self.substitutions.update(json.loads(output[1:-1])) - outputs = [] + decoder = json.JSONDecoder() + for output in outputs: + if output.get('name') != 'stdout': + continue + text = output['text'] + while text: + d, pos = decoder.raw_decode(text) + text = text[pos:].strip() + self.substitutions.update(d) if "hide-cell" not in cell_tags and "hide-input" not in cell_tags: display(Code(source, language=lexer[language])) if "hide-cell" not in cell_tags and "hide-output" not in cell_tags: diff --git a/jupylates/jupylates_helpers.py b/jupylates/jupylates_helpers.py index 660be1b..036781e 100644 --- a/jupylates/jupylates_helpers.py +++ b/jupylates/jupylates_helpers.py @@ -27,11 +27,11 @@ def RANDOM_CHOICE(*args: Any) -> Any: return choice(args) -def SUBSTITUTE(**args: Any) -> str: +def SUBSTITUTE(**args: Any) -> None: import __main__ __main__.__dict__.update(args) - return json.dumps({key: str(value) for key, value in args.items()}) + print(json.dumps({key: str(value) for key, value in args.items()})) T = TypeVar("T", int, float, str, Any) -- GitLab