;; highlight.scm -- source code highlighting library
;; Copyright (c) 2011 Alex Shinn. All rights reserved.
;; BSD-style license: http://synthcode.com/license.txt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (string-concatenate-reverse ls)
(string-concatenate (reverse ls)))
(define (reverse-list->string ls)
(list->string (reverse ls)))
(define (highlight-detect-language str)
(cond
((or (string-contains str "(define")
(string-contains str "(eval")
(string-contains str "(set")
(string-contains str "(string")
(string-contains str "(let")
(string-contains str "(lambda (")
)
'scheme)
((string-contains str "\tmovl\t")
'asm)
(else
'c)))
(define (highlight source)
(let ((str (if (string? source) source (port->string source))))
((case (highlight-detect-language str)
((scheme) highlight-scheme)
((asm) highlight-assembly)
((c) highlight-c))
str)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define highlight-themes
'((light
(keyword . "#800080")
(type . "#008000")
(function . "#0000FF")
(variable . "#B8860B")
(comment . "#FF0000")
(string . "#BC8F8F")
(attribute . "#FF5000")
(preprocessor . "#FF00FF")
(builtin . "#FF00FF")
(character . "#0055AA")
(syntaxerror . "#FF0000")
(diff-deleted . "#5F2121")
(diff-added . "#215F21"))))
(define highlight-paren-styles
;;'("#BAFFFF" "#FFCACA" "#FFFFBA" "#CACAFF" "#CAFFCA" "FFBAFF")
'("#AAAAAA" "#888888" "#666666" "#444444" "#222222" "#000000"))
(define (highlight-style . theme)
(string-concatenate
(append
(map
(lambda (x)
(string-append
"." (symbol->string (car x)) " { color: " (cdr x)
"; background-color: inherit; }\n"))
(cond ((assq (and (pair? theme) (car theme)) highlight-themes) => cdr)
(else (cdar highlight-themes))))
(map
(lambda (s i)
(string-append
;;"span.paren" (number->string i)
;;":hover { color: inherit; background-color: " s "; }\n"
"span.paren" (number->string i)
" { color: " s "; background-color: inherit; }\n"))
highlight-paren-styles
(cdr (iota (+ 1 (length highlight-paren-styles)))))
)))
(define (highlight-start class out)
(display "" out))
(define (highlight-end class out)
(display "" out))
(define (highlight-class class str out)
(highlight-start class out)
(display str out)
(highlight-end class out))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (read-whitespace in)
(let lp ((res '()))
(if (char-whitespace? (peek-char in))
(lp (cons (read-char in) res))
(reverse-list->string res))))
(define (read-to-whitespace in res)
(cond
((char-whitespace? (peek-char in))
(reverse-list->string res))
(else
(read-to-whitespace in (cons (read-char in) res)))))
(define (read-escaped in term ls)
(let ((c (read-char in)))
(cond
((eof-object? c) (reverse-list->string ls))
((eqv? c term) (reverse-list->string (cons c ls)))
((eqv? c #\<) (read-escaped in term `(#\; #\t #\l #\& ,@ls)))
;;((eqv? c #\>) (read-escaped in term `(#\; #\t #\g #\& ,@ls)))
((eqv? c #\&) (read-escaped in term `(#\; #\p #\m #\a #\& ,@ls)))
;;((eqv? c #\\) (read-escaped in term (cons (read-char in) (cons c ls))))
(else (read-escaped in term (cons c ls))))))
(define (html-escape str)
(call-with-input-string str (lambda (in) (read-escaped in #f '()))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (highlight-scheme-delimiter? ch)
(or (eof-object? ch)
(char-whitespace? ch)
(memq ch '(#\; #\# #\( #\) #\[ #\] #\{ #\} #\' #\` #\, #\"))))
(define (highlight-scheme-definition? id)
(memq id '(define define-syntax define-module define-class
define-record define-record-type)))
(define (highlight-scheme-syntax? id)
(memq id '(if lambda define set! cond case let let* letrec letrec*
let-values let-values* let-optionals let-optionals*
let-keywords let-keywords* and-let* rec receive do
loop rxmatch-cond rxmatch-case begin when unless
match match-lambda match-let match-let* dotimes dolist
quote quasiquote unquote unquote-splicing error errorf
define-syntax let-syntax letrec-syntax syntax-rules
syntax-case parameterize module library require
require-extension use use-modules import export
define-module select-module provide autoload
condition-case guard cond-expand for with to by
in-list in-lists in-string in-string-reverse
in-vector in-vector-reverse in-file listing appending
summing multpliying up-from down-from else
)))
(define (highlight-scheme source)
(let ((in (if (string? source) (open-input-string source) source)))
(call-with-output-string
(lambda (out)
(define (read-to-eol ls)
(let ((c (read-char in)))
(cond
((eof-object? c) (reverse-list->string ls))
((eqv? c #\newline) (reverse-list->string (cons c ls)))
(else (read-to-eol (cons c ls))))))
(define (read-identifier ls)
(let ((c (peek-char in)))
(cond
((highlight-scheme-delimiter? c)
(string->symbol (reverse-list->string ls)))
(else
(read-char in)
(read-identifier (cons c ls))))))
(define (hash-mark n)
(let ((c (read-char in)))
(case c
((#\;)
(highlight-start 'comment out)
(highlight n)
(highlight-end 'comment out))
((#\\)
(let ((id (read-identifier (list (read-char in) #\\ #\#))))
(highlight-class 'string id out)
(highlight n)))
(else
(write-char #\# out)
(write-char c out)
(highlight n)))))
(define (highlight n)
(let ((c (read-char in)))
(if (eof-object? c)
#f
(case c
((#\;)
(let lp ((ls '()))
(let ((ls (cons (read-to-eol (list #\;)) ls)))
(cond
((eqv? #\; (peek-char in))
(lp ls))
(else
(highlight-class 'comment
(string-concatenate-reverse ls)
out)
(highlight n))))))
((#\")
(let ((str (read-escaped in #\" (list #\"))))
(highlight-class 'string str out)
(highlight n)))
((#\()
;;(highlight-start
;; (string->symbol
;; (string-append
;; "paren"
;; (number->string
;; (+ 1 (modulo n (length highlight-paren-styles))))))
;;out)
(write-char #\( out)
(if (highlight-scheme-delimiter? (peek-char in))
(highlight (+ n 1))
(let ((id (read-identifier '())))
(cond
((highlight-scheme-definition? id)
(highlight-class 'keyword id out)
(display (read-whitespace in) out)
(if (eqv? #\( (peek-char in))
(write-char (read-char in) out))
(highlight-class 'function (read-identifier '()) out)
(highlight (+ n 1)))
((highlight-scheme-syntax? id)
(highlight-class 'keyword id out)
(highlight (+ n 1)))
(else
(display "" out)
(display id out)
(display "" out)
(highlight (+ n 1)))))))
((#\))
(cond
((zero? n)
(highlight-class 'syntaxerror c out)
(highlight n))
(else
(write-char c out)
;;(highlight-end 'paren out)
(highlight (- n 1)))))
((#\#)
(hash-mark n))
((#\<)
(display "<" out)
(highlight n))
((#\&)
(display "&" out)
(highlight n))
;;((#\newline)
;; (write-char c out)
;; (highlight 0))
(else
(cond
((highlight-scheme-delimiter? c)
(write-char c out)
(highlight n))
(else
(let ((id (read-identifier (list c))))
(display "")
(display id out)
(display "")
(highlight n)))))))))
(highlight 0)))))
(define (highlight-c-keyword? id)
(memq id '(asm break case catch const_cast continue default delete
do dynamic_cast else explicit export false for friend goto
if mutable namespace new operator private protected public
register reinterpret_cast return sizeof static_cast switch
template this throw true try typedef typeid typename using
virtual while)))
(define (highlight-c-type? id)
(memq id '(auto bool char class const double enum extern float inline int long
short signed static struct union unsigned void volatile wchar_t)))
(define (highlight-c source)
(let ((in (if (string? source) (open-input-string source) source)))
(call-with-output-string
(lambda (out)
(define (read-to-eol ls)
(let ((c (read-char in)))
(cond
((eof-object? c) (reverse-list->string ls))
((eqv? c #\newline) (reverse-list->string (cons c ls)))
(else (read-to-eol (cons c ls))))))
(define (char-c-initial? c)
(and (char? c) (or (char-alphabetic? c) (eqv? c #\_) (eqv? c #\$))))
(define (char-c-identifier? c)
(and (char? c) (or (char-c-initial? c) (char-numeric? c))))
(define (read-identifier ls)
(let ((c (peek-char in)))
(if (char-c-identifier? c)
(read-identifier (cons (read-char in) ls))
(string->symbol (reverse-list->string ls)))))
(define (write-identifier id)
(cond
((highlight-c-keyword? id)
(highlight-class 'keyword id out))
((highlight-c-type? id)
(highlight-class 'type id out))
(else
(display id out))))
(define (highlight-line)
(cond
((eqv? #\# (peek-char in))
(highlight-start 'preprocessor out)
(write-char (read-char in) out)
(display (read-whitespace in) out)
(let ((id (read-identifier '())))
(display id out)
(highlight-end 'preprocessor out)
(display (read-whitespace in) out)
(cond
((eq? 'define id)
(highlight-class
'function
(html-escape (read-to-whitespace in '()))
out))
((memq id '(include import))
(highlight-class
'string
(html-escape (read-to-whitespace in '()))
out)))))
((char-c-initial? (peek-char in))
;; line beginning w/ an identifier is probably a
;; function declaration
(let ((id1 (read-identifier '())))
(cond
((eqv? #\: (peek-char in))
(highlight-class 'function id1 out))
(else
(let lp ((decls '())
(id id1))
(let ((space (read-whitespace in)))
(cond
((char-c-initial? (peek-char in))
(lp (cons space (cons id decls))
(read-identifier '())))
((eqv? #\( (peek-char in))
(highlight-start 'type out)
(for-each (lambda (x) (display x out))
(reverse decls))
(highlight-end 'type out)
(highlight-start 'function out)
(display id out)
(highlight-end 'function out)
(display space out))
(else
(for-each write-identifier (reverse decls))
(display id out)
(display space out))))))))))
(highlight))
(define (highlight)
(let ((c (read-char in)))
(if (eof-object? c)
#f
(case c
((#\/)
(case (peek-char in)
((#\/)
(highlight-start 'comment out)
(write-char c out)
(display (read-to-eol '()) out)
(highlight-end 'comment out)
(newline out)
(highlight))
((#\*)
(highlight-start 'comment out)
(write-char c out)
(write-char (read-char in) out)
(let ((prev (read-char in)))
(write-char prev out)
(let lp ((prev prev))
(let ((c (read-char in)))
(write-char c out)
(if (not (and (eqv? prev #\*) (eqv? c #\/)))
(lp c)
#f))))
(highlight-end 'comment out)
(highlight))
(else
(write-char c out)
(highlight))))
((#\" #\')
(let ((str (read-escaped in c (list c))))
(highlight-class 'string str out)
(highlight)))
((#\newline)
(newline out)
(highlight-line))
((#\<)
(display "<" out)
(highlight))
((#\&)
(display "&" out)
(highlight))
(else
(cond
((char-c-initial? c)
(let ((id (read-identifier (list c))))
(if (eqv? #\: (peek-char in))
(highlight-class 'function id out)
(write-identifier id))))
(else
(write-char c out)))
(highlight))))))
(highlight-line)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (highlight-assembly source)
(let ((in (if (string? source) (open-input-string source) source)))
(call-with-output-string
(lambda (out)
(define (read-to-eol ls)
(let ((c (read-char in)))
(cond
((eof-object? c) (reverse-list->string ls))
((eqv? c #\newline) (reverse-list->string (cons c ls)))
(else (read-to-eol (cons c ls))))))
(define (char-asm-initial? c)
(and (char? c) (or (char-alphabetic? c) (memv c '(#\_ #\$ #\.)))))
(define (char-asm-identifier? c)
(and (char? c) (or (char-asm-initial? c) (char-numeric? c))))
(define (read-identifier ls)
(let ((c (peek-char in)))
(if (char-asm-identifier? c)
(read-identifier (cons (read-char in) ls))
(string->symbol (reverse-list->string ls)))))
(define (highlight)
(let ((c (read-char in)))
(cond
((eof-object? c))
(else
(case c
((#\newline)
(write-char c out)
(line))
((#\")
(let ((str (read-escaped in c (list c))))
(highlight-class 'string str out)
(highlight)))
((#\%)
(highlight-class 'variable (read-identifier (list c)) out)
(highlight))
((#\;)
(highlight-class 'comment (read-to-eol (list c)) out)
(highlight))
((#\<)
(display "<" out)
(highlight))
((#\&)
(display "&" out)
(highlight))
(else
(write-char c out)
(highlight)))))))
(define (line)
(cond
((eof-object? (peek-char in)))
((char-asm-initial? (peek-char in))
(let ((id (read-identifier '())))
(if (eqv? #\: (peek-char in))
(highlight-class 'function id out)
(highlight-class 'keyword id out))
(highlight)))
((eqv? #\tab (peek-char in))
(write-char (read-char in) out)
(highlight-class 'keyword (read-identifier '()) out)
(highlight))
(else
(highlight))))
(line)))))