Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the parser #62

Draft
wants to merge 234 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
234 commits
Select commit Hold shift + click to select a range
400c795
first steps to replace the old parser
PhilippImhof Nov 30, 2022
a94dffa
lexing, parsing (partial), shunting yard
PhilippImhof Feb 26, 2023
478ec2d
update include()
PhilippImhof Feb 26, 2023
107302d
separate token types for different parentheses
PhilippImhof Feb 26, 2023
40428e6
progress on functions and ternary operators
PhilippImhof Mar 3, 2023
52ff940
progress on arrays
PhilippImhof Mar 4, 2023
82a29fc
implement PREFIX token
PhilippImhof Mar 5, 2023
359de77
add support for pi and π as constant, not function
PhilippImhof Mar 5, 2023
d09223b
improved error reporting
PhilippImhof Mar 5, 2023
751a073
error reporting for mismatched parens
PhilippImhof Mar 6, 2023
62f430b
refactoring for single-char tokens
PhilippImhof Mar 6, 2023
6f7ad19
parsing of fixed-value ranges & improvements
PhilippImhof Mar 7, 2023
7f99762
add general check for unbalanced parens
PhilippImhof Mar 7, 2023
fcc7328
remove includes and use auto-loading
PhilippImhof Mar 7, 2023
2981508
remove redundant check for mismatched parens
PhilippImhof Mar 8, 2023
414e150
some code cleanup
PhilippImhof Mar 8, 2023
d5e26e3
refactoring, improvements, tests
PhilippImhof Mar 9, 2023
fbe3b07
ranges
PhilippImhof Mar 24, 2023
aa28be5
range improvements + tests
PhilippImhof Mar 24, 2023
e15f0d7
add some syntax checks + cleanup
PhilippImhof Mar 24, 2023
da3405c
handling of π, pi and pi()
PhilippImhof Mar 25, 2023
a85f19e
implement for loops, refactoring
PhilippImhof Mar 26, 2023
c43313c
refactoring
PhilippImhof Mar 26, 2023
39488c2
first steps for evaluator
PhilippImhof Mar 29, 2023
eab2ae7
fix test
PhilippImhof Mar 29, 2023
6c0175a
allow access to char from string
PhilippImhof Apr 13, 2023
95e51c5
change syntax for accessing pi
PhilippImhof Apr 13, 2023
31f70e8
cleanup: array(...) -> [...]
PhilippImhof Apr 13, 2023
52e9aff
cleanup: remove unused code in shunting yard
PhilippImhof Apr 14, 2023
1b90799
implement access to array elements & string chars
PhilippImhof Apr 14, 2023
3a6a221
port some of the functions from existing code
PhilippImhof Apr 14, 2023
3bcbde4
cleanup, add checks to binary operators
PhilippImhof Apr 15, 2023
b2a287a
improved binary operators, bugfix for logical NOT
PhilippImhof Apr 15, 2023
0bdceeb
add class for variables
PhilippImhof Apr 15, 2023
1812559
deal with variables during evaluation (partially)
PhilippImhof Apr 15, 2023
ca25f9b
add fqversionnumber() to functions
PhilippImhof Apr 15, 2023
4863e54
cosmetic changes
PhilippImhof Apr 15, 2023
642a061
evaluation of for loops, some cleanup
PhilippImhof Apr 16, 2023
7c688da
assignment to array elements, cleanup
PhilippImhof Apr 18, 2023
3665598
disallow assigning values to reserved variables
PhilippImhof Apr 22, 2023
be4717e
helper to wrap values in token with correct type
PhilippImhof Apr 22, 2023
02126c1
implement various functions
PhilippImhof Apr 22, 2023
fda3889
some refactoring
PhilippImhof Apr 30, 2023
5d3179f
support random variables
PhilippImhof May 1, 2023
e075bab
sync row/column of value and variable
PhilippImhof May 1, 2023
54880ce
support recursive and non-recursive shuffling
PhilippImhof May 1, 2023
db1071c
implement inv() function
PhilippImhof May 2, 2023
4490de5
change sort() order for negative numbers
PhilippImhof May 6, 2023
7d2b281
implement map() function
PhilippImhof May 8, 2023
0d93222
resolve variables before sending them to functions
PhilippImhof May 9, 2023
31612a4
fact(): throw error if result too big
PhilippImhof May 10, 2023
026b83e
rewrite of questiontype.php
PhilippImhof Jul 25, 2023
e8e1bc0
first steps to replace the old parser
PhilippImhof Nov 30, 2022
64afda7
lexing, parsing (partial), shunting yard
PhilippImhof Feb 26, 2023
f1c626d
update include()
PhilippImhof Feb 26, 2023
3465c17
separate token types for different parentheses
PhilippImhof Feb 26, 2023
23b09b9
progress on functions and ternary operators
PhilippImhof Mar 3, 2023
63e456b
progress on arrays
PhilippImhof Mar 4, 2023
57c4a92
implement PREFIX token
PhilippImhof Mar 5, 2023
0c26ca8
add support for pi and π as constant, not function
PhilippImhof Mar 5, 2023
82a11c2
improved error reporting
PhilippImhof Mar 5, 2023
5d90795
error reporting for mismatched parens
PhilippImhof Mar 6, 2023
36cf8eb
refactoring for single-char tokens
PhilippImhof Mar 6, 2023
6b7af50
parsing of fixed-value ranges & improvements
PhilippImhof Mar 7, 2023
fdb7c2e
add general check for unbalanced parens
PhilippImhof Mar 7, 2023
e2ebff6
remove includes and use auto-loading
PhilippImhof Mar 7, 2023
6fa32ee
remove redundant check for mismatched parens
PhilippImhof Mar 8, 2023
77b95b7
some code cleanup
PhilippImhof Mar 8, 2023
671f924
refactoring, improvements, tests
PhilippImhof Mar 9, 2023
84ea88c
ranges
PhilippImhof Mar 24, 2023
e5582e8
range improvements + tests
PhilippImhof Mar 24, 2023
b585483
add some syntax checks + cleanup
PhilippImhof Mar 24, 2023
438dce5
handling of π, pi and pi()
PhilippImhof Mar 25, 2023
9dd18f9
implement for loops, refactoring
PhilippImhof Mar 26, 2023
fd23215
refactoring
PhilippImhof Mar 26, 2023
7a3830a
first steps for evaluator
PhilippImhof Mar 29, 2023
547d732
fix test
PhilippImhof Mar 29, 2023
d8ac61d
allow access to char from string
PhilippImhof Apr 13, 2023
7d42861
change syntax for accessing pi
PhilippImhof Apr 13, 2023
7296955
cleanup: array(...) -> [...]
PhilippImhof Apr 13, 2023
985a00d
cleanup: remove unused code in shunting yard
PhilippImhof Apr 14, 2023
10fd612
implement access to array elements & string chars
PhilippImhof Apr 14, 2023
6a4d33a
port some of the functions from existing code
PhilippImhof Apr 14, 2023
dd68232
cleanup, add checks to binary operators
PhilippImhof Apr 15, 2023
dceabb3
improved binary operators, bugfix for logical NOT
PhilippImhof Apr 15, 2023
2b3d86a
add class for variables
PhilippImhof Apr 15, 2023
5f98a72
deal with variables during evaluation (partially)
PhilippImhof Apr 15, 2023
758ee7d
add fqversionnumber() to functions
PhilippImhof Apr 15, 2023
bfb44f9
cosmetic changes
PhilippImhof Apr 15, 2023
2de29c3
evaluation of for loops, some cleanup
PhilippImhof Apr 16, 2023
f7f9caf
assignment to array elements, cleanup
PhilippImhof Apr 18, 2023
854a759
disallow assigning values to reserved variables
PhilippImhof Apr 22, 2023
fdc0425
helper to wrap values in token with correct type
PhilippImhof Apr 22, 2023
bcdc347
implement various functions
PhilippImhof Apr 22, 2023
1778778
some refactoring
PhilippImhof Apr 30, 2023
efaec38
support random variables
PhilippImhof May 1, 2023
c981bbb
sync row/column of value and variable
PhilippImhof May 1, 2023
0792068
support recursive and non-recursive shuffling
PhilippImhof May 1, 2023
921a622
implement inv() function
PhilippImhof May 2, 2023
27edc57
change sort() order for negative numbers
PhilippImhof May 6, 2023
9218d4c
implement map() function
PhilippImhof May 8, 2023
a7f1efd
resolve variables before sending them to functions
PhilippImhof May 9, 2023
4dadc4d
fact(): throw error if result too big
PhilippImhof May 10, 2023
8cc77d5
rewrite of questiontype.php
PhilippImhof Jul 25, 2023
a2825ab
savepoint WIP
PhilippImhof Nov 15, 2023
bcd73eb
advance grading
PhilippImhof Nov 16, 2023
948178b
progress on grading of answer type algebraic
PhilippImhof Nov 20, 2023
7057b96
fix initialisation of question parts
PhilippImhof Nov 21, 2023
b6bba05
update behat for grading criterion
PhilippImhof Nov 21, 2023
4f4c4dd
support for multichoice
PhilippImhof Nov 21, 2023
78df451
fix wrong syntax in test helper (MC questions)
PhilippImhof Nov 23, 2023
da45517
fixing some bugs
PhilippImhof Nov 23, 2023
767424c
fix bug with combined unit field
PhilippImhof Nov 24, 2023
d36f9a5
update behat test: new error message
PhilippImhof Nov 24, 2023
7336fb6
fix bug in is_complete_response()
PhilippImhof Nov 24, 2023
bcbc2d1
improve "correct answer" feedback for mc answers
PhilippImhof Nov 24, 2023
80344a1
define missing properties
PhilippImhof Nov 24, 2023
bb92342
Merge branch 'parser-WIP-backup' into parser
PhilippImhof Nov 25, 2023
55104c6
migration of some unit tests + bug fixes
PhilippImhof Nov 26, 2023
9bb4f5e
some cleanup
PhilippImhof Nov 26, 2023
67282ee
syntax validation for student answer types
PhilippImhof Nov 27, 2023
d686dde
add ln() function
PhilippImhof Nov 27, 2023
57b7c2b
improvements in evaluator class
PhilippImhof Nov 27, 2023
0b88004
further migration of unit tests
PhilippImhof Nov 27, 2023
e187c3d
improvements and bugfixes in evaluator/parser
PhilippImhof Nov 28, 2023
ae4f98c
finished (+/-) migration of variables_test.php
PhilippImhof Nov 28, 2023
0d3b11a
improve ternary operator
PhilippImhof Nov 30, 2023
0334a02
bugfix: while in ternary operator syntax check
PhilippImhof Nov 30, 2023
0b38f10
minor cleanup
PhilippImhof Nov 30, 2023
2c498de
Merge branch 'master' into parser
PhilippImhof Dec 1, 2023
95c71d5
functions: improvements, bugfixes and tests
PhilippImhof Dec 3, 2023
14c4093
Merge branch 'master' into parser
PhilippImhof Dec 3, 2023
fa2907d
update test for criterion (new error message)
PhilippImhof Dec 3, 2023
ed20185
finish (+/-) questiontype.php + tests, bugfixes
PhilippImhof Dec 3, 2023
1ee454f
Merge branch 'master' into parser
PhilippImhof Dec 5, 2023
2dcc59e
some bug fixes, some more testing
PhilippImhof Dec 10, 2023
a29b7c7
bugfix in test helper
PhilippImhof Dec 10, 2023
ecec5db
test compatibility with PHP 7.4
PhilippImhof Dec 10, 2023
2aa8984
Merge branch 'master' into parser
PhilippImhof Dec 18, 2023
188aaee
remove legacy functions
PhilippImhof Dec 20, 2023
17569c6
check for algebraic var in answer
PhilippImhof Dec 20, 2023
51eb995
refactor instantiation to use new code
PhilippImhof Dec 20, 2023
f3f7cb2
cleanup, codestyle
PhilippImhof Dec 20, 2023
8a25a24
bugfixes and cleanup
PhilippImhof Dec 20, 2023
426fae6
update and further migration of tests
PhilippImhof Dec 20, 2023
fc2688b
adapt behat test to new error messages
PhilippImhof Dec 20, 2023
0e9f7cc
tweak for compatibility with PHP 7.4
PhilippImhof Dec 20, 2023
ade9d69
improvements, tests for token and variable
PhilippImhof Dec 21, 2023
f4c2bf5
remove legacy variables.php
PhilippImhof Dec 21, 2023
7b9027f
improved syntax checking + tests
PhilippImhof Dec 22, 2023
bbb45d5
improvementst in instantiation check + tests
PhilippImhof Dec 22, 2023
20800c2
update clear_from_response_if_wrong
PhilippImhof Dec 24, 2023
5c9b136
refactoring and bug fixes in renderer
PhilippImhof Dec 24, 2023
a72c3d8
allow removing special vars from evaluator
PhilippImhof Dec 24, 2023
4c818c0
docs change
PhilippImhof Dec 24, 2023
aa3c5a2
add tests for renderer
PhilippImhof Dec 24, 2023
4794793
remove behat for unique answer, now unit test
PhilippImhof Dec 24, 2023
b9f143d
remove unused function has_multichoice_coordinate
PhilippImhof Dec 24, 2023
9a74048
more tests for renderer and question definition
PhilippImhof Dec 25, 2023
a99c125
remove unused function
PhilippImhof Dec 25, 2023
533e4f3
bugfixes, cleanup, additional tests
PhilippImhof Dec 29, 2023
3b7e777
cleanup and docs
PhilippImhof Dec 30, 2023
fffe457
cleanup of input stream and lexer, tests
PhilippImhof Dec 31, 2023
72b406a
try pcov instead of xdebug
PhilippImhof Jan 3, 2024
5196a5b
various bug fixes and improved testing
PhilippImhof Jan 3, 2024
9746441
only use pcov for Moodle >= 3.11
PhilippImhof Jan 3, 2024
93b08b3
cleanup, additional test cases
PhilippImhof Jan 3, 2024
c320da4
bug fixes (functions), additional tests, clean up
PhilippImhof Jan 8, 2024
ae81d80
bugfix: instantiation fails with < in definition
PhilippImhof Jan 10, 2024
3146b37
cosmetic change to lib.php
PhilippImhof Jan 10, 2024
b6e1957
disallow variables starting with underscore
PhilippImhof Jan 10, 2024
3f7d6be
improve test for rshuffle()
PhilippImhof Jan 10, 2024
4717720
fix grade_parts_that_can_be_graded and its tests
PhilippImhof Feb 9, 2024
db4643a
Merge branch 'master' into parser
PhilippImhof Feb 9, 2024
9f0e012
update moodle-plugin-ci in GHA
PhilippImhof Feb 9, 2024
7789675
Merge branch 'master' into parser
PhilippImhof Feb 10, 2024
12f6dec
Merge branch 'master' into parser
PhilippImhof Feb 10, 2024
9d8ee47
check_file_access() and tests thereof
PhilippImhof Feb 11, 2024
68030d3
Merge branch 'master' into parser
PhilippImhof Feb 12, 2024
e5ea913
improvements to check_file_access
PhilippImhof Feb 12, 2024
9d06d0c
bugfix in compute_final_grade() + add test
PhilippImhof Feb 15, 2024
a946146
deal with empty input in adaptive mode
PhilippImhof Feb 16, 2024
3dbab1f
various bugfixes and cleanup, tests for question.php
PhilippImhof Feb 18, 2024
3e25ed6
cleanup, improved testing for questiontype
PhilippImhof Feb 24, 2024
6afd8be
remove obsolete behat tests for import/export
PhilippImhof Feb 24, 2024
1eacc1e
small fixes to tests
PhilippImhof Feb 24, 2024
9a9ec5e
questiontype.php improvements and cleanup
PhilippImhof Mar 2, 2024
59840b7
cleanup, improved validation, added tests
PhilippImhof Mar 3, 2024
b9479c7
move strings to language file to allow localisation
PhilippImhof Mar 10, 2024
06ce576
bugfix in token::wrap()
PhilippImhof Mar 10, 2024
399d540
Merge branch 'master' into parser
PhilippImhof Apr 20, 2024
ede3e99
http -> https
PhilippImhof Sep 9, 2024
7a3ff07
mobile behat: stick to old app version for now
PhilippImhof Sep 9, 2024
6e49a7c
Merge branch 'master' into parser
PhilippImhof Sep 10, 2024
94162af
further tests and cleanup
PhilippImhof Dec 7, 2024
0ff528e
Merge branch 'master' into parser
PhilippImhof Dec 7, 2024
896493b
update tests
PhilippImhof Dec 7, 2024
fe138dc
fix test with implicit multiplication
PhilippImhof Dec 7, 2024
65ffabb
remove separate mobile behat workflow
PhilippImhof Dec 7, 2024
b3c0d9c
fix unit test for save_question
PhilippImhof Dec 8, 2024
2c17dde
fix typo
PhilippImhof Dec 8, 2024
fc87c64
some cleanup in tests
PhilippImhof Dec 9, 2024
7bf6cfe
more cleanup in tests
PhilippImhof Dec 11, 2024
bd1dcb9
make sure tests can be run invidiually
PhilippImhof Dec 11, 2024
027100a
more tests, more cleanup
PhilippImhof Dec 12, 2024
0c0d68a
test for variable/exponential precedence
PhilippImhof Dec 12, 2024
35d8a77
make test less prone to random failure
PhilippImhof Dec 12, 2024
867ad1f
rename syntax checker function
PhilippImhof Dec 12, 2024
aa41118
add test for general/combined part feedback
PhilippImhof Dec 14, 2024
f580752
add test for parens
PhilippImhof Dec 15, 2024
ee115c6
port test for number conversion
PhilippImhof Dec 15, 2024
c69aa89
bugfix, more tests, cleanup
PhilippImhof Dec 15, 2024
b64a8db
add invocation test for php builtin funcs
PhilippImhof Dec 15, 2024
dd7c098
move autoloaded classes to official path
PhilippImhof Dec 16, 2024
38e1dc8
add phpdoc, externalise strings
PhilippImhof Dec 16, 2024
2fbd8f8
clean up language strings
PhilippImhof Dec 16, 2024
6b54b00
cleanup
PhilippImhof Dec 16, 2024
7c69580
type hints, further testing, some cleanup
PhilippImhof Dec 19, 2024
97c65db
finish porting old tests, cleanup
PhilippImhof Dec 22, 2024
07e3f5a
correctly handle prefix in model and student answers
PhilippImhof Dec 23, 2024
15bdf00
explicitly disallow prefix in algebraic model answer
PhilippImhof Dec 23, 2024
86e4c95
improve language
PhilippImhof Dec 23, 2024
68f5634
improve error reporting for nan/inf
PhilippImhof Dec 24, 2024
422fe60
avoid precision problem in ncr function
PhilippImhof Dec 24, 2024
15f457d
Merge branch 'master' into parser
PhilippImhof Dec 24, 2024
a7de593
add some more tests
PhilippImhof Jan 27, 2025
2c3bd1a
make data providers static
PhilippImhof Jan 27, 2025
2e79f03
more changes for PHP Unit 11.5
PhilippImhof Jan 27, 2025
a6d4295
more 11.5 compatibility stuff
PhilippImhof Jan 27, 2025
84a3f2e
disallow wrapping array into STRING token
PhilippImhof Jan 27, 2025
2ce01e3
work around bug in core
PhilippImhof Jan 27, 2025
bb297df
Remove previously ignored phpunit.xml
PhilippImhof Jan 27, 2025
d0022a2
fix forgotten line
PhilippImhof Jan 27, 2025
51e61da
add unit test for calculate_algebraic_expression
PhilippImhof Jan 28, 2025
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
4 changes: 3 additions & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ jobs:
with:
php-version: ${{ matrix.php }}
ini-values: max_input_vars=5000
coverage: xdebug
# For whatever reason, externallib tests fail in Moodle 3.9 with PCOV (even with
# clobber), so forcing xdebug for that version.
coverage: ${{ matrix.moodle-branch == 'MOODLE_39_STABLE' && 'xdebug' || 'pcov' }}

- name: Initialise moodle-plugin-ci
run: |
Expand Down
4 changes: 4 additions & 0 deletions answer_unit.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@
* @param string $default_rules default rules
*/
public function assign_default_rules($default_id, $default_rules) {
if ($this->default_id == $default_id) {

Check warning on line 79 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $default_id is not named in camelCase.

Check warning on line 79 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $default_id is not named in camelCase.
return; // Do nothing if the rules are unchanged.
}
$this->default_id = $default_id;
$this->default_rules = $default_rules;

Check warning on line 83 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $default_rules is not named in camelCase.

Check warning on line 83 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $default_rules is not named in camelCase.
$this->default_mapping = null;
$this->mapping = null;
$this->additional_rules = ''; // Always remove the additional rule.
Expand All @@ -93,7 +93,7 @@
* @param string $additional_rules the additional rule string
*/
public function assign_additional_rules($additional_rules) {
$this->additional_rules = $additional_rules;

Check warning on line 96 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $additional_rules is not named in camelCase.

Check warning on line 96 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $additional_rules is not named in camelCase.
$this->mapping = null;
}

Expand All @@ -103,8 +103,8 @@
*/
public function reparse_all_rules() {
if ($this->default_mapping === null) {
$tmp_mapping = array();

Check warning on line 106 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $tmp_mapping is not named in camelCase.

Check warning on line 106 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $tmp_mapping is not named in camelCase.
$tmp_counter = 0;

Check warning on line 107 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $tmp_counter is not named in camelCase.

Check warning on line 107 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $tmp_counter is not named in camelCase.
$this->parse_rules($tmp_mapping, $tmp_counter, $this->default_rules);
$this->default_mapping = $tmp_mapping;
$this->default_last_id = $tmp_counter;
Expand All @@ -126,8 +126,8 @@

// Return a dimension classes list for current mapping. Each class is an array of $unit to $scale mapping.
public function get_dimension_list() {
$dimension_list = array();

Check warning on line 129 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $dimension_list is not named in camelCase.

Check warning on line 129 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $dimension_list is not named in camelCase.
foreach ($this->mapping as $unit => $class_scale) {

Check warning on line 130 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $class_scale is not named in camelCase.

Check warning on line 130 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $class_scale is not named in camelCase.
list($class, $scale) = $class_scale;
$dimension_list[$class][$unit] = $scale;
}
Expand Down Expand Up @@ -162,7 +162,7 @@
return (object) array('convertible' => false, 'cfactor' => 1, 'target' => null);
}
$this->reparse_all_rules(); // Reparse if the any rules have been updated.
$targets_list = $this->parse_targets($targets);

Check warning on line 165 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $targets_list is not named in camelCase.

Check warning on line 165 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $targets_list is not named in camelCase.
$res = $this->check_convertibility_parsed($ip, $targets_list);
if ($res === null) {
return (object) array('convertible' => false, 'cfactor' => 1, 'target' => null);
Expand All @@ -180,7 +180,7 @@
* @return an array of parsed unit, parsed by the parse_unit().
*/
public function parse_targets($targets) {
$targets_list = array();

Check warning on line 183 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $targets_list is not named in camelCase.

Check warning on line 183 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $targets_list is not named in camelCase.
if (strlen(trim($targets)) == 0) {
return $targets_list;
}
Expand All @@ -189,7 +189,7 @@
if (strlen(trim($unit) ) == 0) {
throw new Exception('""');
}
$parsed_unit = $this->parse_unit($unit);

Check warning on line 192 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.0)

The variable $parsed_unit is not named in camelCase.

Check warning on line 192 in answer_unit.php

View workflow job for this annotation

GitHub Actions / PHP mess detector (8.1)

The variable $parsed_unit is not named in camelCase.
if ($parsed_unit === null) {
throw new Exception('"'.$unit.'"');
}
Expand Down Expand Up @@ -247,6 +247,10 @@
* @return array(conversion factor, unit exponent) if it can be converted, otherwise null.
*/
private function attempt_conversion($test_unit_name, $base_unit_array) {
// If the unit does not exist, we leave early.
if (!array_key_exists($test_unit_name, $this->mapping)) {
return null;
}
$oclass = $this->mapping[$test_unit_name];
if (!isset($oclass)) {
return null; // It does not exist in the mapping implies it is not convertible.
Expand Down
464 changes: 261 additions & 203 deletions classes/external/instantiation.php

Large diffs are not rendered by default.

355 changes: 355 additions & 0 deletions classes/local/answer_parser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
<?php
// This file is part of Moodle - https://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <https://www.gnu.org/licenses/>.

namespace qtype_formulas\local;

use qtype_formulas;

/**
* Parser for answer expressions for qtype_formulas
*
* @package qtype_formulas
* @copyright 2022 Philipp Imhof
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

/* FIXME-TODO: make validation function with units --> add unit tests, should be working, because unit/number are split before check */

class answer_parser extends parser {
/**
* Create a parser for student answers. This class does additional filtering (e. g. block
* forbidden operators) and syntax checking according to the answer type. It also translates
* the ^ symbol to the ** operator.
*
* @param string|array $tokenlist list of tokens as returned from the lexer or input string
* @param array $knownvariables
* @param bool $caretmeanspower whether ^ should be interpreted as exponentiation operator
* @param bool $formodelanswer whether we are parsing a teacher's model answer (thus allowing \ prefix)
*/
public function __construct($tokenlist, array $knownvariables = [], bool $caretmeanspower = true, bool $formodelanswer = false) {
// If the input is given as a string, run it through the lexer first.
if (is_string($tokenlist)) {
$lexer = new lexer($tokenlist);
$tokenlist = $lexer->get_tokens();
}

$precededbyprefix = false;
foreach ($tokenlist as $token) {
// In the context of student answers, the caret (^) *always* means exponentiation (**) instead
// of XOR. In model answers entered by the teacher, the caret *only* means exponentiation
// for algebraic formulas, but not for the other answer types.
if ($caretmeanspower) {
if ($token->type === token::OPERATOR && $token->value === '^') {
$token->value = '**';
}
}

// Students are not allowed to use function names as variables, e.g. they cannot use a
// variable 'sin'. This is important, because teachers have that option and the regular
// parser will automatically consider 'sin' in the expression '3*sin x' as a variable,
// due to the missing parens. We want to avoid that, because it would conceal a syntax
// error. We make one exception: if the identifier has been labelled as a known variable,
// the token will be considered as a variable. This allows the teacher to use e.g. 'exp'
// as a unit name, if they want to.
if ($token->type === token::IDENTIFIER) {
if (in_array($token->value, $knownvariables) && !$precededbyprefix) {
$token->type = token::VARIABLE;
} else if (array_key_exists($token->value, functions::FUNCTIONS + evaluator::PHPFUNCTIONS)) {
$token->type = token::FUNCTION;
}
}

if (!$formodelanswer && $token->type === token::PREFIX) {
$this->die(get_string('error_prefix', 'qtype_formulas'), $token);
}

$precededbyprefix = ($token->type === token::PREFIX);
}

// Once this is done, we can parse the expression normally.
parent::__construct($tokenlist, $knownvariables);
}

/**
* Perform the right check according to a given answer type.
*
* @param int $type the answer type, a constant from the qtype_formulas class
* @return bool
*/
public function is_acceptable_for_answertype(int $type): bool {
if ($type === qtype_formulas::ANSWER_TYPE_NUMBER) {
return $this->is_acceptable_number();
}

if ($type === qtype_formulas::ANSWER_TYPE_NUMERIC) {
return $this->is_acceptable_numeric();
}

if ($type === qtype_formulas::ANSWER_TYPE_NUMERICAL_FORMULA) {
return $this->is_acceptable_numerical_formula();
}

if ($type === qtype_formulas::ANSWER_TYPE_ALGEBRAIC) {
return $this->is_acceptable_algebraic_formula();
}
}

/**
* Check whether the given answer contains only valid tokens for the answer type NUMBER, i. e.
* - just a number, possibly with a decimal point
* - no operators, except unary + or - at start
* - possibly followed by e/E (maybe followed by + or -) plus an integer
*
* @return bool
*/
private function is_acceptable_number(): bool {
// The statement list must contain exactly one expression object.
if (count($this->statements) !== 1) {
return false;
}

$answertokens = $this->statements[0]->body;

// The first element of the answer expression must be a token of type NUMBER or
// CONSTANT, e.g. pi or π; we currently do not have other named constants.
// Note: if the user has entered -5, this has now become [5, _].
if (!in_array($answertokens[0]->type, [token::NUMBER, token::CONSTANT])) {
return false;
}
array_shift($answertokens);

// If there are no tokens left, everything is fine.
if (empty($answertokens)) {
return true;
}

// We accept one more token: an unary minus sign (OPERATOR '_'). An unary plus sign
// would be possible, but it would already have been dropped. For backwards compatibility,
// we do not accept multiple unary minus signs.
if (count($answertokens) > 1) {
return false;
}
$token = $answertokens[0];
return ($token->type === token::OPERATOR && $token->value === '_');
}

/**
* Check whether the given answer contains only valid tokens for the answer type NUMERIC, i. e.
* - numbers
* - operators +, -, *, ** or ^
* - round parens ( and )
* - pi or pi() or π
* - no functions
* - no variables
*
* @return bool
*/
private function is_acceptable_numeric(): bool {
// If it's a valid number expression, we have nothing to do.
if ($this->is_acceptable_number()) {
return true;
}

// The statement list must contain exactly one expression object.
if (count($this->statements) !== 1) {
return false;
}

$answertokens = $this->statements[0]->body;

// Iterate over all tokens.
foreach ($answertokens as $token) {
// The PREFIX operator must not be used in numeric answers.
if ($token->type === token::PREFIX) {
return false;
}

// If we find a FUNCTION or VARIABLE token, we can stop, because those are not
// allowed in the numeric answer type.
if ($token->type === token::FUNCTION || $token->type === token::VARIABLE) {
return false;
}
// If it is an OPERATOR, it has to be +, -, *, /, ^, ** or the unary minus _.
$allowedoperators = ['+', '-', '*', '/', '^', '**', '_'];
if ($token->type === token::OPERATOR && !in_array($token->value, $allowedoperators)) {
return false;
}
$isparen = ($token->type & token::ANY_PAREN);
// Only round parentheses are allowed.
if ($isparen && !in_array($token->value, ['(', ')'])) {
return false;
}
}

// Still here? Then it's all good.
return true;
}

/**
* Check whether the given answer contains only valid tokens for the answer type NUMERICAL_FORMULA, i. e.
* - numerical expression
* - plus functions: sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh
* - plus functions: sqrt, exp, log, log10, ln
* - plus functions: abs, ceil, floor
* - plus functions: fact, ncr, npr
* - no variables
*
* @return bool
*/
private function is_acceptable_numerical_formula(): bool {
if ($this->is_acceptable_number() || $this->is_acceptable_numeric()) {
return true;
}

// Checking whether the expression is valid as an algebraic formula, but with variables
// being disallowed. This also makes sure that there is one single statement.
if (!$this->is_acceptable_algebraic_formula(true)) {
return false;
}

// Still here? Then it's all good.
return true;
}

/**
* Check whether the given answer contains only valid tokens for the answer type ALGEBRAIC, i. e.
* - everything allowed for numerical formulas
* - all functions and operators except assignment =
* - variables (TODO: maybe only allow registered variables, would avoid student mistake "ab" instead of "a b" or "a*b")
*
* @param bool $fornumericalformula whether we disallow the usage of variables and the PREFIX operator
* @return bool
*/
private function is_acceptable_algebraic_formula(bool $fornumericalformula = false): bool {
if ($this->is_acceptable_number() || $this->is_acceptable_numeric()) {
return true;
}

// The statement list must contain exactly one expression object.
if (count($this->statements) !== 1) {
return false;
}

$answertokens = $this->statements[0]->body;

// Iterate over all tokens. If we find a FUNCTION token, we check whether it is in the white list.
$functionwhitelist = [
'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh',
'sqrt', 'exp', 'log', 'log10', 'ln', 'abs', 'ceil', 'floor', 'fact', 'ncr', 'npr'
];
$operatorwhitelist = ['+', '_', '-', '/', '*', '**', '^', '%'];
foreach ($answertokens as $token) {
// Cut short, if it is a NUMBER token.
if ($token->type === token::NUMBER) {
continue;
}
// The PREFIX operator must not be used in numerical formulas.
if ($fornumericalformula && $token->type === token::PREFIX) {
return false;
}
if ($token->type === token::VARIABLE) {
if ($fornumericalformula) {
return false;
}
/* TODO: maybe we should reject unknown variables, because that avoids mistakes
like student writing a(x+y) = ax + ay instead of a*x or a x.
if (!$this->is_known_variable($token)) {
return false;
}*/
}
if ($token->type === token::FUNCTION && !in_array($token->value, $functionwhitelist)) {
return false;
}
if ($token->type === token::OPERATOR && !in_array($token->value, $operatorwhitelist)) {
return false;
}
}

// Still here? Then let's check the syntax.
return $this->is_valid_syntax();
}

/**
* This function determines the index where the numeric part ends and the unit part begins, e.g.
* for the answer "1.5e3 m^2", that index would be 6.
* We know that the student cannot (legally) use variables in their answers of type number, numeric
* or numerical formula. Also, we know that units will be classified as variables. Thus, we can
* walk through the list of tokens until we reach the first "variable" (actually a unit) and then
* we know where the unit starts.
*
* @return int
*/
public function find_start_of_units(): int {
foreach ($this->tokenlist as $token) {
if ($token->type === token::VARIABLE) {
return $token->column - 1;
}
}
// Still here? That means there is no unit, so it starts very, very far away...
return PHP_INT_MAX;
}

/**
* Iterate over all tokens and check whether the expression is *syntactically* valid.
* Note that this does not necessarily mean that the expression can be evaluated:
* - sqrt(-3) is syntactically valid, but it cannot be calculated
* - asin(x*y) is syntactically valid, but cannot be evaluated if abs(x*y) > 1
* - a/(b-b) is syntactically valid, but it cannot be evaluated
* - a-*b is syntactically invalid, because the operators cannot be chained that way
*
* @return bool
*/
private function is_valid_syntax(): bool {
$tokens = $this->statements[0]->body;

// Iterate over all tokens. Push literals (strings, number) and variables on the stack.
// Operators and functions will consume them, but not evaluate anything. In the end, there
// should be only one single element on the stack.
$stack = [];
foreach ($tokens as $token) {
if (in_array($token->type, [token::STRING, token::NUMBER, token::VARIABLE])) {
$stack[] = $token->value;
}
if ($token->type === token::OPERATOR) {
// Check whether the operator is unary. We also include operators that are not
// actually allowed in a student's answer. Unary operators would operate on
// the last token on the stack, but as we do not evaluate anything, we just
// drop them.
if (in_array($token->value, ['_', '!', '~'])) {
continue;
}
// All other operators are binary, because the student cannot use the ternary
// operator in their answer. Also, they are not allowed other than round parens,
// so there can be no %%rangebuild or similar pseudo-operators in the queue.
// A binary operator would pop the two top elements, do its magic and then push
// the result on the stack. As we do not evaluate anything, we simply drop the top
// element.
array_pop($stack);
}
// For functions, the top element on the stack (always a number literal) will indicate
// the number of arguments to consume. So we pop that element plus one less than what
// it indicates, meaning we actually drop exactly the number of elements indicated
// by that element.
if ($token->type === token::FUNCTION) {
$n = end($stack);
$stack = array_slice($stack, 0, -$n);
}
}

return (count($stack) === 1);
}

}
Loading
Loading