I found that writing LaTeX formulas is a tiring task, requiring the input of many special characters, especially when dealing with structures like matrices and parentheses. Therefore, I tried using S-expressions to describe the formulas and then convert them into LaTeX format. For example,
(displayln (sexp->latex `("a" + "b" - alpha (sup "c"))))
(displayln (sexp->latex `("a" "b" (frac ("a" "b") ("c" "d")))))
will produce
a+b-\alpha ^{c}
ab\frac{ab}{cd}
The advantage of using this method to write equations is that it allows you to leverage the infrastructure of programming languages, such as bracket matching checks, internal equation calculations, or using aliases (although LaTeX can achieve similar effects with \newcommand
, I find this approach more convenient). For example,
(define abc `(a + b - c))
(displayln (sexp->latex `(a + b - (c) + ,abc + "a" - (frac 3 gamma))))
will produce
a+b-c+a+b-c+a-\frac{3}{\gamma }
More complex equations can also be generated using this method, such as matrices and summations. For example,
(displayln (sexp->latex `(a + b + (bmat (1 2 3) (4 5 6)))))
(displayln (sexp->latex `(a + b + (sum (i = 1) (6) i))))
will produce
a+b+\begin{bmatrix}
1 & 2 & 3 \\
4 & 5 & 6
\end{bmatrix}
a+b+\sum_{i=1}^{6}i
Inputting brackets in LaTeX can also be done using this method.
(displayln (sexp->latex `(a + (brak b + c) - (sbrk (d - e)) prod (brac f))))
will produce
a+\left( b+c\right) -\left[ d-e\right] \times \left\{ f\right\}
The implementation of this tool is actually quite simple, and the main part of the function looks like this:
(define (sexp->latex sexp)
(apply string-append (flatten (tex-preprocess sexp))))
(define (tex-preprocess sexp)
(match sexp
[(? string? sexp) sexp]
[(list-rest 'sup res) (tex-sup res)]
[(list-rest 'sub res) (tex-sub res)]
[(list-rest 'frac res) (tex-frac res)]
[(list-rest 'bmat res) (tex-bmatrix res)]
[(list-rest 'sum res) (tex-sum res)]
[(list-rest 'brak res) (tex-bracket res)]
[(list-rest 'sbrk res) (tex-squarebracket res)]
[(list-rest 'brac res) (tex-brace res)]
[(? pair? sexp) (map tex-preprocess sexp)]
[(? greek? sexp) (list "\\" (symbol->string sexp) " ")]
[(? tex-sym-assoc sexp) (list "\\" (cdr (tex-sym-assoc sexp)) " ")]
[(? symbol? sexp) (symbol->string sexp)]
[(? number? sexp) (number->string sexp)]
[_ (error "no matched")]))
Here, tex-preprocess
recursively converts each statement with special meaning inside the entire S-expression into a list of strings, ultimately forming a tree composed entirely of strings. The tree is then flattened, and these strings are concatenated together. For example,
(tex-preprocess `(a + (brak b + c) - (sbrk (d - e)) prod (brac f)))
will produce
'("a"
"+"
("\\left( " ("b" "+" "c") "\\right) ")
"-"
("\\left[ " (("d" "-" "e")) "\\right] ")
("\\" ("times") " ")
("\\left\\{ " ("f") "\\right\\} "))
The implementation of each sub-processor is also quite simple: they recursively process the sub-content and then add their own part to it. For example, the tex-bmatrix
can be implementated as
(define (tex-bmatrix sexp)
(if (not (apply = (map length sexp)))
(error "BMATRIX: invalid input")
(list "\\begin{bmatrix}\n"
(add-between (map (lambda (s)
; s is each line
; (map parse s) is to parse every single element
(add-between (map tex-preprocess s) " & "))
sexp)
" \\\\\n")
"\n\\end{bmatrix}")))
I have uploaded the whole file to GitHub:
chemPolonium/roxy-equation: Convert Racket expression to LaTeX equations
This is still a conceptual idea in the draft stage. If you have any suggestions, feel free to share them and thank you for your suggestions.