Skip to content

Commit a456585

Browse files
committed
Merge branch 'develop'
2 parents 5cbc8bb + 41f9847 commit a456585

File tree

7 files changed

+109
-58
lines changed

7 files changed

+109
-58
lines changed

README.md

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ python build/build.py
1414

1515
После сборки необходимо импортировать XML файл в систему Moodle.
1616

17-
Для этого нужно перейти в *режим редактирования*, после нажать *Добавитьэлемент или ресурс*, выбрать *Тест*, ввести необходимые настройки в частности *Название*.
17+
Для этого нужно перейти в *режим редактирования*, после нажать *Добавить элемент или ресурс*, выбрать *Тест*, ввести необходимые настройки в частности *Название*.
1818

1919
После создания теста переходим в вкладку *Банк вопросов*, в выпадающем списке с названием *Вопросы* выбираем импорт, выбираем формат *Формат Moodle XML* и загружаем XML-файл
2020

@@ -94,9 +94,9 @@ class PrintSeedQuestion(QuestionBase):
9494
## Класс `QuestionRandomCondition`
9595
Класс обёртки для задания изменения массива по случайному условию.
9696

97-
### Методы экземпляра класса:
97+
### Методы класса:
9898

99-
- `__init__(self, *, seed: int, condition_length: int, array_length: int, strictness: float)` - метод инициализации экземпляра.
99+
- `__init__(self, *, seed: int, condition_length: int, array_length: int, strictness: float)` - метод инициализации
100100

101101
* `seed` - случайное зерно, используемое для воспроизводимости результатов псевдослучайной генерации случайных значений;
102102
* `condition_length` - целое число, указывающее длину условия (количество операндов в генерируемом условии);
@@ -164,20 +164,69 @@ class PrintSeedQuestion(QuestionBase):
164164

165165
В случае возникновения ошибки выполнение остальных тестов заканчивается и возвращается соответствующая полученной ошибке строка. Если все тесты выполнены корректно - метод возвращает строку `OK`.
166166

167-
### Свойства экземпляра класса:
167+
### Свойства класса:
168168

169169
- `questionName: str` - название вопроса.
170170

171171
- `questionText: str` - задание/текст вопроса.
172172

173173
- `preloadedCode: str` - код, который подгружается в поле редактирования кода.
174174

175+
## Класс `QuestionRandomExpression`
176+
177+
Класс для генерации и тестирования задач на вычисление выражений с использованием случайных данных.
178+
179+
### Методы класса
180+
181+
- `__init__(self, *, seed: int, vars=['x', 'y', 'z', 'w'], operations=['+', '-', '*', '&', '|'], length=5, minuses_threshold=0, brackets_treshold=0, minus_symbol="-", all_variables=False, strictness=0)` - Метод инициализации экземпляра.
182+
- **seed**: Сид для генерации случайных данных.
183+
- **vars**: Список переменных для генерации выражений (по умолчанию `['x', 'y', 'z', 'w']`).
184+
- **operations**: Допустимые операции для выражений (по умолчанию `['+', '-', '*', '&', '|']`).
185+
- **length**: Длина генерируемого выражения (по умолчанию `5`).
186+
- **minuses_threshold**: Порог для использования унарных минусов (в диапазоне `[0; 1]`, по умолчанию `0`).
187+
- **brackets_treshold**: Порог для генерации скобок (в диапазоне `[0; 1]`, по умолчанию `0`).
188+
- **minus_symbol**: Символ для отображения минуса (по умолчанию `"-"`).
189+
- **all_variables**: Флаг обязательного использования всех переменных (по умолчанию `False`).
190+
- **strictness**: Уровень строгости проверки (в диапазоне `[0; 1]`, влияет на количество тестов).
191+
192+
193+
- `generate_c_code(self)`
194+
Генерирует код на языке C для вычисления сгенерированного выражения.
195+
196+
Возвращает строку с кодом на языке C.
197+
198+
- `test(self, code: str) -> str`
199+
Организует тестирование переданного кода.
200+
201+
- **code**: Код, который необходимо протестировать.
202+
203+
Возвращает одну из строк:
204+
- `"OK"` — если код прошел все тесты.
205+
- `"Ошибка компиляции"` — если код не компилируется.
206+
- `"Ошибка выполнения [{exit_code}]: {error}"` — если код завершился с ошибкой выполнения.
207+
- Сообщение об ошибке теста с описанием входных данных и ожидаемого результата.
208+
209+
### Свойства класса
210+
211+
- `preloadedCode: str`
212+
Код, который подгружается в поле редактирования кода.
213+
214+
- `questionText: str`
215+
Текст задания. Генерируется автоматически и включает:
216+
- Условие задачи.
217+
- Формат ввода и вывода.
218+
- Пример входных и выходных данных.
219+
220+
- `questionExpression: str`
221+
Генерируемое выражение для задачи. Использует заданные переменные, операции и параметры генерации.
222+
223+
175224
## Класс `QuestionStringOperations`
176225
Класс обёртки для задания применения заданных операций к строке.
177226

178-
### Методы экземпляра класса:
227+
### Методы класса:
179228

180-
- `__init__(self, *, seed: int, num_operations: int, min_length: int, max_length: int, strictness: float)` - метод инициализации экземпляра.
229+
- `__init__(self, *, seed: int, num_operations: int, min_length: int, max_length: int, strictness: float)` - метод инициализации класса.
181230

182231
* `seed` - случайное зерно, используемое для воспроизводимости результатов псевдослучайной генерации случайных операций, строк и тестов;
183232
* `num_operations` - целое число, указывающее количество операций задания;
@@ -225,7 +274,7 @@ class PrintSeedQuestion(QuestionBase):
225274
* `Ошибка выполнения (код {e.exit_code}): {e}` - если код студента заврешился ошибкой выполнения.
226275

227276

228-
### Свойства экземпляра класса:
277+
### Свойства класса:
229278

230279
- `questionName: str` - название вопроса.
231280

@@ -261,12 +310,13 @@ class PrintSeedQuestion(QuestionBase):
261310
* `message` - сообщение об ошибке
262311
* `exit_code` - код завершения программы
263312

264-
### Вспомогательный класс `ExitCodeHandler`:
313+
## Вспомогательный класс `ExitCodeHandler`:
265314
Класс для обработки кодов завершения и сигналов.
266315

267-
Методы класса:
316+
### Методы класса:
268317

269318
- `get_exit_message(self, exit_code) -> str` - преобразует код завершения в текстовое сообщение
319+
270320
* `exit_code` - код завершения программы
271321
* *Возвращаемое значение* - текстовое описание кода завершения
272322

build/build.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,31 @@
5151
xml_template.xpath('//file')[0].text = bundle_base64
5252

5353

54-
# Класс извлечения узла аргументов из конструктора класса
55-
class InitArgumentsExtractor(ast.NodeVisitor):
54+
# Класс извлечения узла аргументов из конструктора класса и именя вопроса
55+
class InternalQuestionDataExtractor(ast.NodeVisitor):
5656
def visit_FunctionDef(self, node):
5757
if node.name != '__init__':
5858
return
5959

6060
self.arguments_node = node.args
6161

62+
def visit_Assign(self, node):
63+
if len(node.targets) < 1 or not isinstance(node.targets[0], ast.Name) or node.targets[0].id != 'questionName' or not isinstance(node.value, ast.Constant):
64+
return
65+
66+
self.question_name = node.value.value
67+
68+
def visit_AnnAssign(self, node):
69+
if not isinstance(node.target, ast.Name) or node.target.id != 'questionName' or not isinstance(node.value, ast.Constant):
70+
return
71+
72+
self.question_name = node.value.value
73+
6274
def extract(self, node: ast.AST) -> ast.arguments | None:
6375
self.arguments_node = None
76+
self.question_name = None
6477
self.visit(node)
65-
return self.arguments_node
78+
return self.arguments_node, self.question_name
6679

6780
# Класс извлечения названия класса и узла аргументов конструктора класса для потомков QuestionBase
6881
class QuestionDataExtractor(ast.NodeVisitor):
@@ -72,14 +85,15 @@ def visit_ClassDef(self, node):
7285

7386
self.class_name = node.name
7487

75-
arguments_extractor = InitArgumentsExtractor()
76-
self.arguments_node = arguments_extractor.extract(node)
88+
arguments_extractor = InternalQuestionDataExtractor()
89+
self.arguments_node, self.question_name = arguments_extractor.extract(node)
7790

7891
def extract(self, node: ast.AST) -> tuple[str | None, ast.arguments | None]:
7992
self.class_name = None
8093
self.arguments_node = None
94+
self.question_name = None
8195
self.visit(node)
82-
return self.class_name, self.arguments_node
96+
return self.class_name, self.arguments_node, self.question_name
8397

8498

8599
# Шаблоны кода, внедряемого в xml-файл
@@ -103,7 +117,7 @@ def extract(self, node: ast.AST) -> tuple[str | None, ast.arguments | None]:
103117
# Проверка для всех файлов проекта
104118
for file in sources:
105119
# Получение информации о классе вопроса из файла
106-
question_class, question_arguments = QuestionDataExtractor().extract(ast.parse(file.read_text(encoding='utf-8')))
120+
question_class, question_arguments, question_name = QuestionDataExtractor().extract(ast.parse(file.read_text(encoding='utf-8')))
107121

108122
# Если в файле нет класса вопроса - пропускаем
109123
if question_class is None:
@@ -129,7 +143,7 @@ def extract(self, node: ast.AST) -> tuple[str | None, ast.arguments | None]:
129143
code = code_template.format(class_name=question_class).lstrip()
130144

131145
# Модификация xml-шаблона
132-
xml_template.xpath('//question/name/text')[0].text = question_class
146+
xml_template.xpath('//question/name/text')[0].text = xml.CDATA(question_name)
133147
xml_template.xpath('//templateparams')[0].text = xml.CDATA(parameters_code)
134148
xml_template.xpath('//template')[0].text = xml.CDATA(code)
135149

src/prog_questions/QuestionBase.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55

66
class QuestionBase(ABC):
7+
questionName: str = ''
8+
'''
9+
Название вопроса
10+
'''
11+
712
def __init__(self, *, seed: int, **parameters):
813
self.seed = seed
914
self.parameters = parameters
@@ -34,21 +39,12 @@ def getTemplateParameters(self) -> str:
3439
Возвращает параметры в формате JSON для шаблонизатора Twig
3540
'''
3641
return json.dumps({
37-
'QUESTION_NAME': self.questionName,
3842
'QUESTION_TEXT': self.questionText,
3943
'PRELOADED_CODE': self.preloadedCode,
4044
'SEED': self.seed,
4145
'PARAMETERS': json.dumps(self.parameters | { 'seed': self.seed }),
4246
})
4347

44-
@property
45-
@abstractmethod
46-
def questionName(self) -> str:
47-
'''
48-
Название вопроса
49-
'''
50-
...
51-
5248
@property
5349
@abstractmethod
5450
def questionText(self) -> str:

src/prog_questions/QuestionRandomCondition.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,15 @@
7373
"""
7474

7575
class QuestionRandomCondition(QuestionBase):
76+
questionName = "Случайное условие"
7677

77-
"""
78-
:param seed: Seed для воспроизводимости тестов.
79-
:param condition_length: Длина условия задачи.
80-
:param array_length: Длина массива данных.
81-
:param strictness: Параметр для регулирования количества случайных тестов (0.0 - минимум, 1.0 - максимум).
82-
"""
8378
def __init__(self, *, seed: int, condition_length: int=4, array_length: int=10, strictness: float=1):
79+
"""
80+
:param seed: Seed для воспроизводимости тестов.
81+
:param condition_length: Длина условия задачи.
82+
:param array_length: Длина массива данных.
83+
:param strictness: Параметр для регулирования количества случайных тестов (0.0 - минимум, 1.0 - максимум).
84+
"""
8485
super().__init__(seed=seed, condition_length=condition_length, array_length=array_length, strictness=strictness)
8586
self.task = Task(array_length, condition_length, seed)
8687
self.parse(self.task.text)
@@ -97,10 +98,6 @@ def __init__(self, *, seed: int, condition_length: int=4, array_length: int=10,
9798
)
9899
self.expected_output_runner = CProgramRunner(self.example_solution)
99100

100-
@property
101-
def questionName(self) -> str:
102-
return "Случайное условие"
103-
104101
@property
105102
def questionText(self) -> str:
106103
cleaned_text = dedent(BASE_TEXT)

src/prog_questions/QuestionRandomExpression.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
class QuestionRandomExpression(QuestionBase):
14-
"""Демонстрационный класс, реализующий задачу сложения чисел"""
14+
questionName = "Вычисление выражения"
1515

1616
def __init__(self, *, seed: int, vars=['x','y','z','w'], operations=['+','-','*','&','|'], length=5,
1717
minuses_threshold=0,
@@ -49,10 +49,6 @@ def __init__(self, *, seed: int, vars=['x','y','z','w'], operations=['+','-','*'
4949
self.max_space_number = 15
5050
self.space_amount = self.min_space_number + self.strictness * (self.max_space_number - self.min_space_number)
5151

52-
@property
53-
def questionName(self) -> str:
54-
return f"Сложение чисел"
55-
5652
def generate_c_code(self):
5753
# Сортируем переменные в алфавитном порядке
5854
sorted_vars = sorted(self.vars)

src/prog_questions/QuestionStringOperations.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,23 @@
4545

4646

4747
class QuestionStringOperations(QuestionBase):
48-
"""
49-
:param seed: Seed для воспроизводимости тестов.
50-
:param num_operations: количество операций задачи.
51-
:param min_length: минимальная длина входных данных.
52-
:param max_length: максимальная длина входных данных.
53-
:param strictness: Параметр для регулирования количества случайных тестов (0.0 - минимум, 1.0 - максимум).
54-
"""
48+
questionName = 'Операции над строками'
49+
5550
def __init__(self, *, seed: int, num_operations: int=3, min_length: int=30, max_length: int=100, strictness: float=1):
51+
"""
52+
:param seed: Seed для воспроизводимости тестов.
53+
:param num_operations: количество операций задачи.
54+
:param min_length: минимальная длина входных данных.
55+
:param max_length: максимальная длина входных данных.
56+
:param strictness: Параметр для регулирования количества случайных тестов (0.0 - минимум, 1.0 - максимум).
57+
"""
5658
super().__init__(seed=seed, num_operations=num_operations,
5759
min_length=min_length, max_length=max_length, strictness=strictness)
5860
self.strictness = strictness
5961
self.operations = generate_operations(seed, num_operations)
6062
self.min_length = min_length
6163
self.max_length = max_length
6264

63-
@property
64-
def questionName(self) -> str:
65-
return "Операции над строками"
66-
6765
@property
6866
def questionText(self) -> str:
6967
dedent_question_text = dedent(QUESTION_TEXT)
@@ -80,7 +78,7 @@ def noise_input_string(self, input_string: str):
8078
if self.strictness == 0.0 or len(input_string) >= self.max_length:
8179
return input_string
8280

83-
noise_chars = "!@#$%^&*()[]{}/?|~ "
81+
noise_chars = "!@#$%^&*()[]{}/?|~"
8482
noise_level = int(self.strictness * 10)
8583

8684
words = input_string.split(" ")

tests/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ class TestQuestionSum:
4545

4646
### Функции:
4747

48-
`moodleInit(questionClass: type[QuestionBase], *, seed: int, **parameters) -> QuestionBase` - создаёт экземпляр класса `questionClass` с эмуляцией пробрасывания параметров, как если бы это было заинтегрировано в moodle
48+
- `moodleInit(questionClass: type[QuestionBase], *, seed: int, **parameters) -> QuestionBase` - создаёт экземпляр класса `questionClass` с эмуляцией пробрасывания параметров, как если бы это было заинтегрировано в moodle
4949

50-
- `questionClass` - **тип** класса вопроса, экземпляр которого будет создаваться
51-
- `seed` - сид, с которым будет создаваться вопрос
52-
- `parameters` - параметры, с которыми будет создаваться вопрос
53-
- *Возвращаемое значение* - созданный экземпляр класса
50+
- `questionClass` - **тип** класса вопроса, экземпляр которого будет создаваться
51+
- `seed` - сид, с которым будет создаваться вопрос
52+
- `parameters` - параметры, с которыми будет создаваться вопрос
53+
- *Возвращаемое значение* - созданный экземпляр класса

0 commit comments

Comments
 (0)