branch: externals/org-mathsheet commit 4cfa9040d2a1bd9c178f94e0547547cf98d1e255 Author: Ian Martins <ia...@jhu.edu> Commit: Ian Martins <ia...@jhu.edu>
Added math funcs, multi range sequences, worksheet instruction --- example.org | 88 +++++++------- mathsheet.org | 364 +++++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 286 insertions(+), 166 deletions(-) diff --git a/example.org b/example.org index 3abbe060bd..19ea901393 100644 --- a/example.org +++ b/example.org @@ -9,58 +9,52 @@ | 1 | 3 | [a=2..10] - [1..$a] | small number subtraction, positive result | #+name: add-sub-2 -| weight | order | template | descr | -|--------+-------+-------------------------------+-----------------------------------------| -| 3 | 1 | [1..10] + [0..10] | simple | -| 2 | 2 | [1..10] + [8..15] | second number bigger | -| 1 | 2 | [a=3..10] - [0..$a] | subtraction | -| 1 | 3 | [1..10] + [1..7] + [1..5] | three numbers | -| 1 | 4 | [a=1..10] + [0..10] - [0..$a] | three with subtraction, positive result | -| 0 | 0 | [$a*[1..5]] / [a=1..10] | division | +| weight | order | template | descr | +|--------+-------+--------------------------------------+-----------------------------------------| +| 3 | 1 | [1..10] + [0..10] | simple | +| 2 | 2 | [1..10] + [8..15] | second number bigger | +| 1 | 2 | [a=3..10] - [0..$a] | subtraction | +| 1 | 3 | [1..10] + [1..7] + [1..5] | three numbers | +| 1 | 4 | [a=1..10] + [0..10] - [0..$a] | three with subtraction, positive result | +| 0 | 0 | [$a*[1..5]] / [a=1..10] | division | +| 5 | 5 | [a=1..10] + [b=0..10] - [0..($a+$b)] | three with subtraction, positive result | -#+BEGIN: problem-set :templates "add-sub-1" :count 21 :instruction "Compute the answer" -| problem | answer | -|---------+--------| -| 6 + 3 | 9 | -| 1 + 6 | 7 | -| 5 + 6 | 11 | -| 2 + 0 | 2 | -| 1 + 4 | 5 | -| 8 + 7 | 15 | -| 6 + 5 | 11 | -| 2 + 3 | 5 | -| 9 + 9 | 18 | -| 3 + 8 | 11 | -| 1 + 11 | 12 | -| 4 + 2 | 6 | -| 2 + 9 | 11 | -| 1 + 4 | 5 | -| 11 + 1 | 12 | -| 6 + 0 | 6 | -| 4 + 9 | 13 | -| 7 + 10 | 17 | -| 3 - 1 | 2 | -| 9 - 8 | 1 | -| 2 - 1 | 1 | +#+BEGIN: problem-set :templates "add-sub-2" :count 12 :instruction "Compute the answer" +| problem | answer | +|-----------+--------| +| 2 + 6 | 8 | +| 6 + 4 | 10 | +| 9 + 5 | 14 | +| 4 + 9 | 13 | +| 6 - 4 | 2 | +| 6 + 12 | 18 | +| 5 + 3 + 1 | 9 | +| 4 + 1 - 3 | 2 | +| 2 + 7 - 5 | 4 | +| 1 + 7 - 5 | 3 | +| 9 + 6 - 7 | 8 | +| 2 + 0 - 0 | 2 | #+END: * algebra #+name: algebra-1 -| weight | order | template | descr | -|--------+-------+------------------------------------+--------| -| 3 | 1 | x / ([2..4] + [a=0..5]) = [$a..10] | simple | -| 3 | 2 | [$a*[2..10]] / x = [a=1,2,4] | simple | -| 1 | 3 | x^2 = sqrt([16 - $a] + [a=1..5]) | simple | +| weight | order | template | descr | +|--------+-------+---------------------------------------+-----------------------| +| 3 | 1 | x / ([2..4] + [a=0..5]) = [$a..10] | simple | +| 3 | 2 | [$a*[2..10]] / x = [a=1,2,4] | simple | +| 1 | 3 | x^2 = sqrt([16 - $a] + [a=1..5]) | sqrt | +| 3 | 4 | [-5..-1,1..5] x + [0..10] = [-10..10] | double range sequence | +| 3 | 4 | x = 2/x + sin([1..10]) | trig, two vars | #+BEGIN: problem-set :templates "algebra-1" :count 8 :instruction "Solve for x" -| problem | answer | -|--------------------+--------| -| x / (3 + 0) = 5 | x = 15 | -| x / (3 + 4) = 9 | x = 63 | -| x / (3 + 0) = 4 | x = 12 | -| 12 / x = 2 | x = 6 | -| 8 / x = 2 | x = 4 | -| 36 / x = 4 | x = 9 | -| 6 / x = 2 | x = 3 | -| x^2 = sqrt(13 + 3) | x = 2 | +| problem | answer | +|--------------------+-------------------| +| x / (2 + 3) = 9 | x = 45 | +| x / (3 + 3) = 9 | x = 54 | +| 8 / x = 2 | x = 4 | +| 36 / x = 4 | x = 9 | +| x^2 = sqrt(12 + 4) | x = 2 | +| -5 x + 8 = 0 | x = 8:5 | +| -2 x + 3 = 0 | x = 3:2 | +| x = 2/x + sin(2) | x = 1.43177096141 | #+END: diff --git a/mathsheet.org b/mathsheet.org index 3f18868811..73279900a5 100644 --- a/mathsheet.org +++ b/mathsheet.org @@ -1,12 +1,87 @@ -* Goal -The goal is to generate a math practice sheet made up of dynamic -problems that are defined based on a set of flexible templates. The -problem distribution and order should also be configurable. -* Problem Templates -** Overview -This section contains some example templates. Each table defines a -worksheet. Each time the worksheet is generated the problems are -re-randomized. +* TODO rename to org-mathsheet.el +* Overview +** Description +This is a math worksheet generator. The worksheets are randomly +generated based on templates that define what kinds of problems to +include along with the order and relative frequency that each type of +problem should appear on the worksheet. +** Audience +This could be useful for anyone that wants to provide math practice to +someone else. It could be useful for a teacher, tutor, homeschool +parent, or any parent. +** Examples +Here are some example worksheets generated by this tool: +1. arithmatic +2. algebra +** Parts +There are two main components involved in generating a worksheet: +1. the problem templates +2. the problem-set block +*** Problem Templates +**** Expression Templates +The worksheet is made of a set of math problems. Each problem is +defined by a template that lays out an equation or expression and +shows where variables or numbers should be. For example, consider this +template: +#+begin_example +[0..15] + [1..10] +#+end_example +The parts within the brackets are fields. When a template is made into +a problem and added to a worksheet, each field is replaced by a number +based on a set of rules. The supported rules are described in more +detail below, but ~[0..15]~ means pick a random number between 0 and 15, +inclusive, so the above template could result in problems like these: +#+begin_example +1 + 2 +15 + 10 +5 + 1 +#+end_example +**** Equation Templates +In additon to expressions where the answer is a number, templates can +be equations where the solution is found by solving for the +variable. For example, consider this template: +#+begin_example +[1..5] x + [0..10] = [-10..10] +#+end_example +This can produce the following problems: +#+begin_example +3 x + 6 = -1 +4 x + 2 = 2 +1 x + 8 = -3 +#+end_example +**** Field Rules +These are the field rules: +- [-2..8] :: choose a random number from -2 to 8, inclusive +- [1,3,5] :: choose randomly from 1, 3 or 5 +- [-3..-1,1..3] :: choose a random number from -3 to -1 or 1 to 3 +- [10/(2-1)] :: evaluate the expression +- [a=...] :: assign the variable a to the number chosen for this field +- [-2..$a] :: any number from -2 to the value assigned to ~a~ in another + field +- [0..[$a/2]] :: any number from 0 to half the value assigned to ~a~. + +The ability to keep track of the random number chosen in one field and +use it to influence another allows the template to be written to avoid +answers that are negative or don't divide evenly. +**** Template Examples +Here are a few more examples: + +Division problem that divides evenly +#+begin_example +[$a*[1..5]] / [a=1..10] +#+end_example + +Addition and subtraction, still with a positive result +#+begin_example +[a=1..10] + [b=0..10] - [0..($a+$b)] +#+end_example + +*** The Problem Template Table + +You may want to have more than one type of problem on a worksheet, so + +Each table defines a worksheet. Each time the worksheet is generated +the problems are re-randomized. The table contains the following columns: - weight :: the relative number of this type of problem to include on @@ -15,19 +90,9 @@ The table contains the following columns: problems with the same order will be intermingled. - template :: this is the template used to generate problems of this type. Templates are described in more detail below. -- descr :: just notes, not used in worksheet generation. +- descr :: just your notes, not used in worksheet generation. -Templates are problems but the numbers are replaced with placeholders -in square brackets. -- [0..10] :: any number from 0 to 10 -- [a=...] :: assign the variable a to the number chosen for this field -- [1,3,5] :: choose 1 or 3 or 5 -- [10/(2-1)] :: evaluate the expression -- [-2..$a] :: any number from -2 to the value assigned to a in another - placeholder -- [0..[$a/2]] :: placeholders can be embedded within placeholders - -** Examples +**** Examples We label the table so that we can refer to it from the dynamic block that generates the worksheet. Only the first three columns are used. @@ -41,7 +106,9 @@ that generates the worksheet. Only the first three columns are used. | 1 | 3 | [1..10] + [1..7] + [1..5] | three terms | | 1 | 4 | [a=1..10] + [0..10] - [0..$a] | three terms with subtraction | | 0 | 0 | [$a*[1..5]] / [a=1..10] | division | - +*** Problem-Set Block +**** Overview +**** Examples * Code walkthrough ** Problem generation *** Header @@ -57,17 +124,17 @@ This package needs [[https://elpa.gnu.org/packages/peg.html][peg]]. #+end_src *** Variables -Need ~ianxm/var-list~ to keep track of the variables between fields. +Need ~mathsheet--var-list~ to keep track of the variables between fields. ~worksheet-template~ is the LaTeX template for the worksheet. #+name: variables #+begin_src elisp :tangle mathsheet.el :var page=page - (defvar ianxm/var-list '() + (defvar mathsheet--var-list '() "List of variables used in a problem") - (defconst ianxm/worksheet-template page - "LaTeX template for worksheet") + (defconst mathsheet--worksheet-template page + "LaTeX template for the worksheet") #+end_src *** Scan problem @@ -98,6 +165,7 @@ The last entry is ~nil~ for "not visited." It is used by ~dfs-visit~. for example: #+begin_example [$a + 2 + [a=1..5]] => '((nil (a) m1 m19 nil) (a nil m11 m18 nil)) + '((:fields (_0 (a a) (marker . marker) nil) (a nil (marker . marker) nil)) (:alg-vars)) #+end_example This uses the peg package to parse the problem. Instead of using the @@ -110,8 +178,15 @@ new field to the list when we close the current field. #+name: scan-problem #+begin_src elisp :tangle mathsheet.el - (defun ianxm/scan-problem () - "Scan problem" + (defun mathsheet--scan-problem () + "Scan a problem. + + This parses the problem and produces a list containing info about + its fields. For each field it returns a list containing: + 1. a symbol for the assigned variable or a unique placeholder + 2. a list of variables this field depends on + 3. a cons containing start and end markers for the field in the current buffer + 4. `nil' which is used by `dfs-visit' later" (let ((field-index 0) open-fields ; stack closed-fields ; list @@ -122,7 +197,7 @@ new field to the list when we close the current field. (field open (opt assignment) stuff close) (space (* [space])) (open (region "[") - `(l r -- (progn + `(l _ -- (progn (push (list (intern (concat "_" (number-to-string field-index))) ; asn-var nil ; deps @@ -131,28 +206,28 @@ new field to the list when we close the current field. open-fields) (setq field-index (1+ field-index)) "."))) - (assignment (region (substring letter)) "=" - `(l v r -- (progn - (setcar - (car open-fields) - (intern v)) - "."))) + (assignment (substring letter) "=" + `(v -- (progn + (setcar + (car open-fields) + (intern v)) + "."))) (asn-var "$" (substring letter) - `(v -- (progn - (push (intern v) (cadar open-fields)) - "."))) + `(v -- (progn + (push (intern v) (cadar open-fields)) + "."))) (alg-var (substring letter) `(v -- (progn (push v alg-vars) "."))) (close (region "]") - `(l r -- (progn + `(l _ -- (progn (setcdr (caddar open-fields) (copy-marker l t)) (when (> (length open-fields) 1) ; add parent to child dependency (push (caar open-fields) (cadadr open-fields))) (push (pop open-fields) closed-fields) "."))) - (math-func (or "sqrt")) + (math-func (or "sqrt" "sin" "cos" "tan" "asin" "acos" "atan" "floor" "ceil" "round")) (letter [a-z]) (digit [0-9]) (symbol (or "." "," "+" "-" "*" "/" "^" "(" ")" "="))) @@ -171,13 +246,13 @@ test scan <<scan-problem>> (with-temp-buffer - (insert "y = [1..4] + [5,7,9]") + (insert "[0..4,6-9,11] * x + [floor([-10..10]/3)] = [-10..10]") (goto-char (point-min)) - (ianxm/scan-problem)) + (mathsheet--scan-problem)) #+end_src #+RESULTS: -: ((:fields (_1 nil (#<marker in no buffer> . #<marker (moves after insertion) in no buffer>) nil) (_0 nil (#<marker in no buffer> . #<marker (moves after insertion) in no buffer>) nil)) (:alg-vars "y")) +: ((:fields (_3 nil (#<marker in no buffer> . #<marker (moves after insertion) in no buffer>) nil) (_1 (_2) (#<marker in no buffer> . #<marker (moves after insertion) in no buffer>) nil) (_2 nil (#<marker in no buffer> . #<marker (moves after insertion) in no buffer>) nil) (_0 nil (#<marker in no buffer> . #<marker (moves after insertion) in no buffer>) nil)) (:alg-vars "x")) *** Reduce field @@ -191,41 +266,58 @@ This uses the peg package to parse the field. This time there shouldn't be any fields embedded within the field. We should have already evaluated and replaced them. +We use ~..~ insead of ~-~ for range because if we used ~-~ then this would +be ambiguous: +#+begin_example +[1-5] +#+end_example + #+name: reduce-field #+begin_src elisp :tangle mathsheet.el - (defun ianxm/reduce-field () + (defun mathsheet--reduce-field () + "Reduce the field to a number. + + Parse the field again, replacing spans with random numbers and + evaluating arithmetic operations. The field shouldn't have any + internal fields so this should result in a single number. Return + that number." (with-peg-rules - ((field "[" space (or range sequence assignment expression value) space "]") + ((field "[" space (or math-func expression sequence assignment value) space "]") (expression (list value space operation space value (* space operation space value)) `(vals -- (string-to-number (calc-eval - (mapconcat - (lambda (x) (if (numberp x) (number-to-string x) x)) - vals - " "))))) + (list + (mapconcat + (lambda (x) (if (numberp x) (number-to-string x) x)) + vals + " ")) + calc-prefer-frac nil)))) (operation (substring (or "+" "-" "*" "/"))) (assignment var-lhs space "=" space (or range sequence) `(v r -- (progn - (push (cons (intern v) r) ianxm/var-list) + (push (cons (intern v) r) mathsheet--var-list) r))) + (sequence (list (or range value) (* "," space (or range value))) + `(vals -- (seq-random-elt vals))) (range value ".." value `(min max -- (+ (random (- max min)) min))) - (sequence (list value "," value (* "," value)) - `(vals -- (seq-random-elt vals))) (value (or (substring (opt "-") (+ digit)) var-rhs parenthetical) `(v -- (if (stringp v) (string-to-number v) v))) - (parenthetical "(" expression ")") + (parenthetical "(" (or expression value) ")") (var-lhs (substring letter)) ; var for assignment (var-rhs "$" (substring letter) ; var for use - `(v -- (let ((val (alist-get (intern v) ianxm/var-list))) + `(v -- (let ((val (alist-get (intern v) mathsheet--var-list))) (or val (error "var %s not set" v))))) + (math-func (substring (or "sqrt" "sin" "cos" "tan" "asin" "acos" "atan" "floor" "ceil" "round")) + parenthetical + `(f v -- (string-to-number (calc-eval (format "%s(%s)" f v))))) (space (* [space])) (letter [a-z]) (digit [0-9])) (peg-run (peg field) (lambda (x) (message "failed %s" x)) - (lambda (x) (funcall x))))) + (lambda (x) (car (funcall x)))))) #+end_src test with @@ -235,13 +327,14 @@ test with <<reduce-field>> (with-temp-buffer - (insert "[1..4]") + ;(insert "[1..10,15..20,50]") + (insert "[1..10]") (goto-char (point-min)) - (ianxm/reduce-field)) + (mathsheet--reduce-field)) #+end_src #+RESULTS: -: (1) +: 8 *** Replace field @@ -249,13 +342,18 @@ Replace a field with the value returned from reducing it. #+name: replace-field #+begin_src elisp :tangle mathsheet.el - (defun ianxm/replace-field (node) + (defun mathsheet--replace-field (node) + "Replace a field with the number to which it reduces + + Update the current buffer by replacing the field at point in the + current buffer with the number it reduces to. NODE contains the + info for the current field." (let ((start (caaddr node)) (end (1+ (cdaddr node))) val) (goto-char start) (when (looking-at "\\[") - (setq val (car (ianxm/reduce-field))) + (setq val (mathsheet--reduce-field)) (goto-char start) (delete-char (- end start) t) (insert (number-to-string val))))) @@ -269,7 +367,12 @@ the node. #+name: dfs-visit #+begin_src elisp :tangle mathsheet.el - (defun ianxm/dfs-visit (node fields) + (defun mathsheet--dfs-visit (node fields) + "Visit NODE as part of a DFS of the problem + + Traverse the fields of a problem using depth first search to + ensure that field replacement happens in dependency order. FIELDS + is a list of all fields in the problem." (pcase (cadddr node) (1 (error "cycle detected")) ; cycle (2) ; skip @@ -277,10 +380,10 @@ the node. (setcar (cdddr node) 1) ; started (let ((deps (cadr node))) (dolist (dep deps) - (ianxm/dfs-visit + (mathsheet--dfs-visit (assq dep fields) fields))) - (ianxm/replace-field node) ; visit + (mathsheet--replace-field node) ; visit (setcar (cdddr node) 2)))) ; mark done #+end_src @@ -293,21 +396,26 @@ processes all fields in a problem. #+end_example #+begin_src elisp :tangle mathsheet.el - (defun ianxm/fill-problem (full-problem) + (defun mathsheet--fill-problem (full-problem) + "Replace all fields in FULL-PROBLEM + + Goes through all fields in the given problem in dependency order + and replaces fields with numbers. When this completes the problem + will be ready to solve." (with-temp-buffer ;; stage problem in temp buffer (insert full-problem) - (beginning-of-buffer) + (goto-char (point-min)) ;; find fields, assignment variables, algebraic variables, dependencies - (let* ((scan-ret (ianxm/scan-problem)) + (let* ((scan-ret (mathsheet--scan-problem)) (fields (alist-get :fields scan-ret)) (alg-vars (alist-get :alg-vars scan-ret))) ;; visit fields ordered according to dependencies (dolist (node fields) - (ianxm/dfs-visit node fields)) - (setq ianxm/var-list '()) + (mathsheet--dfs-visit node fields)) + (setq mathsheet--var-list '()) ;; return filled problem `((:problem . ,(buffer-string)) @@ -322,11 +430,11 @@ test with this <<replace-field>> <<dfs-visit>> - (ianxm/fill-problem "[1..12] + [1,4,6,10]") - ;;(ianxm/fill-problem "[1..[2..[10..100]]]") - ;;(ianxm/fill-problem "[$a*[1..10]] / [a=1..10]") - ;;(ianxm/fill-problem "[$a]/(3+[a=1..5])") - ;; (ianxm/fill-problem "1/x + 2 = [-10..[10..20]]") + (mathsheet--fill-problem "[1..12] + [1,4,6,10]") + ;;(mathsheet--fill-problem "[1..[2..[10..100]]]") + ;;(mathsheet--fill-problem "[$a*[1..10]] / [a=1..10]") + ;;(mathsheet--fill-problem "[$a]/(3+[a=1..5])") + ;; (mathsheet--fill-problem "1/x + 2 = [-10..[10..20]]") #+end_src @@ -366,7 +474,13 @@ other examples #+name: generate-problems #+begin_src elisp :tangle mathsheet.el - (defun ianxm/generate-problems (template-name count) + (defun mathsheet--generate-problems (template-name count) + "Generate COUNT problems based on TEMPLATE-NAME + + Generate problems and answers based on what is defined in the + given template table. The template table defines problem + templates as well as relative weights and how they should be + ordered." (let (total-weight templates problems) (save-excursion (goto-char (point-min)) @@ -385,7 +499,7 @@ other examples templates 0))) ;; calculate number for each row - (dotimes (ii (length templates) problems) + (dotimes (ii (length templates)) (let* ((item (nth ii templates)) (weight (car item)) (needed (cond ; number of problems to add for this template @@ -395,42 +509,39 @@ other examples (- count (length problems))) (t (max (round (* (/ weight total-weight) count) ) 1)))) - problem answer) - - (let ((added 0) - (dup-count 0) - problem-set - fill-ret problem solution) - (while (< added needed) ; add until "needed" are kept - (let* ((fill-ret (ianxm/fill-problem (caddr item))) - (problem (alist-get :problem fill-ret)) - (alg-vars (alist-get :alg-vars fill-ret)) - (calc-string (if (not alg-vars) - problem - (format "solve(%s,[%s])" problem (string-join alg-vars ",")))) - (solution - (replace-regexp-in-string (rx (or "[" ".]" "]")) - "" - (calc-eval calc-string)))) - (cond - ((member problem problem-set) ; dedup problems - (setq dup-count (1+ dup-count)) - (when (> dup-count 100) - ;; high number of dups indicates a narrow problem space relative to problem count - (error "Giving up, too many dups"))) - (t - (push problem problem-set) - (push (list problem ; problem - solution ; solution - (cadr item) ; order - (not (null alg-vars))) ; true if algebraic variables exist - problems) - (setq added (1+ added))))))))) + (added 0) + (dup-count 0) + problem-set) + (while (< added needed) ; add until "needed" are kept + (let* ((fill-ret (mathsheet--fill-problem (caddr item))) + (problem (alist-get :problem fill-ret)) + (alg-vars (alist-get :alg-vars fill-ret)) + (calc-string (if (not alg-vars) + problem + (format "solve(%s,[%s])" problem (string-join (seq-uniq alg-vars) ",")))) + (solution + (replace-regexp-in-string (rx (or "[" ".]" "]")) + "" + (calc-eval calc-string)))) + (cond + ((member problem problem-set) ; dedup problems + (setq dup-count (1+ dup-count)) + (when (> dup-count 100) + ;; high number of dups indicates a narrow problem space relative to problem count + (error "Giving up, too many dups"))) + (t + (push problem problem-set) + (push (list problem ; problem + solution ; solution + (cadr item) ; order + (not (null alg-vars))) ; true if algebraic variables exist + problems) + (setq added (1+ added)))))))) ;; shuffle (dotimes (ii (- (length problems) 1)) (let ((jj (+ (random (- (length problems) ii)) ii))) - (psetf (elt problems ii) (elt problems jj) + (cl-psetf (elt problems ii) (elt problems jj) (elt problems jj) (elt problems ii)))) ;; sort by order @@ -450,24 +561,31 @@ I need to extract the values - :templates :: templates - :count :: 10 +- :instruction :: "Solve for x" #+begin_src elisp :tangle mathsheet.el (defun org-dblock-write:problem-set (params) - "Update problem-set block and optionally write a worksheet." + "Update problem-set block and optionally write a worksheet. + + PARAMS is a plist with the properties set on the dynamic block + header, which includes `:tempates' which is the name of the + templates table, `:count' which is the number of problems to put + on the worksheet, and `:instruction' which is the content of the + instruction line at the top of the page" ;; write the table header (insert "| problem | answer |\n") (insert "|-\n") ;; generate problem set - (let ((problems (ianxm/generate-problems + (let ((problems (mathsheet--generate-problems (plist-get params :templates) (plist-get params :count)))) ;; for each problem, write a row to the table (insert (mapconcat - (lambda (problem) (format "|%s|%s|" + (lambda (problem) (format "| %s | %s |" (car problem) (cadr problem))) problems @@ -478,7 +596,7 @@ I need to extract the values ;; should we generate the sheet? (when (y-or-n-p "Write worksheet? ") - (ianxm/gen-worksheet + (mathsheet--gen-worksheet (plist-get params :templates) (plist-get params :instruction) problems)))) @@ -549,7 +667,11 @@ Convert a calc expression to latex format. #+name: convert-to-latex #+begin_src elisp :tangle mathsheet.el - (defun ianxm/convert-to-latex (expr) + (defun mathsheet--convert-to-latex (expr) + "Format the given calc expression EXPR for LaTeX + + EXPR should be in normal calc format. The result is the same + expression (not simplified) but in LaTeX format." (let* ((calc-language 'latex) (calc-expr (math-read-expr expr)) (latex-expr (math-format-stack-value (list calc-expr 1 nil))) @@ -565,9 +687,13 @@ template name will overwrite the same file. #+begin_src elisp :results silent :tangle mathsheet.el - (defun ianxm/gen-worksheet (template-name instruction problems) - (with-temp-file (concat template-name ".tex") - (insert ianxm/worksheet-template) + (defun mathsheet--gen-worksheet (file-name instruction problems) + "Generate a worksheet with PROBLEMS. + + Write a file named FILE-NAME. Include the INSTRUCTION line at the + top." + (with-temp-file (concat file-name ".tex") + (insert mathsheet--worksheet-template) (goto-char (point-min)) (search-forward "<<instruction>>") @@ -580,16 +706,16 @@ template name will overwrite the same file. (dolist (row problems) (if (cadddr row) (insert (format"\\CircledItem %s\\vspace{4cm}\n" - (ianxm/convert-to-latex (car row)))) + (mathsheet--convert-to-latex (car row)))) (insert (format"\\CircledItem %s = \\rule[-.2\\baselineskip]{2cm}{0.4pt}\n" - (ianxm/convert-to-latex (car row)))))) + (mathsheet--convert-to-latex (car row)))))) (goto-char (point-min)) (search-forward "<<answers>>") (replace-match "") (dolist (row problems) (insert (format "\\CircledItem %s\n" - (ianxm/convert-to-latex (cadr row)))))) - (shell-command (concat "texi2pdf " template-name ".tex") + (mathsheet--convert-to-latex (cadr row)))))) + (shell-command (concat "texi2pdf " file-name ".tex") (get-buffer-create "*Standard output*"))) #+end_src