diff --git a/latex.rkt b/latex.rkt new file mode 100644 index 0000000..a37d03f --- /dev/null +++ b/latex.rkt @@ -0,0 +1,54 @@ +#reader scribble/reader +#lang racket +(require racket/system racket/gui/base) +(provide latex) + +(define str string-append) + +(define TEMPLATE + @str{\documentclass[a0,landscape]{article} + \usepackage[mathletters]{ucs} + \usepackage[utf8x]{inputenc} + \usepackage{amsmath} + \pagestyle{empty} + \usepackage{breqn} + \begin{document} + \hsize=150mm + \begin{dmath*}[style={\huge}] + ~a + \end{dmath*} + \end{document}}) + +(define COMMANDS + @str{/usr/texbin/pdflatex x.tex + /opt/local/bin/convert -density 96x96 x.pdf -trim +repage x.png}) + +(define (latex . strs) + (define latex (make-temporary-file "latex~a" 'directory)) + (define (run) + (parameterize ([current-directory latex] + [current-input-port (open-input-bytes #"")] + [current-output-port (open-output-string)]) + (call-with-output-file* "x.tex" #:exists 'truncate + (lambda (o) (fprintf o TEMPLATE (string-append* strs)))) + (unless (system (regexp-replace #rx"\n+" COMMANDS " \\&\\& ")) + (display (get-output-string (current-output-port)) + (current-error-port)) + (error 'latex + "commands did not run successfully, see above output")) + (make-object image-snip% "x.png"))) + (define (cleanup) (delete-directory/files latex)) + ; (display-lines (list latex)) + (dynamic-wind void run cleanup)) + +;; Examples +;@latex{\sum_{i=0}^{\infty}\lambda_i} +;(let ([self @str{\lambda x . x x}]) +; @latex{(@self) (@self)}) + +;;; An example to test breaking lines with various page width +;@latex{ +;x^{20}+20\,x^{19}+190\,x^{18}+1140\,x^{17}+4845\,x^{16}+15504\,x^{ +; 15}+38760\,x^{14}+77520\,x^{13}+125970\,x^{12}+167960\,x^{11}+184756 +; \,x^{10}+167960\,x^9+125970\,x^8+77520\,x^7+38760\,x^6+15504\,x^5+ +; 4845\,x^4+1140\,x^3+190\,x^2+20\,x+1} diff --git a/racket-maxima.rkt b/racket-maxima.rkt new file mode 100644 index 0000000..b8cccbd --- /dev/null +++ b/racket-maxima.rkt @@ -0,0 +1,160 @@ +#lang at-exp racket +(require racket/tcp racket/gui/base + (planet jaymccarthy/slideshow-latex/latex2bitmap)) + +;;; +;;; Maxima in Racket +;;; + +;; This module starts an external Maxima process. +;; The function send will send a command to Maxima. +;; The function receive will get the output from Maxima as a list of strings. +;; The various send-* and receive-* functions sends and receives to and from Maxima. +;; The various read-* and display-* functions reads and displays to Racket (DrRacket). + + +;;; Configuration: Change maxima, latex and dvipng paths here. +(define PORT 8087) +(define MAXIMA-PATH "/Applications/Maxima.app/Contents/Resources/maxima.sh") + +(define latex-enabled? #t) ; set to #f to disable latex rendering +(latex-path "/usr/texbin/latex") +(dvipng-path "/usr/texbin/dvipng") +(latex-debug? #f) ; if #t prints errors from LaTeX. + +;;; Parameters + +(define out (make-parameter #f)) ; output port for sending +(define in (make-parameter #f)) ; input port for receiving + +;;; Sending + +(define (send str) + (sync (out)) + (display str (out)) + (flush-output (out))) + +(define (send-command str) + (send str)) + +;; Receiving + +(define (receive-line) + (read-line (in))) + +(define (receive-welcome-message) + ; Due to the flag --very-quiet the welcome is + ; a single line containg the pid. + (list (receive-line))) + +(define (maybe-receive-line) + (if (sync/timeout 0 (in)) + (receive-line) + #f)) + +(define (receive) + (let ([first-line (receive-line)]) + (let loop ([lines (list first-line)]) + (let ([line (maybe-receive-line)]) + (if line + (loop (cons line lines)) + (reverse lines)))))) + +(define (receive-whitespace) + (let ([c (read-char (in))]) + (when (not (char-whitespace? c)) + (error 'read-whitespace "expected to receive whitespace " c)))) + +;; String utilities + +(define (blank-line? line) + (andmap char-whitespace? (string->list line))) + +(define (labeled-line? line) + (regexp-match #rx"^(\\(.+\\)) (.*)$" line)) + +(define (remove-$$ str) + (second (regexp-match #px"^\\$\\$(.*)\\$\\$" str))) + +(define (string-begins-with-$$? str) + (regexp-match #rx"^\\$\\$.*$" str)) + +(define (string-ends-with-$$? str) + (regexp-match #rx"^.*\\$\\$$" str)) + +(define (maybe-add-$$ str) + (string-append + (if (string-begins-with-$$? str) + "" "$$") + str + (if (string-ends-with-$$? str) + "" "$$"))) + +(define (string-ref-last str) + (if (string=? "" str) + #f + (string-ref str (sub1 (string-length str))))) + +;; List utilies + +(define (remove-last xs) + (if (empty? xs) xs (drop-right xs 1))) + +;; Displaying + +(define (display-line datum) + (display datum) + (newline)) + +(define (display-prompt prompt) + (display prompt) + (display " ")) + +(define (display-output lines) + (unless (empty? lines) + (display-lines + (if (and latex-enabled? (string-begins-with-$$? (first lines))) + (list (latex-lines (remove-last lines))) + lines)))) + +(define (latex-lines lines) + (latex (string-append* lines))) + +(define (latex str) + (parameterize ([latex-preamble "\\usepackage{breqn}"] + [latex-dpi 90]) + (make-object image-snip% + (latex->bitmap + @string-append{\begin{dmath*}[style={\huge}]@str \end{dmath*}})))) + + +;; Reading + +(define (read-command) + (let loop ([lines '()]) + (let ([line (read-line)]) + (if (memv (string-ref-last line) '(#\$ #\;)) + (string-append* (reverse (cons line lines))) + (loop (cons line lines)))))) + +;; REPL + +(define (read-send-receive-loop) + (display-prompt ">") + (send-command (read-command)) + (display-output (receive)) + (newline) + (read-send-receive-loop)) + + +;; Start Maxima and REPL + +(let ([listener (tcp-listen PORT 3 #t)]) + (match-let + ([(list pin pout pid perr status) + (process* MAXIMA-PATH "--very-quiet" "-s" (format "~a" PORT))]) + (let-values ([(lin lout) (tcp-accept listener)]) + (parameterize ([in lin] [out pout]) + (receive-welcome-message) + (display "Enter a Maxima command. Terminate a command with either ; or $ .\n") + (read-send-receive-loop)))))