From f8a82f7969257b5a97ff4c4d53cc46410d1c7fbc Mon Sep 17 00:00:00 2001 From: "M. Pabiszczak" <2+mpabi@noreply.zsl-gitea.mpabi.pl> Date: Tue, 21 Jul 2026 20:15:17 +0200 Subject: [PATCH] feat: add first-semester C++ course --- catalog/cpp.json | 29 +- plans/informatyka/README.md | 12 +- plans/informatyka/cpp.md | 75 ++-- series/cpp.json | 24 ++ tools/generate_cpp_y2s1.py | 675 ++++++++++++++++++++++++++++++++++++ workspace.json | 10 + 6 files changed, 777 insertions(+), 48 deletions(-) create mode 100644 series/cpp.json create mode 100755 tools/generate_cpp_y2s1.py diff --git a/catalog/cpp.json b/catalog/cpp.json index 3c5fecd..cdf9f0f 100644 --- a/catalog/cpp.json +++ b/catalog/cpp.json @@ -1,22 +1,17 @@ { "id": "cpp", - "title": "C++", - "catalog_only": true, + "title": "C++ · Rok 2 · Semestr 1", + "catalog_only": false, "cards": [ - {"id": "CPP01", "repo": "lab-cpp-build-namespaces", "number": "01", "title": "Translation Units, Linkage and Namespaces", "status": "planned"}, - {"id": "CPP02", "repo": "lab-cpp-classes-invariants", "number": "02", "title": "Classes, Encapsulation and Invariants", "status": "planned"}, - {"id": "CPP03", "repo": "lab-cpp-lifetime-raii", "number": "03", "title": "Construction, Destruction and RAII", "status": "planned"}, - {"id": "CPP04", "repo": "lab-cpp-references-const", "number": "04", "title": "References, const and Value Categories", "status": "planned"}, - {"id": "CPP05", "repo": "lab-cpp-copy-move", "number": "05", "title": "Copy, Move and the Rule of Zero/Five", "status": "planned"}, - {"id": "CPP06", "repo": "lab-cpp-ownership", "number": "06", "title": "Ownership and Resource Types", "status": "planned"}, - {"id": "CPP07", "repo": "lab-cpp-templates", "number": "07", "title": "Function and Class Templates", "status": "planned"}, - {"id": "CPP08", "repo": "lab-cpp-static-polymorphism", "number": "08", "title": "Static Polymorphism and CRTP", "status": "planned"}, - {"id": "CPP09", "repo": "lab-cpp-dynamic-polymorphism", "number": "09", "title": "Inheritance, Interfaces and Dynamic Dispatch", "status": "planned"}, - {"id": "CPP10", "repo": "lab-cpp-strong-types", "number": "10", "title": "Operator Overloading and Strong Types", "status": "planned"}, - {"id": "CPP11", "repo": "lab-cpp-containers-iterators", "number": "11", "title": "Containers, Iterators and Algorithms", "status": "planned"}, - {"id": "CPP12", "repo": "lab-cpp-lambdas-callables", "number": "12", "title": "Lambdas, Function Objects and Callables", "status": "planned"}, - {"id": "CPP13", "repo": "lab-cpp-error-models", "number": "13", "title": "Error Models with and without Exceptions", "status": "planned"}, - {"id": "CPP14", "repo": "lab-cpp-abi-evidence", "number": "14", "title": "Object Layout, ABI and Binary Evidence", "status": "planned"}, - {"id": "CPP15", "repo": "lab-cpp-resource-integration", "number": "15", "title": "Resource-type Integration Project", "status": "planned"} + {"id": "CPP01", "repo": "lab-cpp-build-namespaces", "number": "01", "title": "Oddzielna kompilacja, linkage i przestrzenie nazw", "status": "existing"}, + {"id": "CPP02", "repo": "lab-cpp-types-initialization", "number": "02", "title": "Typy, inicjalizacja, auto, const i enum class", "status": "existing"}, + {"id": "CPP03", "repo": "lab-cpp-pointers-references", "number": "03", "title": "Wskaźniki, referencje, tablice i nullptr", "status": "existing"}, + {"id": "CPP04", "repo": "lab-cpp-functions-overloading", "number": "04", "title": "Funkcje, przeciążanie i modułowość", "status": "existing"}, + {"id": "CPP05", "repo": "lab-cpp-classes-objects", "number": "05", "title": "Klasa i obiekt: interfejs oraz implementacja", "status": "existing"}, + {"id": "CPP06", "repo": "lab-cpp-invariants", "number": "06", "title": "Hermetyzacja, niezmienniki i odpowiedzialność klasy", "status": "existing"}, + {"id": "CPP07", "repo": "lab-cpp-construction-destruction", "number": "07", "title": "Konstruktory, destruktory i inicjalizacja obiektu", "status": "existing"}, + {"id": "CPP08", "repo": "lab-cpp-scope-lifetime", "number": "08", "title": "Zakres i czas życia obiektów", "status": "existing"}, + {"id": "CPP09", "repo": "lab-cpp-raii", "number": "09", "title": "RAII i zasób związany z czasem życia", "status": "existing"}, + {"id": "CPP10", "repo": "lab-cpp-copy-move", "number": "10", "title": "Kopiowanie, przenoszenie i Rule of Zero/Five", "status": "existing"} ] } diff --git a/plans/informatyka/README.md b/plans/informatyka/README.md index 4f83b91..babe309 100644 --- a/plans/informatyka/README.md +++ b/plans/informatyka/README.md @@ -14,7 +14,8 @@ INF · Informatyka │ ├── ASM · RV32I From Blinker to RISC-V [9] │ └── C · Freestanding RV32I and K&R [13] ├── Rok 2 -│ ├── C++ [15] +│ ├── C++ · semestr 1 [10 active] +│ ├── C++ · semestr 2 [10 planned] │ ├── FreeRTOS C [15] │ ├── FreeRTOS C++ [15] │ ├── POSIX Threads [15] @@ -29,10 +30,11 @@ INF · Informatyka ``` Pierwszy blok zawiera dokładnie 30 tematów: Bash 8 + ASM 9 + C 13. -Drugi blok zawiera dokładnie 60 tematów: FreeRTOS C 15 + C++ 15 + FreeRTOS -C++ 15 + POSIX Threads 15. OOP and Design Patterns jest osobnym kursem i nie -jest wliczany do tych 60. `std::thread`/`std::jthread` i Boost.Asio są kolejnym -etapem i nie są jeszcze częścią tego katalogu. +Drugi rok zawiera dwusemestralny kurs teoretyczny C++ (10 + 10 lekcji) oraz +osobne serie laboratoryjne FreeRTOS C, FreeRTOS C++ i POSIX Threads po 15 +kart. OOP and Design Patterns jest osobnym kursem odniesienia. Aktywna seria +`cpp` obejmuje obecnie komplet pierwszego semestru. Biblioteka standardowa, +`std::thread`/`std::jthread` i Boost.Asio należą do kolejnych semestrów. Kolejność stosowania języków jest jawna: diff --git a/plans/informatyka/cpp.md b/plans/informatyka/cpp.md index 21c81b7..4484a7a 100644 --- a/plans/informatyka/cpp.md +++ b/plans/informatyka/cpp.md @@ -1,32 +1,55 @@ -# C++ — plan kart +# C++ — plan wielosemestralny i stan wdrożenia -Status: plan, 15 tematów. +Źródło zakresu: `/home/user/dev/keys/cxx.md`. Standard kursu: C++20 z +wybranymi, jawnie oznaczonymi elementami C++23. Cykl obejmuje 53 lekcje +teoretyczne. Projekty, FreeRTOS i praca na urządzeniu pozostają osobnymi +zajęciami. -Seria uczy języka i modelu obiektu bez zależności od RTOS. Dopiero po niej -uczeń przechodzi do FreeRTOS C++ i rozpoznaje, które zachowanie pochodzi z C++, -a które z wrappera lub kernela. Kurs może być prowadzony równolegle z -FreeRTOS C, ponieważ oba mają wspólny ukończony prerekwizyt C. +| Etap | Lekcje | Oś merytoryczna | Stan | +| --- | ---: | --- | --- | +| Rok 2, semestr 1 | 10 | fundamenty języka, klasy, czas życia i RAII | **wdrożony** | +| Rok 2, semestr 2 | 10 | OOP i C++ w systemach wbudowanych | plan | +| Rok 3, semestr 1 | 16 | STL i biblioteka standardowa | plan | +| Rok 3, semestr 2 | 17 | współbieżność, korutyny i Boost.Asio | plan | + +## Rok 2, semestr 1 — aktywna seria `cpp` + +Książka prowadząca: Bjarne Stroustrup, *A Tour of C++, 3rd Edition*. +*The C++ Programming Language, 4th Edition* jest źródłem uzupełniającym dla +nauczyciela. Karty parafrazują pojęcia i zawierają własne eksperymenty; nie +kopiują treści książek. ```text -C++ -├── CPP01 · Translation Units, Linkage and Namespaces -├── CPP02 · Classes, Encapsulation and Invariants -├── CPP03 · Construction, Destruction and RAII -├── CPP04 · References, const and Value Categories -├── CPP05 · Copy, Move and the Rule of Zero/Five -├── CPP06 · Ownership and Resource Types -├── CPP07 · Function and Class Templates -├── CPP08 · Static Polymorphism and CRTP -├── CPP09 · Inheritance, Interfaces and Dynamic Dispatch -├── CPP10 · Operator Overloading and Strong Types -├── CPP11 · Containers, Iterators and Algorithms -├── CPP12 · Lambdas, Function Objects and Callables -├── CPP13 · Error Models with and without Exceptions -├── CPP14 · Object Layout, ABI and Binary Evidence -└── CPP15 · Resource-type Integration Project +C++ · Rok 2 · Semestr 1 +├── CPP01 · Oddzielna kompilacja, deklaracja, definicja, linkage i namespace +├── CPP02 · Typy, inicjalizacja, auto, const, enum class i typ użytkownika +├── CPP03 · Wskaźniki, referencje, tablice, nullptr i argumenty +├── CPP04 · Funkcje, przeciążanie, wartości zwracane, zakres i modułowość +├── CPP05 · Klasa i obiekt: pola, metody, dostęp, interfejs i implementacja +├── CPP06 · Hermetyzacja, niezmienniki i odpowiedzialność klasy +├── CPP07 · Konstruktory, destruktory i inicjalizacja obiektu +├── CPP08 · Zakres i czas życia obiektów automatycznych, statycznych i dynamicznych +├── CPP09 · RAII i powiązanie zasobu z czasem życia obiektu +└── CPP10 · Kopiowanie, przenoszenie, Rule of Zero/Five i własność zasobu ``` -Kurs OOP/wzorców pozostaje osobną serią. Karty C++ mogą przygotowywać potrzebny -mechanizm, ale nie udają, że każda cecha języka jest wzorcem GoF. Gdy w kodzie -pojawia się rzeczywisty problem projektowy, karta C++ linkuje do odpowiedniej -karty OOP jako rozwinięcia decyzji. +Każda karta ma trzy niezależne programy, kompilację +`-std=c++20 -Wall -Wextra -Wpedantic -Werror`, dokładny kontrakt stdout, +testy Node, widok React, PDF A4 i akcje `build/test/run/debug` dla profilu +`native-amd64`. Generator źródeł znajduje się w +`tools/generate_cpp_y2s1.py` tego repozytorium. + +## Granice kolejnych semestrów + +- Semestr 2 rozpoczyna się od typów wartościowych i operatorów, a kończy + teorią wrapperów `Task`, `StaticQueue`, `Mutex` i `LockGuard`. +- Rok 3, semestr 1 należy do STL: kontenery, iteratory, algorytmy, ranges, + typy pomocnicze, smart pointery i narzędzia biblioteki standardowej. +- Rok 3, semestr 2 obejmuje `thread`/`jthread`, synchronizację, atomiki, + futures, korutyny i Boost.Asio. +- Szczegóły API FreeRTOS, portu, ISR i uruchamiania na płytce pozostają w + seriach FreeRTOS C oraz FreeRTOS C++. + +Kurs OOP/wzorców jest osobną serią odniesienia. Karta C++ linkuje do niej +dopiero wtedy, gdy występuje rzeczywisty problem projektowy, a nie tylko +pojedynczy mechanizm języka. diff --git a/series/cpp.json b/series/cpp.json new file mode 100644 index 0000000..bbf44bf --- /dev/null +++ b/series/cpp.json @@ -0,0 +1,24 @@ +{ + "id": "cpp", + "title": "C++ · Rok 2 · Semestr 1", + "source_org": "edu-cpp", + "answer_org": "c2025-2a-inf", + "source_remote": "r1", + "answer_remote": "r1a", + "fallback_branch": "main", + "workspace_dir": "cpp", + "source_dir": "cpp", + "default_card": "CPP01", + "cards": [ + {"id": "CPP01", "repo": "lab-cpp-build-namespaces", "branch": "main", "title": "Oddzielna kompilacja, linkage i przestrzenie nazw"}, + {"id": "CPP02", "repo": "lab-cpp-types-initialization", "branch": "main", "title": "Typy, inicjalizacja, auto, const i enum class"}, + {"id": "CPP03", "repo": "lab-cpp-pointers-references", "branch": "main", "title": "Wskaźniki, referencje, tablice i nullptr"}, + {"id": "CPP04", "repo": "lab-cpp-functions-overloading", "branch": "main", "title": "Funkcje, przeciążanie i modułowość"}, + {"id": "CPP05", "repo": "lab-cpp-classes-objects", "branch": "main", "title": "Klasa i obiekt: interfejs oraz implementacja"}, + {"id": "CPP06", "repo": "lab-cpp-invariants", "branch": "main", "title": "Hermetyzacja, niezmienniki i odpowiedzialność klasy"}, + {"id": "CPP07", "repo": "lab-cpp-construction-destruction", "branch": "main", "title": "Konstruktory, destruktory i inicjalizacja obiektu"}, + {"id": "CPP08", "repo": "lab-cpp-scope-lifetime", "branch": "main", "title": "Zakres i czas życia obiektów"}, + {"id": "CPP09", "repo": "lab-cpp-raii", "branch": "main", "title": "RAII i zasób związany z czasem życia"}, + {"id": "CPP10", "repo": "lab-cpp-copy-move", "branch": "main", "title": "Kopiowanie, przenoszenie i Rule of Zero/Five"} + ] +} diff --git a/tools/generate_cpp_y2s1.py b/tools/generate_cpp_y2s1.py new file mode 100755 index 0000000..7fff2b5 --- /dev/null +++ b/tools/generate_cpp_y2s1.py @@ -0,0 +1,675 @@ +#!/usr/bin/env python3 +"""Generate the ten C++20 cards for Year 2, semester 1. + +The lesson scope comes from /home/user/dev/keys/cxx.md. The generated cards +share the same runtime, React viewer and PDF contract as the existing STEM +cards, while every task contains an independently compilable C++ experiment. +""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +import shutil +import stat +import textwrap +import uuid + + +ROOT = Path(__file__).resolve().parents[3] +OUTPUT = ROOT / "series" / "cpp" +TEMPLATE = ROOT / "series" / "console" / "lab-console-busybox" +UUID_NAMESPACE = uuid.UUID("1b43af4e-354f-51dc-a42a-67593c614624") + + +def clean(text: str) -> str: + return textwrap.dedent(text).lstrip() + + +LESSONS = [ + { + "id": "CPP01", + "number": "01", + "repo": "lab-cpp-build-namespaces", + "slug": "build-namespaces", + "title": "Od C do C++: oddzielna kompilacja i przestrzenie nazw", + "topic": "Deklaracja, definicja, translation unit, linkage i namespace", + "chapter": "A Tour of C++ 3e: rozdziały 1 i 3", + "mission": "Zbudować program z wielu jednostek translacji i odczytać granice nazw oraz linkage.", + "tasks": [ + ("Deklaracja i definicja", "Rozdziel deklarację funkcji, definicję i punkt wejścia między trzy pliki.", "sum=7", { + "calculator.hpp": """#pragma once +namespace lab { int add(int left, int right); } +""", + "calculator.cpp": """#include "calculator.hpp" +namespace lab { int add(int left, int right) { return left + right; } } +""", + "main.cpp": """#include "calculator.hpp" +#include +int main() { std::cout << "sum=" << lab::add(3, 4) << '\\n'; } +""", + }), + ("Linkage zewnętrzny i wewnętrzny", "Porównaj symbol dostępny między plikami z nazwą ukrytą w anonimowej przestrzeni nazw.", "external=5 internal_bias_result=8", { + "api.hpp": """#pragma once +int global_value(); +int shifted_value(); +""", + "global.cpp": """#include "api.hpp" +int global_value() { return 5; } +""", + "shifted.cpp": """#include "api.hpp" +namespace { constexpr int bias = 3; } +int shifted_value() { return global_value() + bias; } +""", + "main.cpp": """#include "api.hpp" +#include +int main() { std::cout << "external=" << global_value() << " internal_bias_result=" << shifted_value() << '\\n'; } +""", + }), + ("Kwalifikacja przestrzeni nazw", "Użyj dwóch funkcji o tej samej nazwie bez kolizji i wywołaj je kwalifikowaną nazwą.", "sensor=21 network=204", { + "main.cpp": """#include +namespace sensor { int read() { return 21; } } +namespace network { int read() { return 204; } } +int main() { std::cout << "sensor=" << sensor::read() << " network=" << network::read() << '\\n'; } +""", + }), + ], + }, + { + "id": "CPP02", "number": "02", "repo": "lab-cpp-types-initialization", "slug": "types-initialization", + "title": "Typy, inicjalizacja, auto, const i enum class", + "topic": "Bezpieczna inicjalizacja i typy definiowane przez użytkownika", + "chapter": "A Tour of C++ 3e: rozdziały 1 i 2", + "mission": "Rozpoznać typy wyrażeń i używać inicjalizacji, const oraz enum class bez niejawnego mieszania domen.", + "tasks": [ + ("Inicjalizacja i auto", "Zastosuj inicjalizację klamrową i potwierdź typ dedukowany przez auto.", "count=4 twice=8 type=int", { + "main.cpp": """#include +#include +int main() { + int count{4}; + auto twice = count * 2; + static_assert(std::is_same_v); + std::cout << "count=" << count << " twice=" << twice << " type=int\\n"; +} +""", + }), + ("const i constexpr", "Oddziel wartość obliczaną w czasie kompilacji od obiektu tylko do odczytu.", "square=49 limit=12", { + "main.cpp": """#include +constexpr int square(int value) { return value * value; } +int main() { + constexpr int result = square(7); + const int limit{12}; + static_assert(result == 49); + std::cout << "square=" << result << " limit=" << limit << '\\n'; +} +""", + }), + ("enum class i struktura", "Zbuduj typ rekordu oraz silnie typowany stan bez niejawnej konwersji do int.", "sample=23 state=ready", { + "main.cpp": """#include +enum class State { idle, ready }; +struct Sample { int value; State state; }; +int main() { + Sample sample{23, State::ready}; + std::cout << "sample=" << sample.value << " state=" << (sample.state == State::ready ? "ready" : "idle") << '\\n'; +} +""", + }), + ], + }, + { + "id": "CPP03", "number": "03", "repo": "lab-cpp-pointers-references", "slug": "pointers-references", + "title": "Wskaźniki, referencje, tablice i nullptr", + "topic": "Tożsamość obiektu, dostęp pośredni i przekazywanie argumentów", + "chapter": "A Tour of C++ 3e: sekcje 1.7 i 3.4", + "mission": "Odróżnić wskaźnik od referencji i tablicę od wskaźnika podczas przekazywania danych.", + "tasks": [ + ("Wskaźnik i nullptr", "Sprawdź brak obiektu przed dereferencją, a następnie zmodyfikuj wskazywaną wartość.", "null=1 value=9", { + "main.cpp": """#include +int main() { + int value{7}; + int* pointer = nullptr; + const bool was_null = pointer == nullptr; + pointer = &value; + *pointer += 2; + std::cout << "null=" << was_null << " value=" << value << '\\n'; +} +""", + }), + ("Referencja jako alias", "Przekaż dwa obiekty przez referencję i zamień ich wartości.", "left=8 right=3", { + "main.cpp": """#include +void swap_values(int& left, int& right) { const int temp = left; left = right; right = temp; } +int main() { int left{3}; int right{8}; swap_values(left, right); std::cout << "left=" << left << " right=" << right << '\\n'; } +""", + }), + ("Tablica i jej rozmiar", "Policz elementy w referencji do tablicy, zanim argument zaniknie do wskaźnika.", "count=4 sum=20", { + "main.cpp": """#include +#include +template int sum(const int (&values)[N]) { int result{}; for (int value : values) result += value; return result; } +int main() { int values[]{2, 4, 6, 8}; std::cout << "count=" << (sizeof(values) / sizeof(values[0])) << " sum=" << sum(values) << '\\n'; } +""", + }), + ], + }, + { + "id": "CPP04", "number": "04", "repo": "lab-cpp-functions-overloading", "slug": "functions-overloading", + "title": "Funkcje, przeciążanie, wartości zwracane i modułowość", + "topic": "Sygnatura funkcji, wybór przeciążenia, zakres i granica modułu", + "chapter": "A Tour of C++ 3e: rozdziały 1 i 3", + "mission": "Zaprojektować mały interfejs funkcji i sprawdzić wybór przeciążenia oraz zwracanie wartości.", + "tasks": [ + ("Przeciążanie", "Wywołaj dwie funkcje o tej samej nazwie i różnych typach parametrów.", "int=6 double=3.5", { + "main.cpp": """#include +int twice(int value) { return value * 2; } +double twice(double value) { return value * 2.0; } +int main() { std::cout << "int=" << twice(3) << " double=" << twice(1.75) << '\\n'; } +""", + }), + ("Zwracanie przez wartość", "Zwróć kompletny wynik obliczenia jako wartość, bez ujawniania obiektu lokalnego.", "quotient=3 remainder=2", { + "main.cpp": """#include +struct Division { int quotient; int remainder; }; +Division divide(int value, int by) { return {value / by, value % by}; } +int main() { const Division result = divide(17, 5); std::cout << "quotient=" << result.quotient << " remainder=" << result.remainder << '\\n'; } +""", + }), + ("Interfejs modułu", "Oddziel publiczne deklaracje statystyk od ich implementacji.", "min=2 max=9", { + "stats.hpp": """#pragma once +namespace stats { int minimum(int left, int right); int maximum(int left, int right); } +""", + "stats.cpp": """#include "stats.hpp" +namespace stats { int minimum(int left, int right) { return left < right ? left : right; } int maximum(int left, int right) { return left > right ? left : right; } } +""", + "main.cpp": """#include "stats.hpp" +#include +int main() { std::cout << "min=" << stats::minimum(9, 2) << " max=" << stats::maximum(9, 2) << '\\n'; } +""", + }), + ], + }, + { + "id": "CPP05", "number": "05", "repo": "lab-cpp-classes-objects", "slug": "classes-objects", + "title": "Klasa i obiekt: interfejs oraz implementacja", + "topic": "Pola, metody, kontrola dostępu i zachowanie obiektu", + "chapter": "A Tour of C++ 3e: rozdziały 2 i 5", + "mission": "Zamknąć stan i operacje w klasie oraz oddzielić interfejs publiczny od implementacji.", + "tasks": [ + ("Klasa w dwóch plikach", "Zdefiniuj interfejs licznika w nagłówku, a metody w osobnej jednostce translacji.", "counter=3", { + "counter.hpp": """#pragma once +class Counter { public: explicit Counter(int start); void increment(); int value() const; private: int value_; }; +""", + "counter.cpp": """#include "counter.hpp" +Counter::Counter(int start) : value_{start} {} +void Counter::increment() { ++value_; } +int Counter::value() const { return value_; } +""", + "main.cpp": """#include "counter.hpp" +#include +int main() { Counter counter{2}; counter.increment(); std::cout << "counter=" << counter.value() << '\\n'; } +""", + }), + ("Stan prywatny", "Udostępnij operacje rachunku bez publicznego zapisu pola balance.", "accepted=1 rejected=1 balance=15", { + "main.cpp": """#include +class Account { +public: + explicit Account(int balance) : balance_{balance} {} + bool deposit(int amount) { if (amount <= 0) return false; balance_ += amount; return true; } + int balance() const { return balance_; } +private: int balance_; +}; +int main() { Account account{10}; const bool accepted = account.deposit(5); const bool rejected = !account.deposit(-2); std::cout << "accepted=" << accepted << " rejected=" << rejected << " balance=" << account.balance() << '\\n'; } +""", + }), + ("Metody opisują zachowanie", "Przenieś obliczenie pola i skalowanie do obiektu prostokąta.", "area=24 scaled=96", { + "main.cpp": """#include +class Rectangle { public: Rectangle(int width, int height) : width_{width}, height_{height} {} int area() const { return width_ * height_; } void scale(int factor) { width_ *= factor; height_ *= factor; } private: int width_; int height_; }; +int main() { Rectangle rectangle{6, 4}; const int before = rectangle.area(); rectangle.scale(2); std::cout << "area=" << before << " scaled=" << rectangle.area() << '\\n'; } +""", + }), + ], + }, + { + "id": "CPP06", "number": "06", "repo": "lab-cpp-invariants", "slug": "invariants", + "title": "Hermetyzacja, niezmienniki i odpowiedzialność klasy", + "topic": "Poprawny stan obiektu przed i po każdej operacji publicznej", + "chapter": "A Tour of C++ 3e: sekcja 4.3 i rozdział 5", + "mission": "Zdefiniować niezmiennik klasy i utrzymywać go we wszystkich publicznych operacjach.", + "tasks": [ + ("Walidowana zmiana stanu", "Odrzuć temperaturę spoza przyjętego zakresu bez uszkodzenia obiektu.", "accepted=1 rejected=1 value=25", { + "main.cpp": """#include +class Temperature { public: explicit Temperature(int value) : value_{value} {} bool set(int value) { if (value < -50 || value > 150) return false; value_ = value; return true; } int value() const { return value_; } private: int value_; }; +int main() { Temperature temperature{20}; const bool accepted = temperature.set(25); const bool rejected = !temperature.set(400); std::cout << "accepted=" << accepted << " rejected=" << rejected << " value=" << temperature.value() << '\\n'; } +""", + }), + ("Pojemność kolejki", "Nie dopuść, by liczba elementów przekroczyła pojemność lub spadła poniżej zera.", "pushes=2 overflow=1 pops=2 empty=1 depth=0", { + "main.cpp": """#include +class QueueDepth { public: explicit QueueDepth(int capacity) : capacity_{capacity} {} bool push() { if (depth_ == capacity_) return false; ++depth_; return true; } bool pop() { if (depth_ == 0) return false; --depth_; return true; } int depth() const { return depth_; } private: int capacity_; int depth_{}; }; +int main() { QueueDepth queue{2}; int pushes{}; pushes += queue.push(); pushes += queue.push(); const bool overflow = !queue.push(); int pops{}; pops += queue.pop(); pops += queue.pop(); const bool empty = !queue.pop(); std::cout << "pushes=" << pushes << " overflow=" << overflow << " pops=" << pops << " empty=" << empty << " depth=" << queue.depth() << '\\n'; } +""", + }), + ("Normalizacja przedziału", "Skonstruuj przedział zawsze spełniający start <= end.", "start=3 end=10 width=7", { + "main.cpp": """#include +class Interval { public: Interval(int first, int second) : start_{first < second ? first : second}, end_{first < second ? second : first} {} int start() const { return start_; } int end() const { return end_; } int width() const { return end_ - start_; } private: int start_; int end_; }; +int main() { Interval interval{10, 3}; std::cout << "start=" << interval.start() << " end=" << interval.end() << " width=" << interval.width() << '\\n'; } +""", + }), + ], + }, + { + "id": "CPP07", "number": "07", "repo": "lab-cpp-construction-destruction", "slug": "construction-destruction", + "title": "Konstruktory, destruktory i inicjalizacja obiektu", + "topic": "Kolejność inicjalizacji, delegowanie konstruktorów i porządek destrukcji", + "chapter": "A Tour of C++ 3e: rozdziały 5 i 6", + "mission": "Zaobserwować pełny cykl tworzenia i niszczenia obiektu oraz kolejność jego części.", + "tasks": [ + ("Kolejność konstrukcji", "Zaobserwuj, że pole jest konstruowane przed ciałem konstruktora obiektu.", "member\nobject\nready", { + "main.cpp": """#include +struct Member { Member() { std::cout << "member\\n"; } }; +struct Object { Member member; Object() { std::cout << "object\\n"; } }; +int main() { Object object; (void)object; std::cout << "ready\\n"; } +""", + }), + ("Destrukcja w odwrotnej kolejności", "Utwórz dwa obiekty automatyczne i sprawdź kolejność destruktorów.", "enter\nconstruct=A\nconstruct=B\ndestroy=B\ndestroy=A\nleave", { + "main.cpp": """#include +struct Trace { const char* name; explicit Trace(const char* value) : name{value} { std::cout << "construct=" << name << '\\n'; } ~Trace() { std::cout << "destroy=" << name << '\\n'; } }; +void scope() { Trace first{"A"}; Trace second{"B"}; } +int main() { std::cout << "enter\\n"; scope(); std::cout << "leave\\n"; } +""", + }), + ("Konstruktor delegujący", "Skieruj konstruktor domyślny do jednego konstruktora utrzymującego reguły inicjalizacji.", "point=0,0 other=3,4", { + "main.cpp": """#include +class Point { public: Point() : Point{0, 0} {} Point(int x, int y) : x_{x}, y_{y} {} int x() const { return x_; } int y() const { return y_; } private: int x_; int y_; }; +int main() { Point point; Point other{3, 4}; std::cout << "point=" << point.x() << ',' << point.y() << " other=" << other.x() << ',' << other.y() << '\\n'; } +""", + }), + ], + }, + { + "id": "CPP08", "number": "08", "repo": "lab-cpp-scope-lifetime", "slug": "scope-lifetime", + "title": "Zakres i czas życia obiektów", + "topic": "Obiekty automatyczne, statyczne i dynamiczne oraz przesłanianie nazw", + "chapter": "A Tour of C++ 3e: sekcja 1.5 i rozdział 6", + "mission": "Powiązać miejsce przechowywania obiektu z początkiem i końcem jego czasu życia.", + "tasks": [ + ("Automatyczny i statyczny", "Porównaj nowy obiekt automatyczny z zachowującym stan obiektem statycznym.", "auto=7 static_calls=1,2", { + "main.cpp": """#include +int next_call() { static int calls{}; return ++calls; } +int main() { int automatic{7}; const int first = next_call(); const int second = next_call(); std::cout << "auto=" << automatic << " static_calls=" << first << ',' << second << '\\n'; } +""", + }), + ("Obiekt dynamiczny", "Jawnie zestaw new z delete i obserwuj moment wykonania destruktora.", "construct\nvalue=11\ndestroy", { + "main.cpp": """#include +struct Dynamic { explicit Dynamic(int value) : value{value} { std::cout << "construct\\n"; } ~Dynamic() { std::cout << "destroy\\n"; } int value; }; +int main() { Dynamic* object = new Dynamic{11}; std::cout << "value=" << object->value << '\\n'; delete object; } +""", + }), + ("Zakres i przesłanianie", "Pokaż, że nazwa wewnętrzna nie zmienia obiektu o tej samej nazwie w zakresie zewnętrznym.", "outer=7\ninner=9\nouter=7", { + "main.cpp": """#include +int main() { int value{7}; std::cout << "outer=" << value << '\\n'; { int value{9}; std::cout << "inner=" << value << '\\n'; } std::cout << "outer=" << value << '\\n'; } +""", + }), + ], + }, + { + "id": "CPP09", "number": "09", "repo": "lab-cpp-raii", "slug": "raii", + "title": "RAII: zasób związany z czasem życia obiektu", + "topic": "Pozyskanie w konstruktorze, zwolnienie w destruktorze i wszystkie wyjścia z zakresu", + "chapter": "A Tour of C++ 3e: sekcja 6.3", + "mission": "Zapewnić zwolnienie zasobu przez strukturę programu, także przy wczesnym return.", + "tasks": [ + ("Uchwyt jako obiekt", "Zwiąż stan otwarcia uchwytu z konstruktorem i destruktorem.", "acquire\nopen=1\nrelease\nopen=0", { + "main.cpp": """#include +bool resource_open{}; +class Handle { public: Handle() { resource_open = true; std::cout << "acquire\\n"; } ~Handle() { resource_open = false; std::cout << "release\\n"; } Handle(const Handle&) = delete; Handle& operator=(const Handle&) = delete; }; +int main() { { Handle handle; (void)handle; std::cout << "open=" << resource_open << '\\n'; } std::cout << "open=" << resource_open << '\\n'; } +""", + }), + ("Wczesny return", "Potwierdź zwolnienie strażnika przy wyjściu z funkcji przed jej końcem.", "acquire\nwork=short\nrelease\nafter", { + "main.cpp": """#include +struct Guard { Guard() { std::cout << "acquire\\n"; } ~Guard() { std::cout << "release\\n"; } }; +void work(bool short_path) { Guard guard; if (short_path) { std::cout << "work=short\\n"; return; } std::cout << "work=long\\n"; } +int main() { work(true); std::cout << "after\\n"; } +""", + }), + ("Zagnieżdżone zasoby", "Sprawdź zwalnianie zagnieżdżonych zasobów w odwrotnej kolejności.", "acquire=A\nacquire=B\ninside\nrelease=B\nrelease=A", { + "main.cpp": """#include +struct Resource { const char* name; explicit Resource(const char* value) : name{value} { std::cout << "acquire=" << name << '\\n'; } ~Resource() { std::cout << "release=" << name << '\\n'; } }; +int main() { Resource first{"A"}; { Resource second{"B"}; std::cout << "inside\\n"; } } +""", + }), + ], + }, + { + "id": "CPP10", "number": "10", "repo": "lab-cpp-copy-move", "slug": "copy-move", + "title": "Kopiowanie, przenoszenie i Rule of Zero/Five", + "topic": "Głębokie kopiowanie, transfer własności i typy składowe zarządzające zasobem", + "chapter": "A Tour of C++ 3e: sekcje 6.2–6.4", + "mission": "Rozróżnić kopię od przeniesienia i wybrać Rule of Zero dla bezpiecznego typu zasobowego.", + "tasks": [ + ("Głęboka kopia", "Zaimplementuj kopię bufora tak, aby dwa obiekty nie współdzieliły jednej komórki.", "original=7 copy=9 distinct=1", { + "main.cpp": """#include +class Buffer { public: explicit Buffer(int value) : data_{new int{value}} {} ~Buffer() { delete data_; } Buffer(const Buffer& other) : data_{new int{*other.data_}} {} Buffer& operator=(const Buffer&) = delete; int value() const { return *data_; } void set(int value) { *data_ = value; } const int* address() const { return data_; } private: int* data_; }; +int main() { Buffer original{7}; Buffer copy{original}; copy.set(9); std::cout << "original=" << original.value() << " copy=" << copy.value() << " distinct=" << (original.address() != copy.address()) << '\\n'; } +""", + }), + ("Przeniesienie własności", "Przenieś bufor move-only i pozostaw obiekt źródłowy w poprawnym stanie pustym.", "src_empty=1 value=42", { + "main.cpp": """#include +#include +class Buffer { public: explicit Buffer(int value) : data_{new int{value}} {} ~Buffer() { delete data_; } Buffer(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete; Buffer(Buffer&& other) noexcept : data_{std::exchange(other.data_, nullptr)} {} bool empty() const { return data_ == nullptr; } int value() const { return *data_; } private: int* data_; }; +int main() { Buffer source{42}; Buffer target{std::move(source)}; std::cout << "src_empty=" << source.empty() << " value=" << target.value() << '\\n'; } +""", + }), + ("Rule of Zero", "Zbuduj typ z bezpiecznych składowych standardowych bez własnego destruktora i operacji copy/move.", "copy=alpha original=beta moved=alpha", { + "main.cpp": """#include +#include +#include +struct Document { std::string text; }; +int main() { Document original{"alpha"}; Document copy = original; original.text = "beta"; Document moved = std::move(copy); std::cout << "copy=" << moved.text << " original=" << original.text << " moved=" << moved.text << '\\n'; } +""", + }), + ], + }, +] + + +CARD_ACTION = r'''#!/usr/bin/env bash +set -euo pipefail +action="${1:?action is required}" +root="${STEM_REPO:-${CARD_ROOT:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)}}" +profile="${STEM_PROFILE:-native-amd64}" +target="${STEM_TARGET:-native}" +selector="${STEM_TASK:-task1}" +card_id="$(sed -n 's/^card: cpp\///p' "$root/lesson-flow.yaml" | head -n1)" +resolve_task() { + local value="${1,,}" + if [[ "$value" =~ ^(task|t)?[-_]?0*([1-3])($|[-_].*) ]]; then printf 'task%02d\n' "${BASH_REMATCH[2]}"; return; fi + [[ "$value" =~ ^task0[1-3]$ ]] && { printf '%s\n' "$value"; return; } + printf 'Unknown task selector: %s\n' "$1" >&2; exit 2 +} +task="$(resolve_task "$selector")" +artifact_dir="$root/.stem/artifacts/$profile/$target/$task" +binary="$artifact_dir/program" +mkdir -p "$artifact_dir" +compile_task() { + local selected="$1" output="$2" + mapfile -t sources < <(find "$root/src/$selected" -maxdepth 1 -type f -name '*.cpp' | sort) + [[ ${#sources[@]} -gt 0 ]] || { printf 'No C++ sources for %s\n' "$selected" >&2; exit 2; } + "${CXX:-g++}" -std=c++20 -Wall -Wextra -Wpedantic -Werror -O0 -g3 -fno-omit-frame-pointer -I"$root/src/$selected" "${sources[@]}" -o "$output" +} +verify_task() { + local selected="$1" output="$2" actual="$artifact_dir/actual.txt" + compile_task "$selected" "$output" + "$output" >"$actual" + diff -u "$root/tests/expected/$selected.txt" "$actual" +} +case "$profile:$target:$action" in + native-amd64:native:build) + for item in task01 task02 task03; do + current="$root/.stem/artifacts/$profile/$target/$item" + mkdir -p "$current" + compile_task "$item" "$current/program" + done + printf 'BUILD %s standard=c++20 tasks=3\n' "$card_id" + ;; + native-amd64:native:test|native-amd64:native:run) + verify_task "$task" "$binary" + printf 'PASS %s %s standard=c++20\n' "$card_id" "$task" + ;; + native-amd64:native:debug) + verify_task "$task" "$binary" + trace="$artifact_dir/debug.log" + if command -v gdb >/dev/null 2>&1; then + gdb -q -batch -ex 'set pagination off' -ex 'set debuginfod enabled off' -ex 'break main' -ex run -ex 'info source' -ex 'info args' -ex continue "$binary" >"$trace" 2>&1 + else + { printf 'gdb unavailable; binary evidence follows\n'; "${NM:-nm}" -C "$binary" | head -n 80; "$binary"; } >"$trace" 2>&1 + fi + cat "$trace" + [[ -s "$trace" ]] + printf 'TRACE %s %s log=%s\n' "$card_id" "$task" "$trace" + ;; + *) printf 'Unsupported profile/target/action: %s/%s/%s\n' "$profile" "$target" "$action" >&2; exit 2 ;; +esac +''' + + +def write(path: Path, content: str, executable: bool = False) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(clean(content), encoding="utf-8") + if executable: + path.chmod(path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + + +def card_source(lesson: dict, document_uuid: str) -> dict: + prefix = lesson["id"] + sections = [] + tasks = {} + scope_rows = [] + for index, (title, prompt, expected, _files) in enumerate(lesson["tasks"], 1): + task_id = f"task{index:02d}" + expected_tex = expected.replace("_", "\\_") + sections.append({ + "title": f"Task{index:02d} · {title}", + "content_kind": "prose", + "content_tex": f"{prompt} Najpierw przewidź wynik, następnie skompiluj program z ostrzeżeniami traktowanymi jako błędy i porównaj dokładny stdout z kontraktem: {expected_tex.replace(chr(10), '; ')}.", + "educational_requirement_refs": [f"{prefix}.WE01"], + "learning_effect_refs": [f"{prefix}.EN01", f"{prefix}.EK01"], + "assessment_criterion_refs": [f"{prefix}.KW01"], + "area_tree_refs": [f"{prefix}.WE01.OG.IP.01", f"{prefix}.WE01.TECH.I4.01"], + "steps": [ + {"id": f"T{index:02d}-PREDICT", "title": "Zapisz przewidywany wynik i wskaż regułę języka.", "tree_refs": [f"{prefix}.WE01.OG.IP.01"]}, + {"id": f"T{index:02d}-VERIFY", "title": "Uruchom test i zachowaj mierzalny dowód wykonania.", "tree_refs": [f"{prefix}.WE01.TECH.I4.01"]}, + ], + }) + tasks[task_id] = { + "prompt_tex": prompt, + "criterion": expected_tex.replace("\n", "; "), + "assessment_criterion_ref": f"{prefix}.KW01", + } + scope_rows.append({"chapter": "1", "task": f"Task{index:02d}", "idea_tex": title, "priority": "obowiązkowe", "status": "ready", "version": "v00.01"}) + + return { + "$schema": "../../../../tools/card-layouts/schemas/card-source.schema.json", + "schema": "esc-card-source.v1", + "card": { + "id": f"mpabi-inf-cpp-y2s1-{lesson['number']}-{lesson['slug']}", + "series": "cpp", + "series_title": "C++ · Rok 2 · Semestr 1", + "number": lesson["number"], "count": "10", "slug": lesson["slug"], + "title": f"{lesson['id']} · {lesson['title']}", "topic": lesson["topic"], + "project": "C++20 · fundamenty języka i modelu obiektu", "subject": "Informatyka", + "level": "Rok 2 · Semestr 1", "revision_date": "2026-07-21T00:00:00+02:00", + "status": "Gotowa do wykonania", "version": "v00.01", "uuid": document_uuid, + "author": "M. Pabiszczak", "year": "2026", + }, + "generated": {"tex": "doc/generated/main.tex", "html": "web/index.html", "html_css": "web/style.css", "react_app": True, "html_tree_inspector": False}, + "template": "templates/karta-klasyczna.json", + "title_block": { + "category": "KARTA PRACY · INFORMATYKA · C++20", "standard": "ISO 7200", + "prepared_by": "M. Pabiszczak", "prepared_on": "21.07.2026", "checked_by": "testy automatyczne", "approved_by": "—", + "url": "http://localhost:8080", "repository_url": f"https://zsl-gitea.mpabi.pl/edu-cpp/{lesson['repo']}", + "revision": "1", "issued_on": "21.07.2026", "series": f"CPP-Y2S1-{lesson['number']}", "document_type": "karta pracy", + "tool": "card-layouts", "sheet": "1 / 1", "show_qr": False, "show_repository_qr": False, + "height_cm": 3, "repeat_on_every_page": True, "replace_front_matter": True, + }, + "front_page_scope": {"title": "Cel karty", "content_tex": lesson["mission"], "scope_title": "Zakres tasków", "scope_table": {"headers": ["Krok", "Numer tasku", "Najważniejsza idea", "Priorytet", "Status", "Version"], "rows": scope_rows}}, + "learning_effects": { + f"{prefix}.EN01": {"text": f"Uczeń wyjaśnia reguły C++20 związane z tematem: {lesson['topic']}.", "label": "Model języka", "bloom_level": "Analiza", "assessment_criteria": [f"{prefix}.KW01"]}, + f"{prefix}.EK01": {"text": "Uczeń implementuje, kompiluje i weryfikuje trzy małe eksperymenty C++20.", "label": "Eksperyment C++", "bloom_level": "Zastosowanie", "assessment_criteria": [f"{prefix}.KW01"]}, + }, + "assessment_criteria": {f"{prefix}.KW01": {"text": "Trzy taski kompilują się bez ostrzeżeń i zwracają dokładny, testowany wynik.", "learning_effects": [f"{prefix}.EN01", f"{prefix}.EK01"]}}, + "educational_requirements": { + f"{prefix}.WE01": {"text": lesson["mission"], "label": lesson["topic"], "learning_effects": [f"{prefix}.EN01", f"{prefix}.EK01"], + "learning_tree": {"schema": "we-learning-tree.v1", "policy": "Każde twierdzenie o języku ma dowód w kodzie, wyniku programu albo symbolach binarnych.", + "ogolne": [{"tree_id": f"{prefix}.WE01.OG.IP.01", "effect_ref": f"{prefix}.EN01", "display": f"EN IP {prefix}", "text": "Uczeń przewiduje zachowanie programu na podstawie reguł języka.", "kw": [{"criterion_ref": f"{prefix}.KW01", "display": f"KW IP {prefix}", "text": "Uzasadnia przewidywany wynik."}]}], + "zawodowe": [{"tree_id": f"{prefix}.WE01.TECH.I4.01", "effect_ref": f"{prefix}.EK01", "display": f"EK INF04 {prefix}", "text": "Uczeń buduje i testuje program C++20.", "kw": [{"criterion_ref": f"{prefix}.KW01", "display": f"KW INF04 {prefix}", "text": "Uzyskuje trzy komunikaty PASS."}]}], + }, + } + }, + "sections": sections, "tasks": tasks, "tasks_order": ["task01", "task02", "task03"], + "mission": lesson["mission"], + "objectives": ["Wyjaśnisz badaną regułę C++20.", "Skompilujesz program z pełnym zestawem ostrzeżeń.", "Porównasz dokładny wynik i ślad debuggera."], + "agenda": [{"time": "0--15 min", "work": "Model i przewidywanie."}, {"time": "15--45 min", "work": "Trzy eksperymenty kodowe."}, {"time": "45--60 min", "work": "Debug, wnioski i raport."}], + "tech_stack": {"software": [{"name": "C++20", "use": "standard języka"}, {"name": "GCC/GDB", "use": "kompilacja i dowód wykonania"}, {"name": "stemctl", "use": "izolowany profil native-amd64"}], "hardware": [{"name": "Brak", "use": "laboratorium kontenerowe"}]}, + "container_test": {"title": "Test w profilu native-amd64", "commands": [f"stemctl test native-amd64 cpp {lesson['id']} 1", f"stemctl test native-amd64 cpp {lesson['id']} 2", f"stemctl debug native-amd64 cpp {lesson['id']} 3"], "pass_condition": "Trzy wyniki zgodne z expected i niepusty debug.log."}, + "safety_rules": [{"title": "Brak niezdefiniowanego zachowania", "body": "Kod testowy nie dereferencjonuje nullptr i nie używa obiektu po zakończeniu czasu życia."}, {"title": "Jedna zmienna naraz", "body": "Każdy task izoluje jedną regułę języka i ma deterministyczny stdout."}], + "hardware_procedure": [{"step": "1", "action": "Przewidź wynik.", "condition": "Reguła języka zapisana przed uruchomieniem."}, {"step": "2", "action": "Uruchom trzy taski.", "condition": "Trzy komunikaty PASS."}, {"step": "3", "action": "Odczytaj debug.log.", "condition": "Debugger zatrzymał program w main."}], + "references": [], + } + + +def generate_lesson(lesson: dict) -> None: + repo = OUTPUT / lesson["repo"] + if repo.exists() and (repo / ".git").exists(): + raise SystemExit(f"Refusing to overwrite Git repository: {repo}") + if repo.exists(): + shutil.rmtree(repo) + repo.mkdir(parents=True) + + shutil.copytree(TEMPLATE / "scripts", repo / "scripts") + shutil.copy2(TEMPLATE / ".gitignore", repo / ".gitignore") + (repo / "web").mkdir(parents=True) + shutil.copy2(TEMPLATE / "web" / "app.css", repo / "web" / "app.css") + (repo / "doc" / "pdf").mkdir(parents=True) + write(repo / "doc" / "pdf" / ".gitkeep", "") + + package = json.loads((TEMPLATE / "package.json").read_text(encoding="utf-8")) + package["name"] = f"mpabi-inf-cpp-y2s1-{lesson['number']}-{lesson['slug']}" + write(repo / "package.json", json.dumps(package, ensure_ascii=False, indent=2) + "\n") + lock = json.loads((TEMPLATE / "package-lock.json").read_text(encoding="utf-8")) + lock["name"] = package["name"] + if "packages" in lock and "" in lock["packages"]: + lock["packages"][""]["name"] = package["name"] + write(repo / "package-lock.json", json.dumps(lock, ensure_ascii=False, indent=2) + "\n") + + write(repo / "stem-card.yaml", """ + schema: 1 + targets: + native: {profile: native-amd64, actions: [build, test, run, debug]} + actions: + build: [bash, tools/card-action.sh, build] + test: [bash, tools/card-action.sh, test] + run: [bash, tools/card-action.sh, run] + debug: [bash, tools/card-action.sh, debug] + artifacts: {directory: .stem/artifacts} + """) + write(repo / "tools" / "card-action.sh", CARD_ACTION, executable=True) + write(repo / "Makefile", """ + TASK ?= task01 + .PHONY: build test run trace clean + build: + \t@CARD_ROOT="$(CURDIR)" tools/card-action.sh build + test: + \t@CARD_ROOT="$(CURDIR)" STEM_TASK="$(TASK)" tools/card-action.sh test + run: test + trace: + \t@CARD_ROOT="$(CURDIR)" STEM_TASK="$(TASK)" tools/card-action.sh debug + clean: + \t@find .stem/artifacts -mindepth 1 -maxdepth 7 -type f -delete 2>/dev/null || true + """) + + flow = ["schema: 1", f"card: cpp/{lesson['id']}", f"title: {lesson['title']}", "steps:"] + for index, (title, _prompt, _expected, _files) in enumerate(lesson["tasks"], 1): + flow.extend([ + f" - id: task-{index:02d}", f" task: task{index:02d}", " action: test", " profile: native-amd64", " target: native", + f" command: stemctl test native-amd64 cpp {lesson['id']} {index}", + f" board:", f" show: [source, stdout, binary]", f" highlight: [cxx20, {lesson['slug'].replace('-', '_')}]", + f" question: {title} — jaka reguła języka wyjaśnia dokładny wynik?", + f" evidence: [compile_warnings_zero, expected_stdout, exit_zero]", + ]) + write(repo / "lesson-flow.yaml", "\n".join(flow) + "\n") + + for index, (_title, _prompt, expected, files) in enumerate(lesson["tasks"], 1): + task = f"task{index:02d}" + for name, source in files.items(): + write(repo / "src" / task / name, source) + write(repo / "tests" / "expected" / f"{task}.txt", expected + "\n") + + document_uuid = str(uuid.uuid5(UUID_NAMESPACE, lesson["repo"])) + source = card_source(lesson, document_uuid) + write(repo / "json" / "card_source.json", json.dumps(source, ensure_ascii=False, indent=2) + "\n") + write(repo / "doc" / "main.tex", f""" + \\newcommand{{\\PublisherDomain}}{{mpabi}} + \\newcommand{{\\CardArea}}{{inf}} + \\newcommand{{\\CardSeries}}{{cpp-y2s1}} + \\newcommand{{\\CardNumber}}{{{lesson['number']}}} + \\newcommand{{\\CardSlug}}{{{lesson['slug']}}} + \\newcommand{{\\CardVersion}}{{v00.01}} + \\newcommand{{\\DocumentUUID}}{{{document_uuid}}} + """) + + expected_rows = "\n".join(f"| Task{index:02d} | `{title}` | `{expected.replace(chr(10), ' / ')}` |" for index, (title, _prompt, expected, _files) in enumerate(lesson["tasks"], 1)) + write(repo / "README.md", f""" + # {lesson['id']} — {lesson['title']} + + Karta {lesson['number']}/10 kursu **C++ · Rok 2 · Semestr 1**. Standard: C++20. + Oś merytoryczna: {lesson['chapter']}. Materiał książkowy jest parafrazowany; + repozytorium zawiera samodzielne eksperymenty i testy, a nie kopię książki. + + ## Taski + + | Task | Temat | Dokładny dowód stdout | + | --- | --- | --- | + {expected_rows} + + Każdy task jest osobnym programem w `src/task0N`. Test kompiluje wszystkie + jednostki translacji poleceniem zgodnym z `-std=c++20 -Wall -Wextra + -Wpedantic -Werror`, uruchamia program i porównuje dokładny wynik. + + ## Uruchomienie + + ```bash + stemctl build native-amd64 cpp {lesson['id']} 1 + stemctl test native-amd64 cpp {lesson['id']} 1 + stemctl test native-amd64 cpp {lesson['id']} 2 + stemctl debug native-amd64 cpp {lesson['id']} 3 + ``` + + Lokalnie można użyć `make build`, `make test TASK=task02` i + `make trace TASK=task03`. Ślad debuggera trafia do `.stem/artifacts`. + + ## Granica semestru + + Ta seria nie uczy API RTOS, zaawansowanych kontenerów STL ani + współbieżności. Są to osobne bloki następnych semestrów opisanych w + `/home/user/dev/keys/cxx.md`. + """) + + answer_lines = [f"# Odpowiedzi — {lesson['id']}", "", lesson["mission"], ""] + for index, (title, prompt, expected, files) in enumerate(lesson["tasks"], 1): + answer_lines.extend([ + f"## Task{index:02d} — {title}", "", prompt, "", + f"Oczekiwany stdout: `{expected.replace(chr(10), ' / ')}`.", "", + f"Pliki dowodu: {', '.join(f'`src/task{index:02d}/{name}`' for name in sorted(files))}.", "", + "Wynik nie jest oceniany wzrokowo: test kompiluje kod bez ostrzeżeń, uruchamia go i wykonuje dokładne `diff` z plikiem `tests/expected`.", "", + ]) + write(repo / "answers" / "solutions.md", "\n".join(answer_lines)) + + write(repo / "tests" / "card_contract.test.mjs", f""" + import assert from 'node:assert/strict'; + import {{ access, readFile }} from 'node:fs/promises'; + import test from 'node:test'; + const source = JSON.parse(await readFile(new URL('../json/card_source.json', import.meta.url), 'utf8')); + test('{lesson['id']} identity and task order are stable', () => {{ + assert.equal(source.card.number, '{lesson['number']}'); + assert.equal(source.card.uuid, '{document_uuid}'); + assert.deepEqual(source.tasks_order, ['task01', 'task02', 'task03']); + assert.match(JSON.stringify(source), /C\\+\\+20/); + }}); + test('all task programs and expected outputs exist', async () => {{ + for (const task of ['task01', 'task02', 'task03']) {{ + await access(new URL(`../src/${{task}}/main.cpp`, import.meta.url)); + await access(new URL(`./expected/${{task}}.txt`, import.meta.url)); + }} + }}); + """) + shutil.copy2(TEMPLATE / "tests" / "card_state_db.test.mjs", repo / "tests" / "card_state_db.test.mjs") + + +def main() -> None: + OUTPUT.mkdir(parents=True, exist_ok=True) + for lesson in LESSONS: + generate_lesson(lesson) + print(f"generated {lesson['id']} {lesson['repo']}") + + +if __name__ == "__main__": + main() diff --git a/workspace.json b/workspace.json index a228bdc..891484b 100644 --- a/workspace.json +++ b/workspace.json @@ -32,6 +32,12 @@ "title": "FreeRTOS C++ series", "role": "series", "series": ["freertos-cpp"] + }, + { + "id": "edu-cpp", + "title": "C++ course series", + "role": "series", + "series": ["cpp"] } ], "series": [ @@ -62,6 +68,10 @@ { "id": "freertos-cpp", "file": "series/freertos-cpp.json" + }, + { + "id": "cpp", + "file": "series/cpp.json" } ], "catalog_subjects": [