diff --git a/lib/chibi/highlight.module b/lib/chibi/highlight.module new file mode 100644 index 00000000..ce7762af --- /dev/null +++ b/lib/chibi/highlight.module @@ -0,0 +1,7 @@ + +(module (chibi highlight) + (export highlight highlight-detect-language highlight-style + highlight-scheme highlight-c highlight-assembly) + (import-immutable (scheme)) + (import (srfi 1) (chibi io) (chibi ast)) + (include "highlight.scm")) diff --git a/lib/chibi/highlight.scm b/lib/chibi/highlight.scm new file mode 100644 index 00000000..afaf7f89 --- /dev/null +++ b/lib/chibi/highlight.scm @@ -0,0 +1,472 @@ +;; 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)))))