% ---------------------------------------------------------------------------- % the XSIM package - exercises module % % eXercise Sheets IMproved % % ---------------------------------------------------------------------------- % Clemens Niederberger % Web: https://github.com/cgnieder/xsim % E-Mail: contact@mychemistry.eu % ---------------------------------------------------------------------------- % Copyright 2017--2020 Clemens Niederberger % % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3 % of this license or (at your option) any later version. % The latest version of this license is in % http://www.latex-project.org/lppl.txt % and version 1.3 or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is Clemens Niederberger. % ---------------------------------------------------------------------------- % If you have any ideas, questions, suggestions or bugs to report, please % feel free to contact me. % ---------------------------------------------------------------------------- \XSIMmodule{exercises}{main exercises definitions} \xsim_load_modules:n { base, properties, environments, templates, solutions, tags, goals, collections } \seq_new:N \l__xsim_exercise_types_seq \seq_new:N \l__xsim_parameters_seq \seq_new:N \l__xsim_fixed_parameters_seq \seq_new:N \l__xsim_mandatory_parameters_seq \int_gzero_new:N \g_xsim_id_int \int_gzero_new:N \g_xsim_max_id_int \tl_new:N \l_xsim_current_id_tl \tl_new:N \g_xsim_exercise_id_tl \tl_new:N \g_xsim_exercise_type_tl \tl_new:N \ExerciseID \tl_new:N \ExerciseType \bool_new:N \l____xsim_print_bool % ---------------------------------------------------------------------------- \msg_new:nnn {xsim} {parameter-fixed} { You~ tried~ to~ set~ parameter~ `#2'~ for~ type~ `#1'~ \msg_line_context: . ~ However,~ parameter~ `#2'~ is~ a~ fixed~ parameter~ that~ already~ has~ been~ set~ for~ type~ `#1'.~ Its~ value~ can't~ be~ changed! } \msg_new:nnn {xsim} {type-exists} { You~ try~ to~ define~ exercise~ type~ `#1'~ \msg_line_context: .~ However,~ the~ exercise~ type~ `#1'~ already~ exists. } \msg_new:nnn {xsim} {mandatory-parameter} { You~ forget~ to~ set~ the~ mandatory~ parameter~ `#1'~ when~ defining~ exercise~ type~ `#2'. } \msg_new:nnn {xsim} {unknown-parameter} { You~ tried~ to~ retrieve~ the~ parameter~ `#1'~ \msg_line_context: . \\ This~ parameter~ does~ not~ exist.~ Check~ for~ a~ typo~ or~ \\ define~ the~ parameter~ yourself. } % ---------------------------------------------------------------------------- % #1: type % #2: parameter % #3: value \cs_new_protected:Npn \__xsim_set_parameter_type:nnn #1#2#3 { \xsim_attribute_set:nnn {#1} {#2} {#3} } % #1: type % #2: parameter \cs_new:Npn \xsim_get_parameter:nn #1#2 { \xsim_attribute_get:nn {#1} {#2} } \cs_generate_variant:Nn \xsim_get_parameter:nn {o} % #1: type % #2: parameter \prg_new_conditional:Npnn \xsim_if_parameter_set:nn #1#2 {p,T,F,TF} { \xsim_attribute_if_set:nnTF {#1} {#2} { \prg_return_true: } { \prg_return_false: } } % ---------------------------------------------------------------------------- \xsim_new_list:n {types} % #1: true|false (fixed parameter) % #2: true|false (mandatory parameter) % #3: parameter name \cs_new_protected:Npn \xsim_declare_parameter:nnn #1#2#3 { \xsim_verbose:n { Declaring~ parameter~ `#3'. } \xsim_if_parameter_exist:nF {#3} { \seq_put_right:Nn \l__xsim_parameters_seq {#3} \bool_if:cT {c_#1_bool} { \seq_put_right:Nn \l__xsim_fixed_parameters_seq {#3} } \bool_if:cT {c_#2_bool} { \seq_put_right:Nn \l__xsim_mandatory_parameters_seq {#3} } } } \cs_new_protected:Npn \xsim_remove_parameter:n #1 { \xsim_verbose:n { Removing~ parameter~ `#1'. } \xsim_if_parameter_exist:nT {#1} { \seq_remove_all:Nn \l__xsim_parameters_seq {#1} } } \prg_new_conditional:Npnn \xsim_if_parameter_exist:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_parameters_seq {#1} { \prg_return_true: } { \prg_return_false: } } \cs_generate_variant:Nn \xsim_if_parameter_exist:nT {x} \prg_new_conditional:Npnn \xsim_if_parameter_fixed:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_fixed_parameters_seq {#1} { \prg_return_true: } { \prg_return_false: } } % #1: type % #2: csv list of parameters \cs_new_protected:Npn \xsim_set_parameters:nn #1#2 { \clist_map_inline:nn {#2} { \seq_set_split:Nnx \l__xsim_tmpa_seq {=} { \tl_trim_spaces:n {##1} } \xsim_set_parameter:nxx {#1} { \seq_item:Nn \l__xsim_tmpa_seq {1} } { \seq_item:Nn \l__xsim_tmpa_seq {2} } } } \cs_generate_variant:Nn \xsim_set_parameters:nn {nx} % #1: type \cs_new_protected:Npn \xsim_check_mandatory_parameters:n #1 { \seq_map_inline:Nn \l__xsim_mandatory_parameters_seq { \xsim_if_parameter_set:nnF {#1} {##1} { \msg_error:nnnn {xsim} {mandatory-parameter} {##1} {#1} } } } % #1: type \prg_new_conditional:Npnn \xsim_if_type:n #1 {p,T,F,TF} { \seq_if_in:NnTF \l__xsim_exercise_types_seq {#1} { \prg_return_true: } { \prg_return_false: } } % #1: type % #2: parameter % #3: value \cs_new_protected:Npn \xsim_set_parameter:nnn #1#2#3 { \xsim_if_parameter_exist:nT {#2} { \xsim_if_parameter_fixed:nTF {#1} { \xsim_if_parameter_set:nnTF {#1} {#2} { \msg_warning:nnnn {xsim} {parameter-fixed} {#1} {#2} \xsim_verbose:n { Not~ setting~ already~ set~ and~ fixed~ parameter~ `#2'~ for~ type~ `#1'. } } { \xsim_verbose:n { Setting~ parameter~ `#2'~ for~ type~ `#1'~ to~ `#3'. } \__xsim_set_parameter_type:nnn {#1} {#2} {#3} } } { \xsim_verbose:n { Setting~ parameter~ `#2'~ for~ type~ `#1'~ to~ `#3'. } \__xsim_set_parameter_type:nnn {#1} {#2} {#3} } } } \cs_generate_variant:Nn \xsim_set_parameter:nnn {nnx,nxx} \cs_new:Npn \__xsim_get_parameter_tmp:n #1 {} \cs_generate_variant:Nn \__xsim_get_parameter_tmp:n {V} % #1: type % #2: parameter \cs_new_protected:Npn \xsim_get_parameter_if_set:nnTF #1#2#3#4 { \group_begin: \xsim_if_parameter_exist:nTF {#2} { \xsim_if_parameter_set:nnTF {#1} {#2} { \tl_set:Nx \ParameterValue { \xsim_get_parameter:nn {#1} {#2} } \cs_set:Npn \__xsim_get_parameter_tmp:n ##1 {#3} \__xsim_get_parameter_tmp:V \ParameterValue } {#4} } { \msg_error:nnn {xsim} {unknown-parameter} {#2} } \group_end: } \cs_new_protected:Npn \xsim_get_parameter_if_set:nnT #1#2#3 { \xsim_get_parameter_if_set:nnTF {#1} {#2} {#3} {} } \cs_new_protected:Npn \xsim_get_parameter_if_set:nnF #1#2#3 { \xsim_get_parameter_if_set:nnTF {#1} {#2} {} {#3} } \cs_generate_variant:Nn \xsim_get_parameter_if_set:nnTF {o} % ---------------------------------------------------------------------------- % inside the argument `#1' refers to the type \cs_new_protected:Npn \xsim_foreach_exercise_type:n #1 { \xsim_foreach_new_list_entry:nn {types} {#1} } \cs_new_protected:Npn \xsim_foreach_exercise_id:n #1 { \int_step_inline:nnnn {1} {1} { \g_xsim_max_id_int } {#1} } \cs_new:Npn \__xsim_loop_item:nnnnnn #1#2#3#4#5#6 {} \cs_generate_variant:Nn \__xsim_loop_item:nnnnnn {VVxxxx} % #1: type % #2: id % #3: boolean property \cs_new:Npn \__xsim_deliver_exercise_details:nnn #1#2#3 { \xsim_if_in_list:nnT {#3} {#1-#2=={true}} { \tl_set_rescan:Nnx \l__xsim_tmpa_tl {} {#1} \tl_set_rescan:Nnx \l__xsim_tmpb_tl {} {#2} \__xsim_loop_item:VVxxxx \l__xsim_tmpa_tl % #1 \l__xsim_tmpb_tl % #2 { \xsim_get_property:nnn {#1} {#2} {counter} } % #3 { \xsim_get_property:nnn {#1} {#2} {subtitle} } % #4 { \xsim_get_property:nnn {#1} {#2} {points} } % #5 { \xsim_get_property:nnn {#1} {#2} {bonus-points} } % #6 } } % #1: boolean property % #2: loop code \xsim_define_loop_macro:Nnn \xsim_foreach_exercise_type_id:nn {nnnnnn} { \xsim_foreach_exercise_type:n { \xsim_foreach_exercise_id:n { \__xsim_deliver_exercise_details:nnn {##1} {####1} {#1} } } } \xsim_define_loop_macro:Nnn \xsim_foreach_exercise_id_type:nn {nnnnnn} { \xsim_foreach_exercise_id:n { \xsim_foreach_exercise_type:n { \__xsim_deliver_exercise_details:nnn {####1} {##1} {#1} } } } \cs_new_protected:Npn \xsim_define_exercise_type_variables:n #1 { \seq_put_right:Nn \l__xsim_exercise_types_seq {#1} \bool_new:c {l__xsim_#1_solution_print_bool} \bool_new:c {l__xsim_#1_exercise_print_bool} \bool_new:c {l__xsim_#1_exercise_use_bool} } \cs_new_protected:Npn \xsim_define_counters:n #1 { \xsim_verbose:n { Defining~ counters~ for~ new~ exercise~ type~ `#1' } \cs_if_exist:cF { c@ \xsim_get_parameter:nn {#1} {counter} } { \newcounter { \xsim_get_parameter:nn {#1} {counter} } } \cs_if_exist:cF { c@ \xsim_get_parameter:nn {#1} {solution-counter} } { \newcounter { \xsim_get_parameter:nn {#1} {solution-counter} } } \cs_if_exist:cF { c@ number of \xsim_get_parameter:nn {#1} {exercise-env} s } { \newcounter { number of \xsim_get_parameter:nn {#1} {exercise-env} s } } } % #1: type % #2: id \cs_new_protected:Npn \xsim_step_exercise_counter:nn #1#2 { \refstepcounter { \xsim_get_parameter:nn {#1} {counter} } \xsim_set_properties:nnx {#1} {#2} { counter = \use:c { the \xsim_get_parameter:nn {#1} {counter} } , counter-value = \arabic { \xsim_get_parameter:nn {#1} {counter} } } } \cs_generate_variant:Nn \xsim_step_exercise_counter:nn {nV} % #1: type \cs_new_protected:Npn \xsim_declare_exercise_type:nn #1#2 { \xsim_if_type:nTF {#1} { \msg_error:nnn {xsim} {type-exists} {#1} } { \xsim_verbose:n { Declaring~ new~ exercise~ type~ `#1'. } \xsim_define_exercise_type_variables:n {#1} \xsim_add_to_list:nn {types} {#1} \xsim_set_parameters:nn {#1} {#2} \xsim_set_parameter:nnx {#1} {number} { number of \xsim_get_parameter:nn {#1} {exercise-env} s } \xsim_check_mandatory_parameters:n {#1} \xsim_if_parameter_set:nnF {#1} {exercises-name} { \xsim_set_parameters:nn {#1} { exercises-name = \xsim_get_parameter:nn {#1} {exercise-name} s } } \xsim_if_parameter_set:nnF {#1} {solutions-name} { \xsim_set_parameters:nn {#1} { solutions-name = \xsim_get_parameter:nn {#1} {solution-name} s } } \xsim_if_parameter_set:nnF {#1} {counter} { \xsim_set_parameters:nx {#1} { counter = \xsim_get_parameter:nn {#1} {exercise-env} } } \xsim_if_parameter_set:nnF {#1} {solution-counter} { \xsim_set_parameters:nx {#1} { solution-counter = \xsim_get_parameter:nn {#1} {solution-env} } } \xsim_define_counters:n {#1} \xsim_verbose:n { Defining~ options~ for~ new~ exercise~ type~ `#1' } \keys_define:nx {xsim} { \xsim_get_parameter:nn {#1} {solution-env} / print .bool_set:N = \exp_not:c {l__xsim_#1_solution_print_bool} , \xsim_get_parameter:nn {#1} {solution-env} / print .initial:n = false , \xsim_get_parameter:nn {#1} {exercise-env} / print .bool_set:N = \exp_not:c {l__xsim_#1_exercise_print_bool} , \xsim_get_parameter:nn {#1} {exercise-env} / print .initial:n = true , \xsim_get_parameter:nn {#1} {exercise-env} / use .bool_set:N = \exp_not:c {l__xsim_#1_exercise_use_bool} , \xsim_get_parameter:nn {#1} {exercise-env} / use .initial:n = true , \xsim_get_parameter:nn {#1} {exercise-env} / within .code:n = \exp_not:N \@addtoreset { \xsim_get_parameter:nn {#1} {counter} } {####1}, \xsim_get_parameter:nn {#1} {exercise-env} / the-counter .code:n = \cs_set:cpn {the \xsim_get_parameter:nn {#1} {counter} } {####1} , \xsim_get_parameter:nn {#1} {exercise-env} / template .code:n = \xsim_set_parameter:nnn {#1} {exercise-template} {####1} , \xsim_get_parameter:nn {#1} {solution-env} / template .code:n = \xsim_set_parameter:nnn {#1} {solution-template} {####1} , \xsim_get_parameter:nn {#1} {exercise-env} / name .code:n = \xsim_set_parameter:nnn {#1} {exercise-name} {####1} , \xsim_get_parameter:nn {#1} {exercise-env} s / name .code:n = \xsim_set_parameter:nnn {#1} {exercises-name} {####1} , \xsim_get_parameter:nn {#1} {solution-env} / name .code:n = \xsim_set_parameter:nnn {#1} {solution-name} {####1} , \xsim_get_parameter:nn {#1} {solution-env} s / name .code:n = \xsim_set_parameter:nnn {#1} {solutions-name} {####1} , \xsim_get_parameter:nn {#1} {exercise-env} / heading .code:n = \xsim_set_parameter:nnn {#1} {exercise-heading} {####1} , \xsim_get_parameter:nn {#1} {solution-env} / heading .code:n = \xsim_set_parameter:nnn {#1} {solution-heading} {####1} } \xsim_new_environment:nn {#1} {exercise} \xsim_new_environment:nn {#1} {solution} } } % #1: type % #2: id \prg_new_conditional:Npnn \xsim_if_exercise_exist:nn #1#2 {T,F,TF} { \xsim_if_exist:nnnTF {#1} {#2} {exercise} { \prg_return_true: } { \prg_return_false: } } \cs_generate_variant:Nn \xsim_if_exercise_exist:nnT {VV} % #1: type % #2: options \cs_new_protected:Npn \xsim_start_exercise:nn #1#2 { \xsim_if_insert_mode:F { \int_gincr:N \g_xsim_id_int \int_compare:nF { \g_xsim_max_id_int > \g_xsim_id_int } { \int_gset_eq:NN \g_xsim_max_id_int \g_xsim_id_int } \tl_gset:Nx \g_xsim_exercise_id_tl { \int_use:N \g_xsim_id_int } \tl_gset:Nn \g_xsim_exercise_type_tl {#1} % save the goal values from the auxiliary files: \xsim_foreach_goal:n { \xsim_temporary_goal_new:nVn {#1} \g_xsim_exercise_id_tl {##1} \xsim_if_property_set:nVnT {#1} \g_xsim_exercise_id_tl {##1} { \xsim_temporary_goal_set:nVnn {#1} \g_xsim_exercise_id_tl {##1} { \xsim_get_property:nVn {#1} \g_xsim_exercise_id_tl {##1} } } } \xsim_set_properties:nVx {#1} \g_xsim_exercise_id_tl { % preset options so the user doesn't has to: print = true , use = true , % options set by the user: \exp_not:n {#2} , % options which need to have certain values: id = \g_xsim_exercise_id_tl , section-value = \arabic{section} , section = \thesection , page-value = \arabic{page} , sectioning = { \cs_if_exist:NTF \thechapter { \arabic {chapter} } {0} } { \arabic {section} } { \arabic {subsection} } { \arabic {subsubsection} } { \arabic {paragraph} } , page = \thepage , % this cannot yet be set (see below): used = false } % temporary goal now: either 0 or equal the complete property % property: equal to the given value \xsim_foreach_goal:n { \fp_set:Nn \l__xsim_tmpa_fp { \xsim_temporary_goal_get:nVn {#1} \g_xsim_exercise_id_tl {##1} } % set temporary goal to given property: \xsim_if_property_given:nTF {##1} { \xsim_temporary_goal_set:nVnn {#1} \g_xsim_exercise_id_tl {##1} { \xsim_get_property:nVn {#1} \g_xsim_exercise_id_tl {##1} } } { \xsim_temporary_goal_set:nVnn {#1} \g_xsim_exercise_id_tl {##1} {0} } % update to property to complete value: \xsim_if_property_set:nVnT {#1} \g_xsim_exercise_id_tl {##1} { \xsim_set_property:nVnx {#1} \g_xsim_exercise_id_tl {##1} { \fp_to_decimal:N \l__xsim_tmpa_fp } } } \xsim_if_property_exist:nT {chapter} { \xsim_set_properties:nVx {#1} \g_xsim_exercise_id_tl { chapter-value = \arabic{chapter} , chapter = \thechapter } } \tl_set_eq:NN \ExerciseID \g_xsim_exercise_id_tl \tl_set:Nn \ExerciseType {#1} \xsim_verbose:x { Starting~ exercise~ type~ `#1' with~ id~ ` \g_xsim_exercise_id_tl '. } \xsim_exercise_if_use:nVTF {#1} \g_xsim_exercise_id_tl { \stepcounter { \xsim_get_parameter:nn {#1} {number} } % don't step if in active collection: \xsim_if_collections_active:TF { \xsim_set_property:nVnn {#1} \g_xsim_exercise_id_tl {print} {false} } { \xsim_step_exercise_counter:nV {#1} \g_xsim_exercise_id_tl \xsim_set_property:nVnn {#1} \g_xsim_exercise_id_tl {used} {true} } \xsim_set_property:nVnn {#1} \g_xsim_exercise_id_tl {use} {true} } { \xsim_set_properties:nVn {#1} \g_xsim_exercise_id_tl { use = false , print = false } } \xsim_update_collections:nV {#1} \g_xsim_exercise_id_tl } } \cs_new_protected:Npn \xsim_stop_exercise:n #1 { \xsim_if_insert_mode:F { \tl_gset_eq:NN \g_xsim_exercise_id_tl \ExerciseID } } % ---------------------------------------------------------------------------- % #1: type % #2: id \prg_new_protected_conditional:Npnn \xsim_exercise_if_print:nn #1#2 {T,F,TF} { \xsim_if_boolean_property:nnnTF {#1} {#2} {print!} { \prg_return_true: } { \bool_set_true:N \l____xsim_print_bool \bool_if:cF {l__xsim_#1_exercise_print_bool} { \bool_set_false:N \l____xsim_print_bool } \xsim_if_tagged:nnF {#1} {#2} { \bool_set_false:N \l____xsim_print_bool } \xsim_if_boolean_property:nnnF {#1} {#2} {print} { \bool_set_false:N \l____xsim_print_bool } \bool_if:NTF \l____xsim_print_bool { \prg_return_true: } { \prg_return_false: } } } \cs_generate_variant:Nn \xsim_exercise_if_print:nnT {nV} \cs_generate_variant:Nn \xsim_exercise_if_print:nnTF {nV} % #1: type % #2: id \prg_new_protected_conditional:Npnn \xsim_exercise_if_use:nn #1#2 {T,F,TF} { \xsim_if_boolean_property:nnnTF {#1} {#2} {use!} { \prg_return_true: } { \xsim_exercise_if_print:nnTF {#1} {#2} { \prg_return_true: } { \bool_if:cTF {l__xsim_#1_exercise_use_bool} { \xsim_if_boolean_property:nnnTF {#1} {#2} {use} { \xsim_if_tagged:nnTF {#1} {#2} { \prg_return_true: } { \prg_return_false: } } { \prg_return_false: } } { \prg_return_false: } } } } \cs_generate_variant:Nn \xsim_exercise_if_use:nnT {nV} \cs_generate_variant:Nn \xsim_exercise_if_use:nnTF {nV} % #1: type % #2: id % #3: exercise|solution \prg_new_protected_conditional:Npnn \xsim_if_print:nnn #1#2#3 {T,F,TF} { \use:c {xsim_#3_if_print:nnTF} {#1} {#2} { \prg_return_true: } { \prg_return_false: } } % ---------------------------------------------------------------------------- \xsim_new_aux_property:cpn {total-number} #1 { \int_gset:Nn \g_xsim_max_id_int {#1} } \RequirePackage {etoolbox} \AtEndPreamble { \xsim_foreach_exercise_type:n { \tl_new:c { number of #1 s } \tl_set:cn { number of #1 s } {0} \xsim_new_aux_property:cpn {#1} ##1 { \tl_gset:cn { number of #1 s } {##1} } } } \xsim_at_begin_document:n { \xsim_foreach_exercise_type:n { \xsim_write_to_aux:x { \token_to_str:N \providecommand \token_to_str:N \numberof #1 s {} } } } \xsim_at_end_document:n { \xsim_if_clear_aux:T { \int_compare:nNnF { \g_xsim_max_id_int } = { \g_xsim_id_int } { \xsim_empty_lists: } } \xsim_add_property_to_aux:nn {total-number} { { \int_use:N \g_xsim_id_int } } \xsim_foreach_exercise_type:n { \xsim_add_property_to_aux:nn {#1} { { \arabic { \xsim_get_parameter:nn {#1} {number} } } } } \xsim_update_list:n {types} } % ---------------------------------------------------------------------------- \file_input_stop: 2020/02/21 - implement issue #2 2020/03/01 - add options `s/name` and `/name` 2020/03/07 - set \ExerciseID and \ExerciseType only locally 2020/03/14 - performance optimizations