168 lines
5.5 KiB
Racket
168 lines
5.5 KiB
Racket
#lang racket/gui
|
|
(require parser-tools/yacc
|
|
parser-tools/lex
|
|
(prefix-in : parser-tools/lex-sre))
|
|
|
|
;;; Parser
|
|
(define-tokens regular (VARIABLE STRING))
|
|
(define-empty-tokens keywords (STATIC CHAR STAR BRACKET EQUALS LBRACE RBRACE SEMICOLON COMMA EOF))
|
|
|
|
(define lex-xpm
|
|
(lexer
|
|
[(eof) (token-EOF)]
|
|
["static" (token-STATIC)]
|
|
["char" (token-CHAR)]
|
|
["*" (token-STAR)]
|
|
["[]" (token-BRACKET)]
|
|
["=" (token-EQUALS)]
|
|
["{" (token-LBRACE)]
|
|
["}" (token-RBRACE)]
|
|
[";" (token-SEMICOLON)]
|
|
["," (token-COMMA)]
|
|
[whitespace (lex-xpm input-port)]
|
|
[(:: "/*" (complement (:: any-string "*/" any-string)) "*/") (lex-xpm input-port)]
|
|
[(:: #\" (:* (:or (:~ #\") "\\\"")) #\")
|
|
(token-STRING (substring lexeme 1 (- (string-length lexeme) 1)))]
|
|
[(:+ (:or (char-range #\a #\z)
|
|
(char-range #\A #\Z)
|
|
(char-range #\0 #\9)
|
|
#\_
|
|
#\- ; Not really allowed but mini-plt uses it
|
|
))
|
|
(token-VARIABLE lexeme)]))
|
|
|
|
(define parse-raw-xpm
|
|
(parser (start xpm)
|
|
(tokens regular keywords)
|
|
(grammar (xpm [(STATIC CHAR STAR VARIABLE BRACKET EQUALS LBRACE
|
|
strings
|
|
RBRACE)
|
|
(cons $4 $8)])
|
|
(strings [(STRING) (list $1)]
|
|
[(STRING COMMA strings) (list* $1 $3)]))
|
|
(end SEMICOLON)
|
|
(error (lambda (tok-ok? tok-name tok-value)
|
|
(error
|
|
'parse-raw-xpm
|
|
(format
|
|
(if tok-ok?
|
|
"Did not expect token ~a"
|
|
"Invalid token ~a")
|
|
tok-name))))))
|
|
|
|
;;; Struct
|
|
|
|
(struct xpm (var width height color-ht x-hotspot y-hotspot pixels extensions) #:transparent)
|
|
|
|
;;; Reading
|
|
|
|
(define (xpm-read)
|
|
(match-define (cons var strings)
|
|
(parse-raw-xpm (λ () (lex-xpm (current-input-port)))))
|
|
(define-values (width height ncolors cpp x-hotspot y-hotspot extensions?)
|
|
(parse-values (first strings)))
|
|
(define-values (colors-strs pixels*ext-strs)
|
|
(split-at (rest strings) ncolors))
|
|
(define-values (pixels-strs ext-strs)
|
|
(split-at pixels*ext-strs height))
|
|
(xpm var width height
|
|
(parse-colors-ht cpp colors-strs)
|
|
x-hotspot y-hotspot
|
|
(parse-pixels cpp pixels-strs)
|
|
ext-strs))
|
|
|
|
(define parse-values
|
|
(match-lambda
|
|
[(regexp #px"^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)$"
|
|
(list _
|
|
(app string->number w)
|
|
(app string->number h)
|
|
(app string->number ncolors)
|
|
(app string->number cpp)))
|
|
(values w h ncolors cpp #f #f #f)]))
|
|
|
|
(define (in-list* n l)
|
|
(make-do-sequence
|
|
(λ ()
|
|
(values (λ (l)
|
|
(define-values (ret rest) (split-at l n))
|
|
(apply values ret))
|
|
(λ (l)
|
|
(define-values (ret rest) (split-at l n))
|
|
rest)
|
|
l
|
|
(λ (l)
|
|
(not (empty? l)))
|
|
(λ _ #t)
|
|
(λ _ #t)))))
|
|
|
|
(define (split-string-at s n)
|
|
(values (substring s 0 n)
|
|
(substring s n)))
|
|
|
|
(define (parse-colors-ht cpp ss)
|
|
(for/hasheq ([s (in-list ss)])
|
|
(define-values (chars rest*) (split-string-at s cpp))
|
|
(define rest (regexp-replace #px"^\\s+" rest* ""))
|
|
(define ps (regexp-split #px"\\s+" rest))
|
|
(values (string->symbol chars)
|
|
(for/hasheq ([(context color) (in-list* 2 ps)])
|
|
(values (string->symbol context)
|
|
color)))))
|
|
|
|
(define (split-string s n)
|
|
(for/list ([i (in-range 0 (/ (string-length s) n))])
|
|
(substring s (* i n) (* (add1 i) n))))
|
|
|
|
(define (parse-pixels cpp ss)
|
|
(for/list ([row (in-list ss)])
|
|
(for/list ([color (in-list (split-string row cpp))])
|
|
(string->symbol color))))
|
|
|
|
;;; Displaying
|
|
|
|
(define (hex->number n)
|
|
(string->number n 16))
|
|
|
|
(define (color->pen% ht c context)
|
|
(define c-ht (hash-ref ht c (λ () (error 'color->pen% "Unknown color ~e" c))))
|
|
(define code (hash-ref c-ht context (λ () (error 'color->pen% "Unknown context ~e for color ~e" context c))))
|
|
(define style
|
|
(match code
|
|
["None" 'transparent]
|
|
[_ 'solid]))
|
|
(define color
|
|
(match code
|
|
["None" "black"]
|
|
[(regexp #px"^#([\\da-fA-F]{6})" (list _ hex))
|
|
(match-define (list r g b) (split-string hex 2))
|
|
(make-object color% (hex->number r) (hex->number g) (hex->number b))]
|
|
[_
|
|
(error 'color->pen% "Cannot parse ~e" code)]))
|
|
(make-object pen% color 1 style))
|
|
|
|
(define xpm->bitmap%
|
|
(match-lambda
|
|
[(xpm var width height color-ht x-hotspot y-hotspot pixels extensions)
|
|
(define the-bitmap (make-object bitmap% width height))
|
|
(define the-dc (new bitmap-dc% [bitmap the-bitmap]))
|
|
(send the-dc set-background (make-object color% "white"))
|
|
(for ([y (in-naturals)]
|
|
[row (in-list pixels)])
|
|
(for ([x (in-naturals)]
|
|
[color (in-list row)])
|
|
(send the-dc set-pen (color->pen% color-ht color 'c))
|
|
(send the-dc draw-point x y)))
|
|
the-bitmap]))
|
|
|
|
(provide/contract
|
|
[struct xpm ([var string?]
|
|
[width exact-integer?]
|
|
[height exact-integer?]
|
|
[color-ht (hash/c symbol? (hash/c symbol? string?))]
|
|
[x-hotspot (or/c false/c exact-integer?)]
|
|
[y-hotspot (or/c false/c exact-integer?)]
|
|
[pixels (listof (listof symbol?))]
|
|
[extensions (listof string?)])]
|
|
[xpm-read (-> xpm?)]
|
|
[xpm->bitmap% (xpm? . -> . (is-a?/c bitmap%))]) |