Import new table renderer.
This commit is contained in:
		
							parent
							
								
									3a59a9293a
								
							
						
					
					
						commit
						3f7f1356a4
					
				
					 12 changed files with 3859 additions and 1 deletions
				
			
		
							
								
								
									
										72
									
								
								src/Makefile
									
										
									
									
									
								
							
							
						
						
									
										72
									
								
								src/Makefile
									
										
									
									
									
								
							|  | @ -51,7 +51,10 @@ HACKERBASE-OBJS=hackerbase.o testing.o listing.o cal-month.o		\ | |||
| 	 util-string.o util-io.o util-parser.o texts.o tests.o		\
 | ||||
| 	 util-proc.o util-mail.o notifications.o util-format.o		\
 | ||||
| 	 brmember-format.o logging.o specification.o util-git.o		\
 | ||||
| 	 cal-day.o util-stdout.o cal-format.o util-dict-bst.o | ||||
| 	 cal-day.o util-stdout.o cal-format.o util-dict-bst.o table.o	\
 | ||||
| 	 sgr-list.o sgr-block.o table-processor.o table-border.o	\
 | ||||
| 	 table-style.o sgr-state.o util-utf8.o sgr-cell.o		\
 | ||||
| 	 template-list-expander.o box-drawing.o | ||||
| 
 | ||||
| .PHONY: imports | ||||
| imports: $(HACKERBASE-DEPS) | ||||
|  | @ -389,3 +392,70 @@ util-dict-bst.import.scm: $(UTIL-DICT-BST-SOURCES) | |||
| RACKET-KWARGS-SOURCES=racket-kwargs.scm | ||||
| 
 | ||||
| racket-kwargs.import.scm: $(RACKET-KWARGS-SOURCES) | ||||
| 
 | ||||
| TABLE-SOURCES=table.scm sgr-list.import.scm sgr-block.import.scm	\
 | ||||
| 	racket-kwargs.import.scm table-processor.import.scm		\
 | ||||
| 	table-border.import.scm table-style.import.scm | ||||
| 
 | ||||
| table.o: table.import.scm | ||||
| table.import.scm: $(TABLE-SOURCES) | ||||
| 
 | ||||
| SGR-LIST-SOURCES=sgr-list.scm raket-kwargs.import.scm			\
 | ||||
| 	sgr-state.import.scm testing.import.scm util-utf8.import.scm | ||||
| 
 | ||||
| sgr-list.o: sgr-list.import.scm | ||||
| sgr-list.import.scm: $(SGR-LIST-SOURCES) | ||||
| 
 | ||||
| SGR-BLOCK-SOURCES=sgr-block.scm racket-kwargs.import.scm		\
 | ||||
| 	sgr-state.import.scm sgr-list.import.scm testing.import.scm | ||||
| 
 | ||||
| sgr-block.o: sgr-block.import.scm | ||||
| sgr-block.import.scm: $(SGR-BLOCK-SOURCES) | ||||
| 
 | ||||
| TABLE-PROCESSOR-SOURCES=table-processor.scm sgr-cell.import.scm	\
 | ||||
| 	template-list-expander.import.scm | ||||
| 
 | ||||
| table-processor.o: table-processor.import.scm | ||||
| table-processor.import.scm: $(TABLE-PROCESSOR-SOURCES) | ||||
| 
 | ||||
| TABLE-BORDER-SOURCES=table-border.scm racket-kwargs.import.scm	\
 | ||||
| 	box-drawing.import.scm util-utf8.import.scm		\
 | ||||
| 	sgr-block.import.scm | ||||
| 
 | ||||
| table-border.o: table-border.import.scm | ||||
| table-border.import.scm: $(TABLE-BORDER-SOURCES) | ||||
| 
 | ||||
| TABLE-STYLE-SOURCES=table-style.scm box-drawing.import.scm	\
 | ||||
| 	testing.import.scm template-list-expander.import.scm | ||||
| 
 | ||||
| table-style.o: table-style.import.scm | ||||
| table-style.import.scm: $(TABLE-STYLE-SOURCES) | ||||
| 
 | ||||
| SGR-STATE-SOURCES=sgr-state.scm testing.import.scm	\
 | ||||
| 	racket-kwargs.import.scm | ||||
| 
 | ||||
| sgr-state.o: sgr-state.import.scm | ||||
| sgr-state.import.scm: $(SGR-STATE-SOURCES) | ||||
| 
 | ||||
| UTIL-UTF8-SOURCES=util-utf8.scm testing.import.scm | ||||
| 
 | ||||
| util-utf8.o: util-utf8.import.scm | ||||
| util-utf8.import.scm: $(UTIL-UTF8-SOURCES) | ||||
| 
 | ||||
| SGR-CELL-SOURCES=sgr-cell.scm racket-kwargs.import.scm			\
 | ||||
| 	sgr-state.import.scm sgr-list.import.scm sgr-block.import.scm | ||||
| 
 | ||||
| sgr-cell.o: sgr-cell.import.scm | ||||
| sgr-cell.import.scm: $(SGR-CELL-SOURCES) | ||||
| 
 | ||||
| TEMPLATE-LIST-EXPANDER-SOURCES=template-list-expander.scm	\
 | ||||
| 	testing.import.scm | ||||
| 
 | ||||
| template-list-expander.o: template-list-expander.import.scm | ||||
| template-list-expander.import.scm: $(TEMPLATE-LIST-EXPANDER-SOURCES) | ||||
| 
 | ||||
| BOX-DRAWING-SOURCES=box-drawing.scm util-utf8.import.scm	\
 | ||||
| 	racket-kwargs.import.scm testing.import.scm | ||||
| 
 | ||||
| box-drawing.o: box-drawing.import.scm | ||||
| box-drawing.import.scm: $(BOX-DRAWING-SOURCES) | ||||
|  |  | |||
							
								
								
									
										765
									
								
								src/box-drawing.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										765
									
								
								src/box-drawing.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,765 @@ | |||
| ;; | ||||
| ;; box-drawing.scm | ||||
| ;; | ||||
| ;; Unicode box drawing combiners. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit box-drawing)) | ||||
| 
 | ||||
| (module | ||||
|  box-drawing | ||||
|  ( | ||||
|   combine-line-cells | ||||
| 
 | ||||
|   line-cell-somewhat-heavy? | ||||
| 
 | ||||
|   line-cell->unicode-char | ||||
|   line-cell->utf8-string | ||||
| 
 | ||||
|   line-cell->ascii-char | ||||
| 
 | ||||
|   char->line-cell | ||||
| 
 | ||||
|   combine-line-char | ||||
|   | ||||
|   line-style-none | ||||
| 
 | ||||
|   combine-line-style | ||||
| 
 | ||||
|   line-cell-north | ||||
|   line-cell-east | ||||
|   line-cell-west | ||||
|   line-cell-south | ||||
| 
 | ||||
|   spec->line-style | ||||
|   line-style-spec? | ||||
| 
 | ||||
|   make-line-cell | ||||
|   make-straight-horizontal-line-cell | ||||
|   make-straight-vertical-line-cell | ||||
|   make-straight-horizontal-line-cell* | ||||
|   make-straight-vertical-line-cell* | ||||
| 
 | ||||
|   line-cell-none | ||||
| 
 | ||||
|   set-line-cell-north | ||||
|   set-line-cell-west | ||||
|   set-line-cell-east | ||||
|   set-line-cell-south | ||||
| 
 | ||||
|   spec->horizontal-line-cell | ||||
|   spec->vertical-line-cell | ||||
| 
 | ||||
|   spec->top-left-corner-line-cell | ||||
|   spec->top-right-corner-line-cell | ||||
|   spec->bottom-left-corner-line-cell | ||||
|   spec->bottom-right-corner-line-cell | ||||
| 
 | ||||
|   extract-line-cell-top-left | ||||
|   extract-line-cell-top-right | ||||
|   extract-line-cell-bottom-left | ||||
|   extract-line-cell-bottom-right | ||||
| 
 | ||||
|   box-drawing-tests! | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 (chicken bitwise) | ||||
| 	 util-utf8 | ||||
| 	 racket-kwargs | ||||
| 	 testing) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Bit manipulators | ||||
| 
 | ||||
|  (define-syntax define-bit-predicate | ||||
|    (syntax-rules () | ||||
|      ((_ name mask value) | ||||
|       (define (name bits) | ||||
| 	(= (bitwise-and bits mask) value))))) | ||||
| 
 | ||||
|  (define-syntax define-bit-setter | ||||
|    (syntax-rules () | ||||
|      ((_ name andmask ormask) | ||||
|       (define (name bits) | ||||
| 	(bitwise-ior (bitwise-and bits andmask) ormask))))) | ||||
| 
 | ||||
|  (define-syntax define-bit-combiner | ||||
|    (syntax-rules () | ||||
|      ((_ name mask) | ||||
|       (define (name a b) | ||||
| 	(bitwise-ior | ||||
| 	 (bitwise-and a mask) | ||||
| 	 (bitwise-and b mask)))))) | ||||
| 
 | ||||
|  (define-syntax define-bit-accessor | ||||
|    (syntax-rules () | ||||
|      ((_ name shift mask) | ||||
|       (define (name bits) | ||||
| 	(bitwise-and | ||||
| 	 (arithmetic-shift bits (- shift)) mask))))) | ||||
| 
 | ||||
|  (define-syntax define-bit-accessor-setter | ||||
|    (syntax-rules () | ||||
|      ((_ name shift mask) | ||||
|       (define (name bits ormask) | ||||
| 	(bitwise-ior (bitwise-and (bitwise-not (arithmetic-shift mask shift)) | ||||
| 				  bits) | ||||
| 		     (arithmetic-shift (bitwise-and ormask mask) | ||||
| 				       shift)))))) | ||||
| 
 | ||||
|  (define-syntax define-inverted-bit-predicate | ||||
|    (syntax-rules () | ||||
|      ((_ name mask value) | ||||
|       (define (name bits) | ||||
| 	(not (= (bitwise-and bits mask) 0)))))) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Line thickness | ||||
|  ;; * none | ||||
|  ;; * light | ||||
|  ;; * heavy | ||||
|  ;; Bit indices: 10 | ||||
|  ;;              ||_ lower bit: light thickness | ||||
|  ;;              |_ higher bit: heavy thickness | ||||
|  ;; Values: | ||||
|  ;;   00 - none | ||||
|  ;;   01 - light | ||||
|  ;;   10 - heavy | ||||
|  ;;   11 - heavy | ||||
| 
 | ||||
|  (define line-thickness-none 0) | ||||
|  (define line-thickness-light 1) | ||||
|  (define line-thickness-heavy 2) | ||||
| 
 | ||||
|  (define-bit-predicate line-thickness-light? 3 1) | ||||
|  (define-bit-predicate line-thickness-heavy? 2 2) | ||||
|  (define-bit-predicate line-thickness-none? 3 0) | ||||
| 
 | ||||
|  (define-bit-setter set-line-thickness-light 3 1) | ||||
|  (define-bit-setter set-line-thickness-heavy 3 2) | ||||
|  (define-bit-setter set-line-thickness-none 0 0) | ||||
| 
 | ||||
|  (define-bit-combiner combine-line-thickness 3) | ||||
| 
 | ||||
|  (define-inverted-bit-predicate line-thickness-some? 3 0) | ||||
| 
 | ||||
|  (define (line-thickness-normalize thickness) | ||||
|    (if (line-thickness-heavy? thickness) | ||||
|        line-thickness-heavy | ||||
|        thickness)) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Line type | ||||
|  ;; * none | ||||
|  ;; * dashed | ||||
|  ;; * solid | ||||
|  ;; Bit indices: 10 | ||||
|  ;;              ||_ lower bit: dashed type | ||||
|  ;;              |_ higher bit: solid type | ||||
|  ;; Values: | ||||
|  ;;   * 00 - none | ||||
|  ;;   * 01 - dashed | ||||
|  ;;   * 10 - solid | ||||
|  ;;   * 11 - solid | ||||
| 
 | ||||
|  (define line-type-none 0) | ||||
|  (define line-type-dashed 1) | ||||
|  (define line-type-solid 2) | ||||
| 
 | ||||
|  (define-bit-predicate line-type-dashed? 3 1) | ||||
|  (define-bit-predicate line-type-solid? 2 2) | ||||
|  (define-bit-predicate line-type-none? 3 0) | ||||
| 
 | ||||
|  (define-bit-setter set-line-type-dashed 3 1) | ||||
|  (define-bit-setter set-line-type-solid 3 2) | ||||
|  (define-bit-setter set-line-type-none 0 0) | ||||
| 
 | ||||
|  (define-bit-combiner combine-line-type 3) | ||||
| 
 | ||||
|  (define-inverted-bit-predicate line-type-some? 3 0) | ||||
| 
 | ||||
|  (define (line-type-normalize type) | ||||
|    (if (line-type-solid? type) | ||||
|        line-type-solid | ||||
|        type)) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Line style | ||||
|  ;; * thickness | ||||
|  ;; * type | ||||
|  ;; Double-bit indices: 10 | ||||
|  ;;                     ||_ lower nibble: thickness | ||||
|  ;;                     |_ higher nibble: type | ||||
|  ;; Bit indices: 3210 | ||||
|  ;;              ||||_ light thickness | ||||
|  ;;              |||_ heavy thickness | ||||
|  ;;              ||_ dashed type | ||||
|  ;;              |_ solid type | ||||
| 
 | ||||
|  (define line-style-none 0) | ||||
| 
 | ||||
|  ;; Creates line style from specific thickness and type | ||||
|  (define (make-line-style thickness type) | ||||
|    (bitwise-ior | ||||
|     (arithmetic-shift (bitwise-and type 3) 2) | ||||
|     (bitwise-and (bitwise-and thickness 3)))) | ||||
| 
 | ||||
|  (define-bit-accessor line-style-thickness 0 3) | ||||
|  (define-bit-accessor line-style-type 2 3) | ||||
| 
 | ||||
|  (define-bit-predicate line-style-thickness-light? 3 1) | ||||
|  (define-bit-predicate line-style-thickness-heavy? 2 2) | ||||
|  (define-bit-predicate line-style-thickness-none? 3 0) | ||||
|  (define-bit-predicate line-style-type-dashed? 12 4) | ||||
|  (define-bit-predicate line-style-type-solid? 8 8) | ||||
|  (define-bit-predicate line-style-type-none? 12 0) | ||||
| 
 | ||||
|  (define-bit-setter set-line-style-thickness-light 15 1) | ||||
|  (define-bit-setter set-line-style-thickness-heavy 15 2) | ||||
|  (define-bit-setter set-line-style-thickness-none 12 0) | ||||
|  (define-bit-setter set-line-style-type-dashed 15 4) | ||||
|  (define-bit-setter set-line-style-type-solid 15 8) | ||||
|  (define-bit-setter set-line-style-type-none 3 0) | ||||
| 
 | ||||
|  (define-bit-combiner combine-line-style 15) | ||||
| 
 | ||||
|  (define-inverted-bit-predicate line-style-thickness-some? 3 0) | ||||
|  (define-inverted-bit-predicate line-style-type-some? 12 0) | ||||
| 
 | ||||
|  (define (line-style-some? style) | ||||
|    (and (line-style-thickness-some? style) | ||||
| 	(line-style-type-some? style))) | ||||
| 
 | ||||
|  (define (line-style-none? style) | ||||
|    (or (line-style-thickness-none? style) | ||||
|        (line-style-type-none? style))) | ||||
| 
 | ||||
|  (define (line-style-normalize style) | ||||
|    (make-line-style | ||||
|     (line-thickness-normalize (line-style-thickness style)) | ||||
|     (line-type-normalize (line-style-type style)))) | ||||
| 
 | ||||
|  (define (line-style-heavy? style) | ||||
|    (and (line-style-type-some? style) | ||||
| 	(line-style-thickness-heavy? style))) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Line cell | ||||
|  ;; * north | ||||
|  ;; * west | ||||
|  ;; * east | ||||
|  ;; * south | ||||
|  ;; Nibble indices: 3210 | ||||
|  ;;                 ||||_ north style | ||||
|  ;;                 |||_ west style | ||||
|  ;;                 ||_ east style | ||||
|  ;;                 |_ south style | ||||
|  ;; Bit indices: fedcba9876543210 | ||||
|  ;;              ||||||||||||||||_ north light thickness | ||||
|  ;;              |||||||||||||||_ north heavy thickness | ||||
|  ;;              ||||||||||||||_ north dashed type | ||||
|  ;;              |||||||||||||_ north solid type | ||||
|  ;;              ||||||||||||_ west light thickness | ||||
|  ;;              |||||||||||_ west heavy thickness | ||||
|  ;;              ||||||||||_ west dashed type | ||||
|  ;;              |||||||||_ west solid type | ||||
|  ;;              ||||||||_ east light thickness | ||||
|  ;;              |||||||_ east heavy thickness | ||||
|  ;;              ||||||_ east dashed type | ||||
|  ;;              |||||_ east solid type | ||||
|  ;;              ||||_ south light thickness | ||||
|  ;;              |||_ south heavy thickness | ||||
|  ;;              ||_ south dashed type | ||||
|  ;;              |_ south solid type | ||||
| 
 | ||||
|  (define line-cell-none 0) | ||||
| 
 | ||||
|  (define (make-line-cell n w e s) | ||||
|    (bitwise-ior | ||||
|     (bitwise-and n 15) | ||||
|     (arithmetic-shift (bitwise-and w 15) 4) | ||||
|     (arithmetic-shift (bitwise-and e 15) 8) | ||||
|     (arithmetic-shift (bitwise-and s 15) 12))) | ||||
| 
 | ||||
|  (define-bit-accessor line-cell-north 0 15) | ||||
|  (define-bit-accessor line-cell-west 4 15) | ||||
|  (define-bit-accessor line-cell-east 8 15) | ||||
|  (define-bit-accessor line-cell-south 12 15) | ||||
| 
 | ||||
|  (define-bit-accessor-setter set-line-cell-north 0 15) | ||||
|  (define-bit-accessor-setter set-line-cell-west 4 15) | ||||
|  (define-bit-accessor-setter set-line-cell-east 8 15) | ||||
|  (define-bit-accessor-setter set-line-cell-south 12 15) | ||||
| 
 | ||||
|  (define-bit-combiner combine-line-cells 65535) | ||||
| 
 | ||||
|  (define (make-straight-horizontal-line-cell thickness type) | ||||
|    (let ((style (make-line-style thickness type))) | ||||
|      (make-line-cell 0 style style 0))) | ||||
| 
 | ||||
|  (define (make-straight-vertical-line-cell thickness type) | ||||
|    (let ((style (make-line-style thickness type))) | ||||
|      (make-line-cell style 0 0 style))) | ||||
| 
 | ||||
|  (define (make-straight-horizontal-line-cell* style) | ||||
|    (make-line-cell 0 style style 0)) | ||||
| 
 | ||||
|  (define (make-straight-vertical-line-cell* style) | ||||
|    (make-line-cell style 0 0 style)) | ||||
| 
 | ||||
|  (define (make-cross-line-cell thickness type) | ||||
|    (let ((style (make-line-style thickness type))) | ||||
|      (make-line-cell style style style style))) | ||||
| 
 | ||||
|  (define (line-cell-straight-horizontal? cell) | ||||
|    (and (line-style-none? (line-cell-north cell)) | ||||
| 	(line-style-none? (line-cell-south cell)) | ||||
| 	(line-style-some? (line-cell-west cell)) | ||||
| 	(line-style-some? (line-cell-east cell)) | ||||
| 	(= (line-style-normalize (line-cell-west cell)) | ||||
| 	   (line-style-normalize (line-cell-east cell))))) | ||||
| 
 | ||||
|  (define (line-cell-straight-vertical? cell) | ||||
|    (and (line-style-some? (line-cell-north cell)) | ||||
| 	(line-style-none? (line-cell-west cell)) | ||||
| 	(line-style-none? (line-cell-east cell)) | ||||
| 	(line-style-some? (line-cell-south cell)) | ||||
| 	(= (line-style-normalize (line-cell-north cell)) | ||||
| 	   (line-style-normalize (line-cell-south cell))))) | ||||
| 
 | ||||
|  (define (line-cell-mostly-horizontal? cell) | ||||
|    (and (line-style-none? (line-cell-north cell)) | ||||
| 	(line-style-none? (line-cell-south cell)) | ||||
| 	(line-style-some? (line-cell-west cell)) | ||||
| 	(line-style-some? (line-cell-east cell)))) | ||||
| 
 | ||||
|  (define (line-cell-mostly-vertical? cell) | ||||
|    (and (line-style-some? (line-cell-north cell)) | ||||
| 	(line-style-none? (line-cell-west cell)) | ||||
| 	(line-style-none? (line-cell-east cell)) | ||||
| 	(line-style-some? (line-cell-south cell)))) | ||||
| 
 | ||||
|  (define (line-cell-none? cell) | ||||
|    (and (line-style-none? (line-cell-north cell)) | ||||
| 	(line-style-none? (line-cell-west cell)) | ||||
| 	(line-style-none? (line-cell-east cell)) | ||||
| 	(line-style-none? (line-cell-south cell)))) | ||||
| 
 | ||||
|  (define (line-cell-somewhat-heavy? cell) | ||||
|    (or (line-style-heavy? (line-cell-north cell)) | ||||
|        (line-style-heavy? (line-cell-west cell)) | ||||
|        (line-style-heavy? (line-cell-east cell)) | ||||
|        (line-style-heavy? (line-cell-south cell)))) | ||||
| 
 | ||||
|  (define (line-cell-straight-horizontal-type cell) | ||||
|    (line-style-type | ||||
|     (line-cell-west cell))) | ||||
| 
 | ||||
|  (define (line-cell-straight-vertical-type cell) | ||||
|    (line-style-type | ||||
|     (line-cell-north cell))) | ||||
| 
 | ||||
|  (define (line-cell-straight-horizontal-dashed? cell) | ||||
|    (and (line-cell-straight-horizontal? cell) | ||||
| 	(line-type-dashed? | ||||
| 	 (line-cell-straight-horizontal-type cell)))) | ||||
| 
 | ||||
|  (define (line-cell-straight-vertical-dashed? cell) | ||||
|    (and (line-cell-straight-vertical? cell) | ||||
| 	(line-type-dashed? | ||||
| 	 (line-cell-straight-vertical-type cell)))) | ||||
| 
 | ||||
|  (define (line-cell-straight-horizontal-thickness cell) | ||||
|    (line-style-thickness | ||||
|     (line-cell-west cell))) | ||||
| 
 | ||||
|  (define (line-cell-straight-vertical-thickness cell) | ||||
|    (line-style-thickness | ||||
|     (line-cell-north cell))) | ||||
| 
 | ||||
|  (define (line-cell-junction-compress cell) | ||||
|    (let ((n (line-thickness-normalize (line-style-thickness (line-cell-north cell)))) | ||||
| 	 (w (line-thickness-normalize (line-style-thickness (line-cell-west cell)))) | ||||
| 	 (e (line-thickness-normalize (line-style-thickness (line-cell-east cell)))) | ||||
| 	 (s (line-thickness-normalize (line-style-thickness (line-cell-south cell))))) | ||||
|      (+ n | ||||
| 	(* w 3) | ||||
| 	(* e 9) | ||||
| 	(* s 27)))) | ||||
| 
 | ||||
|  (define line-cell-char-junctions | ||||
|    (vector  ;; SEWN (base-3 representation) | ||||
|     #\space ;; 0000 | ||||
|     #\x2575 ;; 0001 | ||||
|     #\x2579 ;; 0002 | ||||
|     #\x2574 ;; 0010 | ||||
|     #\x2518 ;; 0011 | ||||
|     #\x251a ;; 0012 | ||||
|     #\x2578 ;; 0020 | ||||
|     #\x2519 ;; 0021 | ||||
|     #\x251b ;; 0022 | ||||
| 
 | ||||
|     #\x2576 ;; 0100 | ||||
|     #\x2514 ;; 0101 | ||||
|     #\x2516 ;; 0102 | ||||
|     #\x2500 ;; 0110 | ||||
|     #\x2534 ;; 0111 | ||||
|     #\x2538 ;; 0112 | ||||
|     #\x257e ;; 0120 | ||||
|     #\x2535 ;; 0121 | ||||
|     #\x2539 ;; 0122 | ||||
| 
 | ||||
|     #\x257a ;; 0200 | ||||
|     #\x2515 ;; 0201 | ||||
|     #\x2517 ;; 0202 | ||||
|     #\x257c ;; 0210 | ||||
|     #\x2536 ;; 0211 | ||||
|     #\x253a ;; 0212 | ||||
|     #\x2501 ;; 0220 | ||||
|     #\x2537 ;; 0221 | ||||
|     #\x253b ;; 0222 | ||||
| 
 | ||||
|     #\x2577 ;; 1000 | ||||
|     #\x2502 ;; 1001 | ||||
|     #\x257f ;; 1002 | ||||
|     #\x2510 ;; 1010 | ||||
|     #\x2524 ;; 1011 | ||||
|     #\x2526 ;; 1012 | ||||
|     #\x2511 ;; 1020 | ||||
|     #\x2525 ;; 1021 | ||||
|     #\x2529 ;; 1022 | ||||
| 
 | ||||
|     #\x250c ;; 1100 | ||||
|     #\x251c ;; 1101 | ||||
|     #\x251e ;; 1102 | ||||
|     #\x252c ;; 1110 | ||||
|     #\x253c ;; 1111 | ||||
|     #\x2540 ;; 1112 | ||||
|     #\x252d ;; 1120 | ||||
|     #\x253d ;; 1121 | ||||
|     #\x2543 ;; 1122 | ||||
| 
 | ||||
|     #\x250d ;; 1200 | ||||
|     #\x251d ;; 1201 | ||||
|     #\x2521 ;; 1202 | ||||
|     #\x252e ;; 1210 | ||||
|     #\x253e ;; 1211 | ||||
|     #\x2544 ;; 1212 | ||||
|     #\x252f ;; 1220 | ||||
|     #\x253f ;; 1221 | ||||
|     #\x2547 ;; 1222 | ||||
| 
 | ||||
|     #\x257b ;; 2000 | ||||
|     #\x257d ;; 2001 | ||||
|     #\x2503 ;; 2002 | ||||
|     #\x2512 ;; 2010 | ||||
|     #\x2527 ;; 2011 | ||||
|     #\x2528 ;; 2012 | ||||
|     #\x2513 ;; 2020 | ||||
|     #\x252a ;; 2021 | ||||
|     #\x252b ;; 2022 | ||||
| 
 | ||||
|     #\x250e ;; 2100 | ||||
|     #\x251f ;; 2101 | ||||
|     #\x2520 ;; 2102 | ||||
|     #\x2530 ;; 2110 | ||||
|     #\x2541 ;; 2111 | ||||
|     #\x2542 ;; 2112 | ||||
|     #\x2531 ;; 2120 | ||||
|     #\x2545 ;; 2121 | ||||
|     #\x2549 ;; 2122 | ||||
| 
 | ||||
|     #\x250f ;; 2200 | ||||
|     #\x2522 ;; 2201 | ||||
|     #\x2523 ;; 2202 | ||||
|     #\x2532 ;; 2210 | ||||
|     #\x2546 ;; 2211 | ||||
|     #\x254a ;; 2212 | ||||
|     #\x2533 ;; 2220 | ||||
|     #\x2548 ;; 2221 | ||||
|     #\x254b ;; 2222 | ||||
|     )) | ||||
| 
 | ||||
|  (define line-cell-char-horizontal-light-dashed #\x254c) | ||||
|  (define line-cell-char-horizontal-heavy-dashed #\x254d) | ||||
|  (define line-cell-char-vertical-light-dashed #\x254e) | ||||
|  (define line-cell-char-vertical-heavy-dashed #\x254f) | ||||
| 
 | ||||
|  (define (line-cell->unicode-char cell) | ||||
|    (cond ((line-cell-straight-horizontal-dashed? cell) | ||||
| 	  (let ((thickness (line-cell-straight-horizontal-thickness cell))) | ||||
| 	    (if (line-thickness-light? thickness) | ||||
| 		line-cell-char-horizontal-light-dashed | ||||
| 		line-cell-char-horizontal-heavy-dashed))) | ||||
| 	 ((line-cell-straight-vertical-dashed? cell) | ||||
| 	  (let ((thickness (line-cell-straight-vertical-thickness cell))) | ||||
| 	    (if (line-thickness-light? thickness) | ||||
| 		line-cell-char-vertical-light-dashed | ||||
| 		line-cell-char-vertical-heavy-dashed))) | ||||
| 	 (else | ||||
| 	  (let ((compressed (line-cell-junction-compress cell))) | ||||
| 	    (vector-ref line-cell-char-junctions compressed))))) | ||||
| 
 | ||||
|  (define line-cell-string-junctions | ||||
|    (apply | ||||
|     vector | ||||
|     (map utf8-char->string | ||||
| 	 (vector->list line-cell-char-junctions)))) | ||||
| 
 | ||||
|  (define line-cell-string-horizontal-light-dashed | ||||
|    (utf8-char->string line-cell-char-horizontal-light-dashed)) | ||||
|  (define line-cell-string-horizontal-heavy-dashed | ||||
|    (utf8-char->string line-cell-char-horizontal-heavy-dashed)) | ||||
|  (define line-cell-string-vertical-light-dashed | ||||
|    (utf8-char->string line-cell-char-vertical-light-dashed)) | ||||
|  (define line-cell-string-vertical-heavy-dashed | ||||
|    (utf8-char->string line-cell-char-vertical-heavy-dashed)) | ||||
| 
 | ||||
|  (define (line-cell->utf8-string cell) | ||||
|    (cond ((line-cell-straight-horizontal-dashed? cell) | ||||
| 	  (let ((thickness (line-cell-straight-horizontal-thickness cell))) | ||||
| 	    (if (line-thickness-light? thickness) | ||||
| 		line-cell-string-horizontal-light-dashed | ||||
| 		line-cell-string-horizontal-heavy-dashed))) | ||||
| 	 ((line-cell-straight-vertical-dashed? cell) | ||||
| 	  (let ((thickness (line-cell-straight-vertical-thickness cell))) | ||||
| 	    (if (line-thickness-light? thickness) | ||||
| 		line-cell-string-vertical-light-dashed | ||||
| 		line-cell-string-vertical-heavy-dashed))) | ||||
| 	 (else | ||||
| 	  (let ((compressed (line-cell-junction-compress cell))) | ||||
| 	    (vector-ref line-cell-string-junctions compressed))))) | ||||
| 
 | ||||
|  (define (line-cell->ascii-char cell) | ||||
|    (cond ((line-cell-mostly-horizontal? cell) | ||||
| 	  #\-) | ||||
| 	 ((line-cell-mostly-vertical? cell) | ||||
| 	  #\|) | ||||
| 	 ((line-cell-none? cell) | ||||
| 	  #\space) | ||||
| 	 (else | ||||
| 	  #\+))) | ||||
| 
 | ||||
|  (define line-cell-reverse-lookup (make-vector 128 line-cell-none)) | ||||
|  (let loop ((idx 1)) | ||||
|    (when (< idx 81) | ||||
|      (let* ((ch (vector-ref line-cell-char-junctions idx)) | ||||
| 	    (cp (char->integer ch)) | ||||
| 	    (ridx (- cp #x2500)) | ||||
| 	    (n (modulo idx 3)) | ||||
| 	    (w (modulo (quotient idx 3) 3)) | ||||
| 	    (e (modulo (quotient idx 9) 3)) | ||||
| 	    (s (modulo (quotient idx 27) 3))) | ||||
|        (vector-set! line-cell-reverse-lookup | ||||
| 		    ridx | ||||
| 		    (make-line-cell (make-line-style n line-type-solid) | ||||
| 				    (make-line-style w line-type-solid) | ||||
| 				    (make-line-style e line-type-solid) | ||||
| 				    (make-line-style s line-type-solid))) | ||||
|        (loop (add1 idx))))) | ||||
| 
 | ||||
|  (define* (char->line-cell char #:bold (bold? #f)) | ||||
|    (case char | ||||
|      ((#\-) (make-straight-horizontal-line-cell bold? 'solid)) | ||||
|      ((#\|) (make-straight-vertical-line-cell bold? 'solid)) | ||||
|      ((#\+) (make-cross-line-cell bold? 'solid)) | ||||
|      ((#\space) line-cell-none) | ||||
|      (else | ||||
|       (let ((cp (char->integer char))) | ||||
| 	(cond ((or (< cp #x2500) | ||||
| 		   (> cp #x257f)) | ||||
| 	       line-cell-none) | ||||
| 	      (else | ||||
| 	       (vector-ref line-cell-reverse-lookup (- cp #x2500)))))))) | ||||
| 
 | ||||
|  (define (combine-line-char ch1 ch2) | ||||
|    (line-cell->unicode-char | ||||
|     (combine-line-cells | ||||
|      (char->line-cell ch1) | ||||
|      (char->line-cell ch2)))) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Style specifications | ||||
| 
 | ||||
|  (define (spec->line-thickness spec) | ||||
|    (case spec | ||||
|      ((light) line-thickness-light) | ||||
|      ((heavy) line-thickness-heavy) | ||||
|      (else line-thickness-none))) | ||||
| 
 | ||||
|  (define (spec->line-type spec) | ||||
|    (case spec | ||||
|      ((dashed) line-type-dashed) | ||||
|      ((solid) line-type-solid) | ||||
|      (else line-type-none))) | ||||
| 
 | ||||
|  (define (spec->line-style spec) | ||||
|    (cond ((symbol? spec) | ||||
| 	  (case spec | ||||
| 	    ((light heavy) | ||||
| 	     (make-line-style (spec->line-thickness spec) | ||||
| 			      line-type-solid)) | ||||
| 	    ((dashed solid) | ||||
| 	     (make-line-style line-thickness-light | ||||
| 			      (spec->line-type spec))) | ||||
| 	    (else 0))) | ||||
| 	 ((list? spec) | ||||
| 	  (let ((res (foldl | ||||
| 		      (lambda (acc spec1) | ||||
| 			(combine-line-style | ||||
| 			 acc | ||||
| 			 (case spec1 | ||||
| 			   ((light heavy) | ||||
| 			    (make-line-style (spec->line-thickness spec1) | ||||
| 					     line-type-none)) | ||||
| 			   ((dashed solid) | ||||
| 			    (make-line-style line-thickness-none | ||||
| 					     (spec->line-type spec1))) | ||||
| 			   (else line-style-none)))) | ||||
| 		      line-style-none | ||||
| 		      spec))) | ||||
| 	    (cond ((and (line-style-thickness-some? res) | ||||
| 			(line-style-type-none? res)) | ||||
| 		   (set-line-style-type-solid res)) | ||||
| 		  ((and (line-style-thickness-none? res) | ||||
| 			(line-style-type-some? res)) | ||||
| 		   (set-line-style-thickness-light res)) | ||||
| 		  (else | ||||
| 		   res)))) | ||||
| 	 (else line-style-none))) | ||||
| 
 | ||||
|  (define (line-style-spec? spec) | ||||
|    (or (memq spec '(none light heavy dashed solid)) | ||||
|        (and (list? spec) | ||||
| 	    (let loop ((lst spec)) | ||||
| 	      (if (null? lst) | ||||
| 		  #t | ||||
| 		  (if (line-style-spec? (car spec)) | ||||
| 		      (loop (cdr lst)) | ||||
| 		      #f)))))) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Cell makers | ||||
| 
 | ||||
|  (define-syntax define-spec->line-cell | ||||
|    (syntax-rules () | ||||
|      ((_ name spec style a b c d) | ||||
|       (define (name spec) | ||||
| 	(let ((style (spec->line-style spec))) | ||||
| 	  (make-line-cell a b c d)))))) | ||||
| 
 | ||||
|  (define-spec->line-cell spec->horizontal-line-cell spec style 0 style style 0) | ||||
|  (define-spec->line-cell spec->vertical-line-cell spec style style 0 0 style) | ||||
| 
 | ||||
|  (define-spec->line-cell spec->north-west-line-cell spec style style style 0 0) | ||||
|  (define-spec->line-cell spec->north-east-line-cell spec style style 0 style 0) | ||||
|  (define-spec->line-cell spec->south-west-line-cell spec style 0 style 0 style) | ||||
|  (define-spec->line-cell spec->south-east-line-cell spec style 0 0 style style) | ||||
| 
 | ||||
|  (define spec->top-left-corner-line-cell spec->south-east-line-cell) | ||||
|  (define spec->top-right-corner-line-cell spec->south-west-line-cell) | ||||
|  (define spec->bottom-left-corner-line-cell spec->north-east-line-cell) | ||||
|  (define spec->bottom-right-corner-line-cell spec->north-west-line-cell) | ||||
| 
 | ||||
|  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
|  ;; Cell extractors | ||||
| 
 | ||||
|  (define ((make-line-cell-extractor n w e s) c) | ||||
|    (let ((sm (if s 15 0)) | ||||
| 	 (em (if e 240 0)) | ||||
| 	 (wm (if w 3840 0)) | ||||
| 	 (nm (if n 61440 0))) | ||||
|      (bitwise-and c (bitwise-ior sm em wm nm)))) | ||||
| 
 | ||||
|  (define extract-line-cell-north-west | ||||
|    (make-line-cell-extractor #t #t #f #f)) | ||||
| 
 | ||||
|  (define extract-line-cell-north-east | ||||
|    (make-line-cell-extractor #t #f #t #f)) | ||||
| 
 | ||||
|  (define extract-line-cell-south-west | ||||
|    (make-line-cell-extractor #f #t #f #t)) | ||||
| 
 | ||||
|  (define extract-line-cell-south-east | ||||
|    (make-line-cell-extractor #f #f #t #t)) | ||||
| 
 | ||||
|  (define extract-line-cell-top-left extract-line-cell-south-east) | ||||
|  (define extract-line-cell-top-right extract-line-cell-south-west) | ||||
|  (define extract-line-cell-bottom-left extract-line-cell-south-east) | ||||
|  (define extract-line-cell-bottom-right extract-line-cell-south-west) | ||||
| 
 | ||||
|  ;; Self-tests | ||||
|  (define (box-drawing-tests!) | ||||
|    (run-tests | ||||
|     box-drawing | ||||
|     (test-equal? spec->line-thickness | ||||
| 		 (spec->line-thickness 'none) | ||||
| 		 0) | ||||
|     (test-equal? spec->line-thickness | ||||
| 		 (spec->line-thickness 'light) | ||||
| 		 1) | ||||
|     (test-equal? spec->line-thickness | ||||
| 		 (spec->line-thickness 'heavy) | ||||
| 		 2) | ||||
|     (test-equal? spec->line-type | ||||
| 		 (spec->line-type 'none) | ||||
| 		 0) | ||||
|     (test-equal? spec->line-type | ||||
| 		 (spec->line-type 'dashed) | ||||
| 		 1) | ||||
|     (test-equal? spec->line-type | ||||
| 		 (spec->line-type 'solid) | ||||
| 		 2) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style 'solid) ;; light implied | ||||
| 		 #b1001) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style 'dashed) ;; light implied | ||||
| 		 #b101) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style 'light) ;; solid implied | ||||
| 		 #b1001) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style 'heavy) ;; solid implied | ||||
| 		 #b1010) | ||||
|     (test-equal? combine-line-style | ||||
| 		 (combine-line-style #b1001 #b110) | ||||
| 		 #b1111) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style '(solid light)) | ||||
| 		 #b1001) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style '(solid)) ;; light implied | ||||
| 		 #b1001) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style '(dashed)) ;; light implied | ||||
| 		 #b101) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style '(light)) ;; solid implied | ||||
| 		 #b1001) | ||||
|     (test-equal? spec->line-style | ||||
| 		 (spec->line-style '(heavy)) ;; solid implied | ||||
| 		 #b1010) | ||||
|     )) | ||||
|   | ||||
|  ) | ||||
							
								
								
									
										527
									
								
								src/sgr-block.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										527
									
								
								src/sgr-block.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,527 @@ | |||
| ;; | ||||
| ;; sgr-block.scm | ||||
| ;; | ||||
| ;; Represents a block of sgr-list rows. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit sgr-block)) | ||||
| 
 | ||||
| (module | ||||
|  sgr-block | ||||
|  ( | ||||
|   sgr-list->sgr-block | ||||
| 
 | ||||
|   sgr-block->string-list | ||||
| 
 | ||||
|   sgr-block-width | ||||
|   sgr-block-height | ||||
| 
 | ||||
|   sgr-line-render | ||||
|   sgr-block-render | ||||
| 
 | ||||
|   sgr-block-vexpand | ||||
| 
 | ||||
|   sgr-block-happend | ||||
| 
 | ||||
|   sgr-block-tests! | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 racket-kwargs | ||||
| 	 sgr-state | ||||
| 	 sgr-list | ||||
| 	 testing) | ||||
| 
 | ||||
|  ;; Converts sgr-list (possibly containing multiple lines) into a | ||||
|  ;; sgr-block | ||||
|  (define* (sgr-list->sgr-block sl | ||||
| 			       (initial-state empty-sgr-state)) | ||||
|    (let loop ((sl sl) | ||||
| 	      (state initial-state) | ||||
| 	      (row '()) | ||||
| 	      (res '())) | ||||
|      (if (null? sl) | ||||
| 	 (reverse (if (null? row) | ||||
| 		      res | ||||
| 		      (cons (reverse row) res))) | ||||
| 	 (let ((token (car sl))) | ||||
| 	   (cond | ||||
| 	    ((sgr-token-newline? token) | ||||
| 	     (loop (cdr sl) | ||||
| 		   state | ||||
| 		   (list state) | ||||
| 		   (cons (reverse row) res))) | ||||
| 	    ((sgr-state? token) | ||||
| 	     (loop (cdr sl) | ||||
| 		   token | ||||
| 		   (if (or (null? row) | ||||
| 			   (not (sgr-state? (car row)))) | ||||
| 		       (cons token row) | ||||
| 		       (cons token (cdr row))) | ||||
| 		   res)) | ||||
| 	    (else | ||||
| 	     (loop (cdr sl) | ||||
| 		   state | ||||
| 		   (cons token row) | ||||
| 		   res))))))) | ||||
| 
 | ||||
|  ;; Creates a plain list of strings from given block | ||||
|  (define* (sgr-block->string-list sb #:reset-state (reset-state #f)) | ||||
|    (map (lambda (sl) | ||||
| 	  (sgr-list->string sl | ||||
| 			    #:reset-state reset-state)) | ||||
| 	sb)) | ||||
| 
 | ||||
|  ;; Returns the width of the longest SGR line in the block | ||||
|  (define (sgr-block-width sb) | ||||
|    (apply max (map sgr-list-length/stretch sb))) | ||||
| 
 | ||||
|  ;; Returns the number of SGR lines in the block | ||||
|  (define (sgr-block-height sb) | ||||
|    (length sb)) | ||||
| 
 | ||||
|  ;; Creates filler for justification of SGR lines | ||||
|  (define (make-sgr-list-filler n) | ||||
|    (cons (make-string n #\space) n)) | ||||
| 
 | ||||
|  ;; If first or last non-sgr-state token is space, removes it, | ||||
|  ;; converts all spaces to glues. Removes unglue. | ||||
|  (define (sgr-line-preprocess sl justify?) | ||||
|    (let loop ((sl sl) | ||||
| 	      (res '()) | ||||
| 	      (content? #f)) | ||||
|      (if (null? sl) | ||||
| 	 (if (and (not (null? res)) | ||||
| 		  (sgr-token-spaces? (car res))) | ||||
| 	     (reverse (cdr res)) | ||||
| 	     (if (and (not (null? (cdr res))) | ||||
| 		      (sgr-state? (car res)) | ||||
| 		      (sgr-token-spaces? (cadr res))) | ||||
| 		 (reverse (cons (car res) | ||||
| 				(cddr res))) | ||||
| 		 (reverse res))) | ||||
| 	 (let ((t (car sl))) | ||||
| 	   (loop (cdr sl) | ||||
| 		 (if (sgr-token-spaces? t) | ||||
| 		     (if content? | ||||
| 			 (cons (if justify? | ||||
| 				   (cons "\t" 1) | ||||
| 				   t) | ||||
| 			       res) | ||||
| 			 res) | ||||
| 		     (if (sgr-token-unglue? t) | ||||
| 			 res | ||||
| 			 (cons t res))) | ||||
| 		 (or content? | ||||
| 		     (and (not (sgr-token-spaces? t)) | ||||
| 			  (not (sgr-token-unglue? t))))))))) | ||||
| 
 | ||||
|  ;; Splits the remaining evenly prefering first and last | ||||
|  (define (compute-glue-lens num-glues remaining) | ||||
|    (let loop ((num num-glues) | ||||
| 	      (remaining remaining) | ||||
| 	      (res '())) | ||||
|      (if (eq? num 0) | ||||
| 	 (if (<= num 2) | ||||
| 	     (reverse res) | ||||
| 	     (let* ((res0 (reverse res)) | ||||
| 		    (first-glue (car res0)) | ||||
| 		    (rem-glues (cdr res0))) | ||||
| 	       (cons first-glue | ||||
| 		     (reverse rem-glues)))) | ||||
| 	 (let ((len (quotient remaining num))) | ||||
| 	   (loop (sub1 num) | ||||
| 		 (- remaining len) | ||||
| 		 (cons len res)))))) | ||||
| 
 | ||||
|  ;; Expands given SGR line to width by expanding spaces | ||||
|  (define (sgr-line-expand sl width) | ||||
|    (let* ((sll (sgr-list-length-w/o-glues sl)) | ||||
| 	  (rem0 (- width sll))) | ||||
|      (if (>= rem0 0) | ||||
| 	 (let ((num-glues (sgr-list-num-glues sl))) | ||||
| 	   (if (> num-glues 0) | ||||
| 	       (let loop ((gluelens (compute-glue-lens | ||||
| 				     num-glues | ||||
| 				     rem0)) | ||||
| 			  (sl sl) | ||||
| 			  (res '())) | ||||
| 		 (if (null? sl) | ||||
| 		     (reverse res) | ||||
| 		     (let ((tk (car sl))) | ||||
| 		       (if (sgr-token-glue? tk) | ||||
| 			   (loop (cdr gluelens) | ||||
| 				 (cdr sl) | ||||
| 				 (cons (cons (make-string (car gluelens)) | ||||
| 					     (car gluelens)) | ||||
| 				       res)) | ||||
| 			   (loop gluelens | ||||
| 				 (cdr sl) | ||||
| 				 (cons (car sl) res)))))) | ||||
| 	       ;; Nothing to expand, fill-in remainder with spaces | ||||
| 	       (reverse	(cons (cons (make-string rem0) rem0) | ||||
| 			      (reverse sl))))) | ||||
| 	 ;; Nowhere to expand | ||||
| 	 sl))) | ||||
| 
 | ||||
|  ;; Returns justification type: left, right, center, justify | ||||
|  (define (analyze-sgr-line sl) | ||||
|    (if (null? sl) | ||||
|        'left | ||||
|        (let* ((tk0 (if (sgr-state? (car sl)) | ||||
| 		       (if (null? (cdr sl)) | ||||
| 			   #f | ||||
| 			   (cadr sl)) | ||||
| 		       (car sl))) | ||||
| 	      (rsl (reverse sl)) | ||||
| 	      (tkl (if (sgr-state? (car rsl)) | ||||
| 		       (if (null? (cdr rsl)) | ||||
| 			   #f | ||||
| 			   (cadr rsl)) | ||||
| 		       (car rsl)))) | ||||
| 	 (if (not tk0) | ||||
| 	     'left | ||||
| 	     (if (sgr-token-glue? tk0) | ||||
| 		 (if (sgr-token-glue? tkl) | ||||
| 		     'center | ||||
| 		     'right) | ||||
| 		 (if (sgr-token-unglue? tkl) | ||||
| 		     'justify | ||||
| 		     'left)))))) | ||||
| 
 | ||||
|  ;; Extracts initial state | ||||
|  (define (sgr-line-extract-initial-state sl initial-state) | ||||
|    (if (null? sl) | ||||
|        (values sl initial-state) | ||||
|        (if (sgr-state? (car sl)) | ||||
| 	   (values (cdr sl) (car sl)) | ||||
| 	   (if (and (not (null? (cdr sl))) | ||||
| 		    (sgr-state? (cadr sl))) | ||||
| 	       (values (cons (car sl) | ||||
| 			     (cddr sl)) | ||||
| 		       (cadr sl)) | ||||
| 	       (values sl initial-state))))) | ||||
| 
 | ||||
|  ;; Finishes line handling right glue properly | ||||
|  (define (sgr-line-finish sl rightglue?) | ||||
|    (if (null? sl) | ||||
|        (if rightglue? | ||||
| 	   (list (cons "\t" 1)) | ||||
| 	   '()) | ||||
|        (if rightglue? | ||||
| 	   (if (sgr-token-spaces? (car sl)) | ||||
| 	       (reverse (cons (cons "\t" 1) | ||||
| 			      (cdr sl))) | ||||
| 	       (reverse (cons (cons "\t" 1) | ||||
| 			      sl))) | ||||
| 	   (if (sgr-token-spaces? (car sl)) | ||||
| 	       (reverse (cdr sl)) | ||||
| 	       (reverse sl))))) | ||||
| 
 | ||||
|  ;; Returns a list of wrapped sgr-lines and final state | ||||
|  (define (sgr-line-wrap sl-arg width height initial-state-arg leftglue? rightglue? justify?) | ||||
|    (let-values (((sl initial-state) (sgr-line-extract-initial-state sl-arg initial-state-arg))) | ||||
|      (let loop ((sl sl) | ||||
| 		(line (if leftglue? | ||||
| 			  (list (cons "\t" 1) initial-state) | ||||
| 			  (list initial-state))) | ||||
| 		(content? #f) | ||||
| 		(llen 0) | ||||
| 		(res '()) | ||||
| 		(rheight 1) | ||||
| 		(state initial-state)) | ||||
|        (if (or (null? sl) | ||||
| 	       (and height | ||||
| 		    (> rheight height))) | ||||
| 	   (values (if content? | ||||
| 		       (reverse (cons (sgr-line-finish line rightglue?) | ||||
| 				      res)) | ||||
| 		       (reverse res)) | ||||
| 		   state) | ||||
| 	   (let ((tk (car sl))) | ||||
| 	     (if (sgr-state? tk) | ||||
| 		 ;; State change, add and keep | ||||
| 		 (loop (cdr sl) | ||||
| 		       (cons tk line) | ||||
| 		       content? | ||||
| 		       llen | ||||
| 		       res | ||||
| 		       rheight | ||||
| 		       tk) | ||||
| 		 (let ((tklen (cdr tk))) | ||||
| 		   ;; Spaces, texts | ||||
| 		   (if (sgr-token-spaces? tk) | ||||
| 		       ;; Append only after content | ||||
| 		       (if (> (+ llen tklen) width) | ||||
| 			   ;; Spaces force line wrap | ||||
| 			   (loop (cdr sl) | ||||
| 				 (if leftglue? | ||||
| 				     (list (cons "\t" 1) state) | ||||
| 				     (list state)) | ||||
| 				 #f | ||||
| 				 0 | ||||
| 				 (cons (sgr-line-finish line rightglue?) | ||||
| 				       res) | ||||
| 				 (add1 rheight) | ||||
| 				 state) | ||||
| 			   ;; Spaces continue on the same line | ||||
| 			   (loop (cdr sl) | ||||
| 				 (if content? | ||||
| 				     (cons tk line) | ||||
| 				     line) | ||||
| 				 content? | ||||
| 				 (if content? | ||||
| 				     (+ llen tklen) | ||||
| 				     llen) | ||||
| 				 res | ||||
| 				 rheight | ||||
| 				 state)) | ||||
| 		       (if (> (+ llen tklen) width) | ||||
| 			   ;; Wrap word | ||||
| 			   (loop (cdr sl) | ||||
| 				 (if leftglue? | ||||
| 				     (list tk (cons "\t" 1) state) | ||||
| 				     (list tk state)) | ||||
| 				 #t | ||||
| 				 tklen | ||||
| 				 (cons (sgr-line-finish line rightglue?) | ||||
| 				       res) | ||||
| 				 (add1 rheight) | ||||
| 				 state) | ||||
| 			   ;; Keep on going | ||||
| 			   (loop (cdr sl) | ||||
| 				 (cons tk line) | ||||
| 				 #t | ||||
| 				 (+ llen tklen) | ||||
| 				 res | ||||
| 				 rheight | ||||
| 				 state)))))))))) | ||||
| 
 | ||||
|  ;; Neutralizes line like sgr-list-neutralize, but returns final state | ||||
|  ;; as well | ||||
|  (define (sgr-line-neutralize sl initial-state) | ||||
|    (let loop ((sl sl) | ||||
| 	      (res '()) | ||||
| 	      (state initial-state)) | ||||
|      (if (null? sl) | ||||
| 	 (values (reverse res) | ||||
| 		 state) | ||||
| 	 (let ((tk (car sl))) | ||||
| 	   (loop (cdr sl) | ||||
| 		 (cons (sgr-token-neutralize tk) res) | ||||
| 		 (if (sgr-state? tk) | ||||
| 		     tk | ||||
| 		     state)))))) | ||||
| 
 | ||||
|  ;; Renders single SGR line as block. If width is unspecified, only | ||||
|  ;; glues are removed. If height is unspecified, the block can have | ||||
|  ;; any height. With width the line is rendered and glues are expanded | ||||
|  ;; accordingly. With #:justify all spaces are glues and first and | ||||
|  ;; last are removed before rendering. If there are no glues, the | ||||
|  ;; width is set and the result is shorter than the specified width, | ||||
|  ;; the line is simply right-padded with #\space. | ||||
|  (define* (sgr-line-render sl | ||||
| 			   #:width (width #f) | ||||
| 			   #:height (height #f) | ||||
| 			   #:initial-state (initial-state empty-sgr-state)) | ||||
|    (if width | ||||
|        (let* ((alignment (analyze-sgr-line sl)) | ||||
| 	      (sl (sgr-line-preprocess sl (eq? alignment 'justify)))) | ||||
| 	 (let-values (((slw state) | ||||
| 		       (sgr-line-wrap sl | ||||
| 				      width height | ||||
| 				      initial-state | ||||
| 				      (memq alignment '(right center)) | ||||
| 				      (memq alignment '(left center)) | ||||
| 				      (eq? alignment 'justify)))) | ||||
| 	   (values (map (lambda (sl) | ||||
| 			  (let ((sle (sgr-line-expand sl width))) | ||||
| 			    sle)) | ||||
| 			(if (null? slw) | ||||
| 			    (list '()) | ||||
| 			    slw)) | ||||
| 		   state))) | ||||
|        (let-values (((sln) (sgr-list-neutralize sl))) | ||||
| 	 (values sln initial-state)))) | ||||
| 
 | ||||
|  ;; Renders all the lines and appends the resulting blocks | ||||
|  (define* (sgr-block-render sb | ||||
| 			    #:width (width (sgr-block-width sb)) | ||||
| 			    #:height (height #f) | ||||
| 			    #:initial-state (initial-state empty-sgr-state)) | ||||
|    (let loop ((sb sb) | ||||
| 	      (res '()) | ||||
| 	      (total-height 0) | ||||
| 	      (state initial-state)) | ||||
|      (if (or (null? sb) | ||||
| 	     (and height | ||||
| 		  (> total-height height))) | ||||
| 	 (let ((res (if (and height | ||||
| 			     (< total-height height)) | ||||
| 			(let floop ((res0 '()) | ||||
| 				    (idx (if height | ||||
| 					     (- height total-height) | ||||
| 					     0))) | ||||
| 			  (if (= idx 0) | ||||
| 			      (cons res0 res) | ||||
| 			      (floop (cons (sgr-line-expand (list state (cons "\t" 1)) | ||||
| 							    width) | ||||
| 					   res0) | ||||
| 				     (sub1 idx)))) | ||||
| 			res))) | ||||
| 	   (apply append (reverse res))) | ||||
| 	 (let ((sl (car sb))) | ||||
| 	   (let-values (((slb final-state) | ||||
| 			 (sgr-line-render sl | ||||
| 					  #:width width | ||||
| 					  #:height (if height | ||||
| 						       (- height total-height) | ||||
| 						       height) | ||||
| 					  #:initial-state state))) | ||||
| 	     (loop (cdr sb) | ||||
| 		   (cons slb | ||||
| 			 res) | ||||
| 		   (+ total-height | ||||
| 		      (sgr-block-height slb)) | ||||
| 		   final-state | ||||
| 		   )))))) | ||||
| 
 | ||||
|  ;; Expands to given height | ||||
|  (define (sgr-block-vexpand sb h) | ||||
|    (let ((sbh (sgr-block-height sb))) | ||||
|      (if (>= sbh h) | ||||
| 	 sb | ||||
| 	 (let* ((rsb (reverse sb)) | ||||
| 		(sbw (if (null? sb) | ||||
| 			 0 | ||||
| 			 (sgr-list-length (car sb)))) | ||||
| 		(state (if (null? sb) | ||||
| 			   0 | ||||
| 			   (sgr-list-last-state (car rsb)))) | ||||
| 		(filler (list state | ||||
| 			      (cons (make-string sbw) | ||||
| 				    sbw)))) | ||||
| 	   (let loop ((rsb rsb) | ||||
| 		      (rh (- h sbh))) | ||||
| 	     (if (= 0 rh) | ||||
| 		 (reverse rsb) | ||||
| 		 (loop (cons filler rsb) | ||||
| 		       (sub1 rh)))))))) | ||||
| 
 | ||||
|  ;; Appends blocks horizontally | ||||
|  (define (sgr-block-happend b0 . rest) | ||||
|    (if (null? rest) | ||||
|        b0 | ||||
|        (let loop ((b0 b0) | ||||
| 		  (b1 (car rest)) | ||||
| 		  (rres '())) | ||||
| 	 (if (null? b0) | ||||
| 	     (apply sgr-block-happend | ||||
| 		    (reverse rres) | ||||
| 		    (cdr rest)) | ||||
| 	     (loop (cdr b0) | ||||
| 		   (cdr b1) | ||||
| 		   (cons (append (car b0) | ||||
| 				 (car b1)) | ||||
| 			 rres)))))) | ||||
| 
 | ||||
|  ;; Module self-tests | ||||
|  (define (sgr-block-tests!) | ||||
|    (run-tests | ||||
|     sgr-block | ||||
|     (test-equal? sgr-list->sgr-block | ||||
| 		 (sgr-list->sgr-block '(("Hello" . 5))) | ||||
| 		 '((("Hello" . 5)))) | ||||
|     (test-equal? sgr-list->sgr-block | ||||
| 		 (sgr-list->sgr-block '(("Hello" . 5) | ||||
| 					("\n" . 1) | ||||
| 					("World" . 5))) | ||||
| 		 '((("Hello" . 5)) | ||||
| 		   (0 ("World" . 5)))) | ||||
|     (test-equal? sgr-list->sgr-block | ||||
| 		 (sgr-list->sgr-block '(1 | ||||
| 					("Hello" . 5) | ||||
| 					("\n" . 1) | ||||
| 					0 | ||||
| 					("World" . 5))) | ||||
| 		 '((1 ("Hello" . 5)) | ||||
| 		   (0 ("World" . 5)))) | ||||
|     (test-equal? sgr-block->string-list | ||||
| 		 (sgr-block->string-list '((("Hello" . 5)) | ||||
| 					   (1 ("World" . 5)))) | ||||
| 		 '("Hello" | ||||
| 		   "\x1b[1mWorld")) | ||||
|     (test-equal? sgr-block-width | ||||
| 		 (sgr-block-width '((("Hello" . 5)) | ||||
| 				    (("Scheme" . 6) | ||||
| 				     (" " . 1) | ||||
| 				     ("World!" . 6)))) | ||||
| 		 13) | ||||
|     (test-equal? sgr-line-preprocess | ||||
| 		 (sgr-line-preprocess '(("Hello" . 5) | ||||
| 					 1 | ||||
| 					 ("   " . 3) | ||||
| 					 0 | ||||
| 					 ("World" . 5)) | ||||
| 				      #t) | ||||
| 		 '(("Hello" . 5) | ||||
| 		   1 | ||||
| 		   ("\t" . 1) | ||||
| 		   0 | ||||
| 		   ("World" . 5))) | ||||
|     (test-equal? sgr-line-preprocess | ||||
| 		 (sgr-line-preprocess '(("  " . 2) | ||||
| 					 ("Hello" . 5) | ||||
| 					 1 | ||||
| 					 ("   " . 3) | ||||
| 					 0 | ||||
| 					 ("World" . 5) | ||||
| 					 ("  " . 2)) | ||||
| 				      #t) | ||||
| 		 '(("Hello" . 5) | ||||
| 		   1 | ||||
| 		   ("\t" . 1) | ||||
| 		   0 | ||||
| 		   ("World" . 5))) | ||||
|     (test-equal? compute-glue-lens | ||||
| 		 (compute-glue-lens 1 10) | ||||
| 		 '(10)) | ||||
|     (test-equal? compute-glue-lens | ||||
| 		 (compute-glue-lens 2 10) | ||||
| 		 '(5 5)) | ||||
|     (test-equal? compute-glue-lens | ||||
| 		 (compute-glue-lens 2 11) | ||||
| 		 '(5 6)) | ||||
|     (test-equal? compute-glue-lens | ||||
| 		 (compute-glue-lens 3 11) | ||||
| 		 '(3 4 4)) | ||||
|     (test-equal? compute-glue-lens | ||||
| 		 (compute-glue-lens 3 14) | ||||
| 		 '(4 5 5)) | ||||
|     (test-equal? sgr-line-expand | ||||
| 		 (sgr-line-expand | ||||
| 		  '(("Hello" . 5) ("\t" . 1) ("World!" . 6)) | ||||
| 		  20) | ||||
| 		 '(("Hello" . 5) ("         " . 9) ("World!" . 6))) | ||||
|     )) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										67
									
								
								src/sgr-cell.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/sgr-cell.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| ;; | ||||
| ;; sgr-cell.scm | ||||
| ;; | ||||
| ;; Surface API for handling strings with SGR sequences as table cells. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit sgr-cell)) | ||||
| 
 | ||||
| (module | ||||
|  sgr-cell | ||||
|  ( | ||||
|   string->sgr-cell | ||||
| 
 | ||||
|   sgr-cell-width | ||||
|   sgr-cell-height | ||||
| 
 | ||||
|   sgr-cell-min-width | ||||
| 
 | ||||
|   sgr-cell-render | ||||
| 
 | ||||
|   sgr-cell-vexpand | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 racket-kwargs | ||||
| 	 sgr-state | ||||
| 	 sgr-list | ||||
| 	 sgr-block) | ||||
| 
 | ||||
|  (define* (string->sgr-cell str (initial-state empty-sgr-state)) | ||||
|    (let ((cell0 (sgr-list->sgr-block | ||||
| 		 (string->sgr-list/words str initial-state) | ||||
| 		 initial-state))) | ||||
|      (if (null? cell0) | ||||
| 	 (list (list initial-state)) | ||||
| 	 cell0))) | ||||
| 
 | ||||
|  (define sgr-cell-width sgr-block-width) | ||||
|  (define sgr-cell-height sgr-block-height) | ||||
| 
 | ||||
|  (define (sgr-cell-min-width sc) | ||||
|    (apply max (cons 0 (map sgr-list-min-width sc)))) | ||||
| 
 | ||||
|  (define (sgr-cell-render . args) | ||||
|    (apply sgr-block-render args)) | ||||
| 
 | ||||
|  (define sgr-cell-vexpand sgr-block-vexpand) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										485
									
								
								src/sgr-list.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										485
									
								
								src/sgr-list.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,485 @@ | |||
| ;; | ||||
| ;; sgr-list.scm | ||||
| ;; | ||||
| ;; Intermediate representation of strings with SGR state changes. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit sgr-list)) | ||||
| 
 | ||||
| (module | ||||
|  sgr-list | ||||
|  ( | ||||
|   string->sgr-list | ||||
|   string->sgr-list/words | ||||
| 
 | ||||
|   sgr-list->string | ||||
| 
 | ||||
|   sgr-list-length | ||||
|   sgr-list-length/stretch | ||||
|   sgr-list-length-w/o-glues | ||||
| 
 | ||||
|   sgr-token-spaces? | ||||
|   sgr-token-glue? | ||||
|   sgr-token-newline? | ||||
|   sgr-token-unglue? | ||||
| 
 | ||||
|   sgr-token-neutralize | ||||
|   sgr-list-neutralize | ||||
| 
 | ||||
|   sgr-list-num-glues | ||||
| 
 | ||||
|   sgr-list-min-width | ||||
| 
 | ||||
|   sgr-list-last-state | ||||
| 
 | ||||
|   sgr-list-tests! | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 (chicken string) | ||||
| 	 racket-kwargs | ||||
| 	 sgr-state | ||||
| 	 testing | ||||
| 	 util-utf8) | ||||
| 
 | ||||
|  ;; Converts given string into a list of string parts and sgr-states | ||||
|  (define* (string->sgr-list str | ||||
| 			    (initial-state empty-sgr-state)) | ||||
|    (let ((src-len (string-length str))) | ||||
|      (let loop ((src-idx 0) | ||||
| 		(token-start 0) | ||||
| 		(token-end 0) | ||||
| 		(token-pos 0) | ||||
| 		(state initial-state) | ||||
| 		(res '())) | ||||
|        (if (>= src-idx src-len) | ||||
| 	   (reverse | ||||
| 	    (if (= token-start token-end) | ||||
| 		res | ||||
| 		(cons (cons (substring str token-start token-end) | ||||
| 			    token-pos) | ||||
| 		      res))) | ||||
| 	   (let ((ch (string-ref str src-idx))) | ||||
| 	     (cond | ||||
| 	      ((eq? ch #\x1b) | ||||
| 	       (let-values (((sgr-parsed next-idx) | ||||
| 			     (parse-csi-sgr-sequence+pos str src-idx state))) | ||||
| 		 (let ((token (update-sgr-state-from-list state sgr-parsed))) | ||||
| 		   (loop next-idx | ||||
| 			 next-idx | ||||
| 			 next-idx | ||||
| 			 0 | ||||
| 			 token | ||||
| 			 (cons token | ||||
| 			       (if (= token-start token-end) | ||||
| 				   res | ||||
| 				   (cons (cons (substring str token-start token-end) | ||||
| 					       token-pos) | ||||
| 					 res))))))) | ||||
| 	      (else | ||||
| 	       (let ((next-idx (utf8-string-next-char str src-idx))) | ||||
| 		 (loop next-idx | ||||
| 		       token-start | ||||
| 		       next-idx | ||||
| 		       (add1 token-pos) | ||||
| 		       state | ||||
| 		       res))))))))) | ||||
| 
 | ||||
|  ;; Converts given string into a list of string parts and sgr-states, | ||||
|  ;; handling continuous whitespace as separate tokens. | ||||
|  (define* (string->sgr-list/words str | ||||
| 				  (initial-state empty-sgr-state)) | ||||
|    (let ((src-len (string-length str))) | ||||
|      (let loop ((src-idx 0) | ||||
| 		(token-start 0) | ||||
| 		(token-end 0) | ||||
| 		(token-pos 0) | ||||
| 		(state initial-state) | ||||
| 		(space? #f) | ||||
| 		(res '())) | ||||
|        (if (>= src-idx src-len) | ||||
| 	   (reverse | ||||
| 	    (if (= token-start token-end) | ||||
| 		res | ||||
| 		(cons (cons (substring str token-start token-end) | ||||
| 			    token-pos) | ||||
| 		      res))) | ||||
| 	   (let ((ch (string-ref str src-idx))) | ||||
| 	     (cond | ||||
| 	      ((eq? ch #\x1b) | ||||
| 	       (let-values (((sgr-parsed next-idx) | ||||
| 			     (parse-csi-sgr-sequence+pos str src-idx state))) | ||||
| 		 (let ((token (update-sgr-state-from-list state sgr-parsed))) | ||||
| 		   (loop next-idx | ||||
| 			 next-idx | ||||
| 			 next-idx | ||||
| 			 0 | ||||
| 			 token | ||||
| 			 #f | ||||
| 			 (cons token | ||||
| 			       (if (= token-start token-end) | ||||
| 				   res | ||||
| 				   (cons (cons (substring str token-start token-end) | ||||
| 					       token-pos) | ||||
| 					 res))))))) | ||||
| 	      ((eq? ch #\backspace) | ||||
| 	       (let ((next-idx (add1 src-idx))) | ||||
| 		 (loop next-idx | ||||
| 		       next-idx | ||||
| 		       next-idx | ||||
| 		       0 | ||||
| 		       state | ||||
| 		       #f | ||||
| 		       (cons (cons "\b" 1) | ||||
| 			     (if (= token-start token-end) | ||||
| 				 res | ||||
| 				 (cons (cons (substring str token-start token-end) | ||||
| 					     token-pos) | ||||
| 				       res)))))) | ||||
| 	      ((eq? ch #\newline) | ||||
| 	       (let ((next-idx (add1 src-idx))) | ||||
| 		 (loop next-idx | ||||
| 		       next-idx | ||||
| 		       next-idx | ||||
| 		       0 | ||||
| 		       state | ||||
| 		       #f | ||||
| 		       (cons (cons "\n" 1) | ||||
| 			     (if (= token-start token-end) | ||||
| 				 res | ||||
| 				 (cons (cons (substring str token-start token-end) | ||||
| 					     token-pos) | ||||
| 				       res)))))) | ||||
| 	      ((memq ch '(#\space #\tab)) | ||||
| 	       (let ((next-idx (add1 src-idx))) | ||||
| 		 (loop next-idx | ||||
| 		       (if space? | ||||
| 			   token-start | ||||
| 			   src-idx) | ||||
| 		       next-idx | ||||
| 		       (if space? | ||||
| 			   (add1 token-pos) | ||||
| 			   1) | ||||
| 		       state | ||||
| 		       #t | ||||
| 		       (if (and (not space?) | ||||
| 				(> token-end token-start)) | ||||
| 			   (cons (cons (substring str token-start token-end) | ||||
| 				       token-pos) | ||||
| 				 res) | ||||
| 			   res)))) | ||||
| 	      (else | ||||
| 	       (let ((next-idx (utf8-string-next-char str src-idx))) | ||||
| 		 (loop next-idx | ||||
| 		       (if space? | ||||
| 			   src-idx | ||||
| 			   token-start) | ||||
| 		       next-idx | ||||
| 		       (if space? | ||||
| 			   1 | ||||
| 			   (add1 token-pos)) | ||||
| 		       state | ||||
| 		       #f | ||||
| 		       (if space? | ||||
| 			   (cons (cons (substring str token-start token-end) | ||||
| 				       token-pos) | ||||
| 				 res) | ||||
| 			   res)))))))))) | ||||
| 
 | ||||
|  ;; Converts a SGR list into a single string | ||||
|  (define* (sgr-list->string lst | ||||
| 			    (initial-state empty-sgr-state) | ||||
| 			    #:reset-state (reset-state #f)) | ||||
|    (let loop ((lst lst) | ||||
| 	      (res '()) | ||||
| 	      (state initial-state)) | ||||
|      (if (null? lst) | ||||
| 	 (string-intersperse | ||||
| 	  (reverse (if reset-state | ||||
| 		       (cons reset-state res) | ||||
| 		       res)) | ||||
| 	  "") | ||||
| 	 (let ((token (car lst))) | ||||
| 	   (if (sgr-state? token) | ||||
| 	       (loop (cdr lst) | ||||
| 		     (cons (sgr-state-change->string state token) res) | ||||
| 		     token) | ||||
| 	       (loop (cdr lst) | ||||
| 		     (cons (car token) res) | ||||
| 		     state)))))) | ||||
| 
 | ||||
|  ;; Predicate for SGR list tokens | ||||
|  (define (sgr-list-token? v) | ||||
|    (and (pair? v) | ||||
| 	(string? (car v)) | ||||
| 	(fixnum? (cdr v)))) | ||||
| 
 | ||||
|  ;; Returns the length of all utf8 strings in the sgr-list | ||||
|  (define (sgr-list-length sl) | ||||
|    (foldl | ||||
|     (lambda (acc tk) | ||||
|       (if (sgr-list-token? tk) | ||||
| 	  (+ acc (cdr tk)) | ||||
| 	  acc)) | ||||
|     0 sl)) | ||||
| 
 | ||||
|  ;; Returns the length of all utf8 strings without glues. Glues must | ||||
|  ;; have at least width 1 if they are not the first or last | ||||
|  ;; token. Those can have zero length. All spaces have width of | ||||
|  ;; 1. Used for wrapping blocks. | ||||
|  (define (sgr-list-length/stretch sl) | ||||
|    (let loop ((sl sl) | ||||
| 	      (len 0) | ||||
| 	      (seen-string? #f) | ||||
| 	      (last-space? #f)) | ||||
|      (if (null? sl) | ||||
| 	 (if last-space? | ||||
| 	     (sub1 len) | ||||
| 	     len) | ||||
| 	 (let* ((tk (car sl)) | ||||
| 		(is-spaces? (sgr-token-spaces? tk)) | ||||
| 		(is-string? (and (not (sgr-state? tk)) | ||||
| 				 (not is-spaces?)))) | ||||
| 	   (loop (cdr sl) | ||||
| 		 (cond ((sgr-token-spaces? tk) | ||||
| 			(if seen-string? | ||||
| 			    (add1 len) | ||||
| 			    len)) | ||||
| 		       ((sgr-state? tk) | ||||
| 			len) | ||||
| 		       (else | ||||
| 			(+ len (cdr tk)))) | ||||
| 		 (or seen-string? | ||||
| 		     is-string?) | ||||
| 		 (if is-string? | ||||
| 		     #f | ||||
| 		     (if is-spaces? | ||||
| 			 #t | ||||
| 			 last-space?))))))) | ||||
| 
 | ||||
|  ;; Returns length without glues | ||||
|  (define (sgr-list-length-w/o-glues sl) | ||||
|    (foldl (lambda (acc tk) | ||||
| 	    (if (or (sgr-state? tk) | ||||
| 		    (sgr-token-glue? tk) | ||||
| 		    (sgr-token-unglue? tk)) | ||||
| 		acc | ||||
| 		(+ acc (cdr tk)))) | ||||
| 	  0 sl)) | ||||
| 		 | ||||
| 
 | ||||
|  ;; Predicate for string token containing only spaces and tabs | ||||
|  (define (sgr-token-spaces? t) | ||||
|    (and (pair? t) | ||||
| 	(string? (car t)) | ||||
| 	(let loop ((i 0)) | ||||
| 	  (if (= i (cdr t)) | ||||
| 	      #t | ||||
| 	      (if (memq (string-ref (car t) i) '(#\space #\tab)) | ||||
| 		  (loop (add1 i)) | ||||
| 		  #f))))) | ||||
| 
 | ||||
|  ;; Predicate for string token with only spaces and at least one tab | ||||
|  (define (sgr-token-glue? t) | ||||
|    (and (pair? t) | ||||
| 	(string? (car t)) | ||||
| 	(let loop ((i 0) | ||||
| 		   (glue #f)) | ||||
| 	  (if (= i (cdr t)) | ||||
| 	      glue | ||||
| 	      (let ((ch (string-ref (car t) i))) | ||||
| 		(if (eq? ch #\tab) | ||||
| 		    (loop (add1 i) #t) | ||||
| 		    (if (eq? ch #\space) | ||||
| 			(loop (add1 i) glue) | ||||
| 			#f))))))) | ||||
| 
 | ||||
|  ;; Predicate for string containing only a newline | ||||
|  (define (sgr-token-newline? t) | ||||
|    (and (pair? t) | ||||
| 	(string? (car t)) | ||||
| 	(eq? (cdr t) 1) | ||||
| 	(eq? (string-ref (car t) 0) #\newline))) | ||||
| 
 | ||||
|  ;; Predicate for unglue (used for justify) | ||||
|  (define (sgr-token-unglue? t) | ||||
|    (and (pair? t) | ||||
| 	(string? (car t)) | ||||
| 	(eq? (cdr t) 1) | ||||
| 	(eq? (string-ref (car t) 0) #\backspace))) | ||||
| 
 | ||||
|  ;; Replaces all occurences of #\tab with #\space and removes trailing | ||||
|  ;; #\backspace | ||||
|  (define (sgr-token-neutralize t) | ||||
|    (if (and (pair? t) | ||||
| 	    (string? (car t))) | ||||
|        (let* ((str (string-copy (car t))) | ||||
| 	      (len (string-length str))) | ||||
| 	 (if (> len 0) | ||||
| 	     (let loop ((idx 0)) | ||||
| 	       (if (= idx len) | ||||
| 		   (if (eq? (string-ref str (sub1 len)) #\backspace) | ||||
| 		       (cons (substring str 0 (sub1 len)) | ||||
| 			     (sub1 len)) | ||||
| 		       (cons str len)) | ||||
| 		   (let ((ch (string-ref str idx))) | ||||
| 		     (when (eq? ch #\tab) | ||||
| 		       (string-set! str idx #\space)) | ||||
| 		     (loop (add1 idx))))) | ||||
| 	     t)))) | ||||
| 
 | ||||
|  ;; Neutralizes whole SGR list | ||||
|  (define (sgr-list-neutralize sl) | ||||
|    (map sgr-token-neutralize sl)) | ||||
| 
 | ||||
|  ;; Returns the number of glues in given SGR list | ||||
|  (define (sgr-list-num-glues sl) | ||||
|    (foldl (lambda (acc t) | ||||
| 	    (if (sgr-token-glue? t) | ||||
| 		(add1 acc) | ||||
| 		acc)) | ||||
| 	  0 sl)) | ||||
| 
 | ||||
|  ;; Returns the longest word | ||||
|  (define (sgr-list-min-width sl) | ||||
|    (let loop ((sl sl) | ||||
| 	      (res 0)) | ||||
|      (if (null? sl) | ||||
| 	 res | ||||
| 	 (let ((tk (car sl))) | ||||
| 	   (loop (cdr sl) | ||||
| 		 (if (sgr-state? tk) | ||||
| 		     res | ||||
| 		     (max res (cdr tk)))))))) | ||||
| 
 | ||||
|  ;; Returns the last SGR state in given sgr-list | ||||
|  (define* (sgr-list-last-state sl (initial-state empty-sgr-state)) | ||||
|    (let loop ((sl sl) | ||||
| 	      (state initial-state)) | ||||
|      (if (null? sl) | ||||
| 	 state | ||||
| 	 (loop (cdr sl) | ||||
| 	       (if (sgr-state? (car sl)) | ||||
| 		   (car sl) | ||||
| 		   state))))) | ||||
| 
 | ||||
|  ;; Module self-tests | ||||
|  (define (sgr-list-tests!) | ||||
|    (run-tests | ||||
|     sgr-list | ||||
|     (test-equal? sgr-list->string | ||||
| 		 (sgr-list->string '(("Hello" . 5))) | ||||
| 		 "Hello") | ||||
|     (test-true sgr-list-token? | ||||
| 	       (sgr-list-token? '("Hello" . 5))) | ||||
|     (test-false sgr-list-token? | ||||
| 		(sgr-list-token? empty-sgr-state)) | ||||
|     (test-false sgr-list-token? | ||||
| 		(sgr-list-token? (set-sgr-state-foreground | ||||
| 				  empty-sgr-state | ||||
| 				  (make-sgr-truecolor 1 2 3)))) | ||||
|     (test-equal? sgr-list-length | ||||
| 		 (sgr-list-length '(("Hello" . 5))) | ||||
| 		 5) | ||||
|     (test-true sgr-token-spaces? | ||||
| 	       (sgr-token-spaces? '("     " . 5))) | ||||
|     (test-true sgr-token-spaces? | ||||
| 	       (sgr-token-spaces? '("  \x09  " . 5))) | ||||
|     (test-false sgr-token-spaces? | ||||
| 		(sgr-token-spaces? '("  x  " . 5))) | ||||
|     (test-true sgr-token-newline? | ||||
| 	       (sgr-token-newline? '("\n" . 1))) | ||||
|     (test-false sgr-token-newline? | ||||
| 		(sgr-token-newline? '("\na" . 2))) | ||||
|     (test-false sgr-token-newline? | ||||
| 		(sgr-token-newline? '("x" . 1))) | ||||
|     (test-true sgr-token-glue? | ||||
| 	       (sgr-token-glue? '("  \t  " . 5))) | ||||
|     (test-true sgr-token-glue? | ||||
| 	       (sgr-token-glue? '("\t" . 1))) | ||||
|     (test-false sgr-token-glue? | ||||
| 		(sgr-token-glue? '("     " . 5))) | ||||
|     (test-false sgr-token-glue? | ||||
| 		(sgr-token-glue? '("  x  " . 5))) | ||||
|     (test-equal? string->sgr-list | ||||
| 		 (string->sgr-list "Hello") | ||||
| 		 '(("Hello" . 5))) | ||||
|     (test-equal? string->sgr-list | ||||
| 		 (string->sgr-list "\x1b[1mHello \x1b[0mWorld!") | ||||
| 		 '(1 ("Hello " . 6) 0 ("World!" . 6))) | ||||
|     (test-equal? string->sgr-list/words | ||||
| 		 (string->sgr-list/words "Hello World!") | ||||
| 		 '(("Hello" . 5) | ||||
| 		   (" " . 1) | ||||
| 		   ("World!" . 6))) | ||||
|     (test-equal? string->sgr-list/words | ||||
| 		 (string->sgr-list/words "\x1b[1mHello \x1b[0mWorld!") | ||||
| 		 '(1 | ||||
| 		   ("Hello" . 5) | ||||
| 		   (" " . 1) | ||||
| 		   0 | ||||
| 		   ("World!" . 6))) | ||||
|     (test-equal? string->sgr-list/words | ||||
| 		 (string->sgr-list/words "Hello World!\b") | ||||
| 		 '(("Hello" . 5) | ||||
| 		   (" " . 1) | ||||
| 		   ("World!" . 6) | ||||
| 		   ("\b" . 1))) | ||||
|     (test-equal? string->sgr-list/words | ||||
| 		 (string->sgr-list/words "Hello\nWorld!") | ||||
| 		 '(("Hello" . 5) | ||||
| 		   ("\n" . 1) | ||||
| 		   ("World!" . 6))) | ||||
|     (test-equal? sgr-list-length/stretch | ||||
| 		 (sgr-list-length/stretch | ||||
| 		  '(("Hello" . 5))) | ||||
| 		 5) | ||||
|     (test-equal? sgr-list-length/stretch | ||||
| 		 (sgr-list-length/stretch | ||||
| 		  '(("Hello" . 5) ("    " . 4))) | ||||
| 		 5) | ||||
|     (test-equal? sgr-list-length/stretch | ||||
| 		 (sgr-list-length/stretch | ||||
| 		  '(("Hello" . 5) ("    " . 4) 1)) | ||||
| 		 5) | ||||
|     (test-equal? sgr-list-length/stretch | ||||
| 		 (sgr-list-length/stretch | ||||
| 		  '(1 ("    " . 4) ("Hello" . 5) ("    " . 4) 1)) | ||||
| 		 5) | ||||
|     (test-equal? sgr-list-length/stretch | ||||
| 		 (sgr-list-length/stretch | ||||
| 		  '(("Hello" . 5) ("    " . 4) ("World" . 5))) | ||||
| 		 11) | ||||
|     (test-equal? sgr-list-length-w/o-glues | ||||
| 		 (sgr-list-length-w/o-glues | ||||
| 		  '(("Hello" . 5) ("  \t  " . 5) ("World" . 5))) | ||||
| 		 10) | ||||
|     (test-true sgr-token-unglue? | ||||
| 	       (sgr-token-unglue? '("\b" . 1))) | ||||
|     (test-false sgr-token-unglue? | ||||
| 		(sgr-token-unglue? '(" \b" . 2))) | ||||
|     (test-equal? sgr-token-neutralize | ||||
| 		 (sgr-token-neutralize '("Hello\b" . 6)) | ||||
| 		 '("Hello" . 5)) | ||||
|     )) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										756
									
								
								src/sgr-state.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										756
									
								
								src/sgr-state.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,756 @@ | |||
| ;; | ||||
| ;; sgr-state.scm | ||||
| ;; | ||||
| ;; ECMA-48 Set Graphics Rendition state management. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit sgr-state)) | ||||
| 
 | ||||
| (module | ||||
|  sgr-state | ||||
|  ( | ||||
|   sgr-color-valid? | ||||
|   sgr-truecolor? | ||||
|   make-sgr-truecolor | ||||
|   split-sgr-truecolor | ||||
| 
 | ||||
|   make-sgr-state | ||||
|   empty-sgr-state | ||||
| 
 | ||||
|   sgr-state? | ||||
| 
 | ||||
|   sgr-state-intensity | ||||
|   set-sgr-state-intensity | ||||
| 
 | ||||
|   sgr-state-italic | ||||
|   set-sgr-state-italic | ||||
| 
 | ||||
|   sgr-state-underline | ||||
|   set-sgr-state-underline | ||||
| 
 | ||||
|   sgr-state-blink | ||||
|   set-sgr-state-blink | ||||
| 
 | ||||
|   sgr-state-reverse | ||||
|   set-sgr-state-reverse | ||||
| 
 | ||||
|   sgr-state-crossed | ||||
|   set-sgr-state-crossed | ||||
| 
 | ||||
|   sgr-state-foreground | ||||
|   sgr-state-background | ||||
| 
 | ||||
|   set-sgr-state-foreground | ||||
|   set-sgr-state-background | ||||
| 
 | ||||
|   sgr-state-change->string | ||||
| 
 | ||||
|   parse-csi-sgr-sequence+pos | ||||
|   parse-csi-sgr-sequence | ||||
| 
 | ||||
|   update-sgr-state-from-list | ||||
|   update-sgr-state-from-string | ||||
|    | ||||
|   sgr-state-tests! | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 (chicken bitwise) | ||||
| 	 (chicken string) | ||||
| 	 testing | ||||
| 	 racket-kwargs) | ||||
| 
 | ||||
|  ;; Checks whether this is indexed color or truecolor representation | ||||
|  ;; validating the bit mask as well | ||||
|  (define (sgr-color-valid? c) | ||||
|    (or (< c 256) | ||||
|        (and (>= c #x1000000) | ||||
| 	    (<= c #x1ffffff)))) | ||||
| 
 | ||||
|  ;; Returns true if given color is truecolor representation (assumes | ||||
|  ;; #x1000000 bit set) | ||||
|  (define (sgr-truecolor? c) | ||||
|    (> c 255)) | ||||
| 
 | ||||
|  ;; Creates valid truecolor representation | ||||
|  (define (make-sgr-truecolor r g b) | ||||
|    (bitwise-ior #x1000000 | ||||
| 		(arithmetic-shift (bitwise-and r 255) 16) | ||||
| 		(arithmetic-shift (bitwise-and g 255) 8) | ||||
| 		(bitwise-and b 255))) | ||||
| 
 | ||||
|  ;; Returns the separated RGB alues from SGR truecolor representation | ||||
|  (define (split-sgr-truecolor c) | ||||
|    (values (bitwise-and (arithmetic-shift c -16) 255) | ||||
| 	   (bitwise-and (arithmetic-shift c -8) 255) | ||||
| 	   (bitwise-and c 255))) | ||||
| 
 | ||||
|  ;; Creates empty SGR state | ||||
|  (define (make-sgr-state) | ||||
|    0) | ||||
| 
 | ||||
|  ;; Constant representing empty (default) SGR state | ||||
|  (define empty-sgr-state 0) | ||||
| 
 | ||||
|  ;; Returns #t if this is valid SGR state representation | ||||
|  (define (sgr-state? v) | ||||
|    (or (and (fixnum? v) | ||||
| 	    (= (bitwise-and v (bitwise-not sgr-state-bit-mask)) 0)) | ||||
|        (and (pair? v) | ||||
| 	    (fixnum? (car v)) | ||||
| 	    (= (bitwise-and v (bitwise-not sgr-state-bit-mask)) 0) | ||||
| 	    (pair? (cdr v)) | ||||
| 	    (or (not (cadr v)) | ||||
| 		(and (fixnum? (cadr v)) | ||||
| 		     (= (bitwise-and (cadr v) (bitwise-not #x1ffffff)) 0))) | ||||
| 	    (or (not (cddr v)) | ||||
| 		(and (fixnum? (cddr v)) | ||||
| 		     (= (bitwise-and (cddr v) (bitwise-not #x1ffffff)) 0)))))) | ||||
| 
 | ||||
|  ;; Basic SGR state is just a number, extended state is a pair of | ||||
|  ;; number and pair of fg/bg truecolors. | ||||
|  (define (sgr-state-bits s) | ||||
|    (if (pair? s) | ||||
|        (car s) | ||||
|        s)) | ||||
| 
 | ||||
|  ;; Sets the bits part of given SGR state, if it is not a pair, just | ||||
|  ;; returns the new bits as state value | ||||
|  (define (set-sgr-state-bits s b) | ||||
|    (if (pair? s) | ||||
|        (cons b (cdr s)) | ||||
|        b)) | ||||
| 
 | ||||
|  ;; Computes positional mask, creates getter and setter for given | ||||
|  ;; numeric attribute at given bit-offset | ||||
|  (define-syntax define-sgr-num-attribute | ||||
|    (syntax-rules () | ||||
|      ((_ bit-offset bit-size mask getter setter) | ||||
|       (begin | ||||
| 	(define mask (arithmetic-shift (sub1 (arithmetic-shift 1 bit-size)) | ||||
| 				       bit-offset)) | ||||
| 	(define (getter s) | ||||
| 	  (let ((v (arithmetic-shift | ||||
| 		    (bitwise-and (sgr-state-bits s) mask) | ||||
| 		    (- bit-offset)))) | ||||
| 	    (if (> v 0) v #f))) | ||||
| 	(define (setter s vf) | ||||
| 	  (let ((v (case vf | ||||
| 		     ((#f) 0) | ||||
| 		     ((#t) 1) | ||||
| 		     (else vf))) | ||||
| 		(b (sgr-state-bits s))) | ||||
| 	    (set-sgr-state-bits | ||||
| 	     s | ||||
| 	     (bitwise-ior | ||||
| 	      (bitwise-and (arithmetic-shift v bit-offset) mask) | ||||
| 	      (bitwise-and (bitwise-not mask) b))))))))) | ||||
| 
 | ||||
|  ;; Wrapper for defining all attributes iteratively, incrementing | ||||
|  ;; bit-offset as needed | ||||
|  (define-syntax define-sgr-num-attribute+ | ||||
|    (syntax-rules () | ||||
|      ((_ bit-offset total bit-size mask getter setter remaining ...) | ||||
|       (begin | ||||
| 	(define-sgr-num-attribute bit-offset bit-size mask getter setter) | ||||
| 	(define-sgr-num-attribute+ (+ bit-offset bit-size) total remaining ...))) | ||||
|      ((_ bit-offset total) | ||||
|       (define total bit-offset)))) | ||||
| 
 | ||||
|  ;; Wrapper for definer starting at bit offset 0 | ||||
|  (define-syntax define-sgr-num-attributes | ||||
|    (syntax-rules () | ||||
|      ((_ total all ...) | ||||
|       (begin | ||||
| 	(define-sgr-num-attribute+ 0 total all ...))))) | ||||
| 
 | ||||
|  ;; Define simple (numeric) attributes | ||||
|  (define-sgr-num-attributes | ||||
|    sgr-state-num-bits | ||||
|    ;; 1: bold, 2: half-bright | ||||
|    2 sgr-state-intensity-mask sgr-state-intensity set-sgr-state-intensity | ||||
|    1 sgr-state-italic-mask sgr-state-italic set-sgr-state-italic | ||||
|    ;; 1: single, 2: double | ||||
|    2 sgr-state-underline-mask sgr-state-underline set-sgr-state-underline | ||||
|    1 sgr-state-blink-mask sgr-state-blink set-sgr-state-blink | ||||
|    1 sgr-state-reverse-mask sgr-state-reverse set-sgr-state-reverse | ||||
|    1 sgr-state-crossed-mask sgr-state-crossed set-sgr-state-crossed | ||||
|    9 sgr-state-fg256-mask sgr-state-fg256 set-sgr-state-fg256 | ||||
|    9 sgr-state-bg256-mask sgr-state-bg256 set-sgr-state-bg256) | ||||
| 
 | ||||
|  ;; Used in sgr-state? predicate | ||||
|  (define sgr-state-bit-mask (sub1 (arithmetic-shift 1 sgr-state-num-bits))) | ||||
| 
 | ||||
|  ;; Returns true, color 0-255 or RGB triplet with bit 17 set to 1 if | ||||
|  ;; truecolor | ||||
|  (define (sgr-state-foreground s) | ||||
|    (if (pair? s) | ||||
|        (cadr s) | ||||
|        (let ((c+ (sgr-state-fg256 s))) | ||||
| 	 (if c+ | ||||
| 	     (bitwise-and c+ 255) | ||||
| 	     #f)))) | ||||
| 
 | ||||
|  ;; Returns true, color 0-255 or RGB triplet with bit 17 set to 1 if | ||||
|  ;; truecolor | ||||
|  (define (sgr-state-background s) | ||||
|    (if (pair? s) | ||||
|        (cddr s) | ||||
|        (let ((c+ (sgr-state-bg256 s))) | ||||
| 	 (if c+ | ||||
| 	     (bitwise-and c+ 255) | ||||
| 	     #f)))) | ||||
| 
 | ||||
|  ;; Sets foreground color, possibly compacting the SGR state, #f is | ||||
|  ;; valid value representing default color | ||||
|  (define (set-sgr-state-foreground s c) | ||||
|    (if (pair? s) | ||||
|        ;; Initially it is a pair | ||||
|        (if c | ||||
| 	   ;; Setting some foreground | ||||
| 	   (if (< c 256) | ||||
| 	       ;; Foreground does not need pair | ||||
| 	       (if (and (cddr s) | ||||
| 			(>= (cddr s) 256)) | ||||
| 		   ;; Background needs a pair, set foreground to bits | ||||
| 		   ;; and cadr | ||||
| 		   (cons (set-sgr-state-fg256 (car s) | ||||
| 					      (bitwise-ior c 256)) | ||||
| 			 (cons c (cddr s))) | ||||
| 		   ;; Neither background nor foreground need pair, | ||||
| 		   ;; switch to bit-only representation | ||||
| 		   (set-sgr-state-fg256 (car s) (bitwise-ior c 256))) | ||||
| 	       ;; Foreground needs pair, just update cadr and reset | ||||
| 	       ;; bits | ||||
| 	       (cons (set-sgr-state-fg256 (car s) 0) | ||||
| 		     (cons (bitwise-ior c #x1000000) | ||||
| 			   (cddr s)))) | ||||
| 	   ;; Removing foreground | ||||
| 	   (if (and (cddr s) | ||||
| 		    (>= (cddr s) 256)) | ||||
| 	       ;; Still pair needed for background, store both in bits | ||||
| 	       ;; and cadr | ||||
| 	       (cons (set-sgr-state-fg256 (car s) 0) | ||||
| 		     (cons #f (cddr s))) | ||||
| 	       ;; Just reset, no pair for background needed | ||||
| 	       (set-sgr-state-fg256 s 0))) | ||||
|        ;; Initially it is a bit representation | ||||
|        (if c | ||||
| 	   ;; Setting foreground | ||||
| 	   (if (< c 256) | ||||
| 	       ;; Just set the bits | ||||
| 	       (set-sgr-state-fg256 s (bitwise-ior c 256)) | ||||
| 	       ;; Create pair representation, store exclusively in cadr | ||||
| 	       (cons (set-sgr-state-fg256 s 0) | ||||
| 		     (cons (bitwise-ior c #x1000000) #f))) | ||||
| 	   ;; Just clear the bits | ||||
| 	   (set-sgr-state-fg256 s 0)))) | ||||
| 
 | ||||
|  ;; Sets background color, possibly compacting the SGR state, #f is | ||||
|  ;; valid value representing default color | ||||
|  (define (set-sgr-state-background s c) | ||||
|    (if (pair? s) | ||||
|        ;; Initially it is a pair | ||||
|        (if c | ||||
| 	   ;; Setting some background | ||||
| 	   (if (< c 256) | ||||
| 	       ;; Background does not need pair | ||||
| 	       (if (and (cadr s) | ||||
| 			(>= (cadr s) 256)) | ||||
| 		   ;; Foreground needs a pair, set background to bits | ||||
| 		   ;; and cddr | ||||
| 		   (cons (set-sgr-state-bg256 (car s) | ||||
| 					      (bitwise-ior c 256)) | ||||
| 			 (cons (cadr s) c)) | ||||
| 		   ;; Neither background nor foreground need pair, | ||||
| 		   ;; switch to bit-only representation | ||||
| 		   (set-sgr-state-bg256 (car s) (bitwise-ior c 256))) | ||||
| 	       ;; Background needs pair, just update cddr and reset | ||||
| 	       ;; bits | ||||
| 	       (cons (set-sgr-state-bg256 (car s) 0) | ||||
| 		     (cons (cadr s) | ||||
| 			   (bitwise-ior c #x1000000)))) | ||||
| 	   ;; Removing background | ||||
| 	   (if (and (cadr s) | ||||
| 		    (>= (cadr s) 256)) | ||||
| 	       ;; Still pair needed for foreground, store both in bits | ||||
| 	       ;; and cddr | ||||
| 	       (cons (set-sgr-state-bg256 (car s) 0) | ||||
| 		     (cons (cadr s) #f)) | ||||
| 	       ;; Just reset, no pair for foreground needed | ||||
| 	       (set-sgr-state-bg256 s 0))) | ||||
|        ;; Initially it is a bit representation | ||||
|        (if c | ||||
| 	   ;; Setting background | ||||
| 	   (if (< c 256) | ||||
| 	       ;; Just set the bits | ||||
| 	       (set-sgr-state-bg256 s (bitwise-ior c 256)) | ||||
| 	       ;; Create pair representation, store exclusively in cddr | ||||
| 	       (cons (set-sgr-state-bg256 s 0) | ||||
| 		     (cons #f (bitwise-ior c #x1000000)))) | ||||
| 	   ;; Just clear the bits | ||||
| 	   (set-sgr-state-bg256 s 0)))) | ||||
| 
 | ||||
|  ;; Prepends required CSI SGR sequence for given color change | ||||
|  (define (sgr-prepend-color-change lst color background?) | ||||
|    (let ((off (if background? 10 0))) | ||||
|      (cond ((eq? color #f) | ||||
| 	    (cons (+ off 39) lst)) | ||||
| 	   ((< color 8) | ||||
| 	    (cons (+ off 30 color) lst)) | ||||
| 	   ((< color 16) | ||||
| 	    (cons (+ off 82 color) lst)) | ||||
| 	   ((< color 256) | ||||
| 	    (cons color | ||||
| 		  (cons 5 | ||||
| 			(cons (+ 38 off) | ||||
| 			      lst)))) | ||||
| 	   (else | ||||
| 	    (let-values (((r g b) (split-sgr-truecolor color))) | ||||
| 	      (cons b | ||||
| 		    (cons g | ||||
| 			  (cons r | ||||
| 				(cons 2 | ||||
| 				      (cons (+ 38 off) | ||||
| 					    lst)))))))))) | ||||
| 
 | ||||
|  ;; Produces an CSI SGR sequence to change from the orig state to the | ||||
|  ;; next state. | ||||
|  (define (sgr-state-change->string orig next) | ||||
|    (cond | ||||
|     ((equal? orig next) | ||||
|      "") | ||||
|     ((equal? next empty-sgr-state) | ||||
|      "\x1b[0m") | ||||
|     (else | ||||
|      (let* ((cs0 '()) | ||||
| 	    (cs1 (if (eq? (sgr-state-intensity orig) | ||||
| 			  (sgr-state-intensity next)) | ||||
| 		     cs0 | ||||
| 		     (cons (case (sgr-state-intensity next) | ||||
| 			     ((1) 1) | ||||
| 			     ((2) 2) | ||||
| 			     (else 22)) | ||||
| 			   cs0))) | ||||
| 	    (cs2 (if (eq? (sgr-state-italic orig) | ||||
| 			  (sgr-state-italic next)) | ||||
| 		     cs1 | ||||
| 		     (cons (if (sgr-state-italic next) | ||||
| 			       3 | ||||
| 			       23) | ||||
| 			   cs1))) | ||||
| 	    (cs3 (if (eq? (sgr-state-underline orig) | ||||
| 			  (sgr-state-underline next)) | ||||
| 		     cs2 | ||||
| 		     (cons (case (sgr-state-underline next) | ||||
| 			     ((1) 4) | ||||
| 			     ((2) 21) | ||||
| 			     (else 24)) | ||||
| 			   cs2))) | ||||
| 	    (cs4 (if (eq? (sgr-state-blink orig) | ||||
| 			  (sgr-state-blink next)) | ||||
| 		     cs3 | ||||
| 		     (cons (if (sgr-state-blink next) | ||||
| 			       5 | ||||
| 			       25) | ||||
| 			   cs3))) | ||||
| 	    (cs5 (if (eq? (sgr-state-reverse orig) | ||||
| 			  (sgr-state-reverse next)) | ||||
| 		     cs4 | ||||
| 		     (cons (if (sgr-state-reverse next) | ||||
| 			       7 | ||||
| 			       27) | ||||
| 			   cs4))) | ||||
| 	    (cs6 (if (eq? (sgr-state-crossed orig) | ||||
| 			  (sgr-state-crossed next)) | ||||
| 		     cs5 | ||||
| 		     (cons (if (sgr-state-crossed next) | ||||
| 			       9 | ||||
| 			       29) | ||||
| 			   cs5))) | ||||
| 	    (cs7 (if (eq? (sgr-state-foreground orig) | ||||
| 			  (sgr-state-foreground next)) | ||||
| 		     cs6 | ||||
| 		     (sgr-prepend-color-change cs6 (sgr-state-foreground next) #f))) | ||||
| 	    (cs8 (if (eq? (sgr-state-background orig) | ||||
| 			  (sgr-state-background next)) | ||||
| 		     cs7 | ||||
| 		     (sgr-prepend-color-change cs7 (sgr-state-background next) #t))) | ||||
| 	    (cs cs8)) | ||||
|        (string-append | ||||
| 	"\x1b[" | ||||
| 	(string-intersperse | ||||
| 	 (map number->string (reverse cs)) | ||||
| 	 ";") | ||||
| 	"m"))))) | ||||
| 
 | ||||
|  ;; Parses a CSI SGR sequence. Returns a list of numeric arguments in | ||||
|  ;; the same order as they are present in the sequence, prepends the | ||||
|  ;; terminating character (sequence type). Returns position just after | ||||
|  ;; parsed sequence as second value. | ||||
|  (define (parse-csi-sgr-sequence+pos str . ps) | ||||
|    (let ((pos (if (null? ps) 0 (car ps))) | ||||
| 	 (len (string-length str))) | ||||
|      (if (or (>= pos len) | ||||
| 	     (not (eq? (string-ref str pos) #\x1b))) | ||||
| 	 (values '() 0) | ||||
| 	 (if (or (>= (add1 pos) len) | ||||
| 		 (not (eq? (string-ref str (add1 pos)) #\[))) | ||||
| 	     (values '() 1) | ||||
| 	     (let loop ((pos (+ pos 2)) | ||||
| 			(res '()) | ||||
| 			(pending #f)) | ||||
| 	       (if (>= pos len) | ||||
| 		   (values '() pos) | ||||
| 		   (let ((ch (string-ref str pos))) | ||||
| 		     (cond ((and (char>=? ch #\0) | ||||
| 				 (char<=? ch #\9)) | ||||
| 			    (let ((digit (- (char->integer ch) | ||||
| 					    (char->integer #\0)))) | ||||
| 			      (loop (add1 pos) | ||||
| 				    res | ||||
| 				    (if pending | ||||
| 					(+ (* pending 10) digit) | ||||
| 					digit)))) | ||||
| 			   ((eq? ch #\;) | ||||
| 			    (loop (add1 pos) | ||||
| 				  (cons pending res) | ||||
| 				  #f)) | ||||
| 			   (else | ||||
| 			    (values (cons ch | ||||
| 					  (reverse (if pending | ||||
| 						       (cons pending res) | ||||
| 						       res))) | ||||
| 				    (add1 pos))))))))))) | ||||
| 
 | ||||
|  ;; Parses CSI SGR sequence and returns the sequence list only. | ||||
|  (define (parse-csi-sgr-sequence str . ps) | ||||
|    (let-values (((lst pos) | ||||
| 		 (parse-csi-sgr-sequence+pos str (if (null? ps) 0 (car ps))))) | ||||
|      lst)) | ||||
| 
 | ||||
|  ;; Parses 256 and 16M color sequences | ||||
|  (define (parse-extended-sgr-color lst state background?) | ||||
|    (if (null? lst) | ||||
|        (list lst state) | ||||
|        (case (car lst) | ||||
| 	 ((5) | ||||
| 	  (if (null? (cdr lst)) | ||||
| 	      (list (cdr lst) state) | ||||
| 	      (list (cddr lst) | ||||
| 		    (if background? | ||||
| 			(set-sgr-state-background state (cadr lst)) | ||||
| 			(set-sgr-state-foreground state (cadr lst)))))) | ||||
| 	 ((2) | ||||
| 	  (if (null? (cdr lst)) ; R? | ||||
| 	      (list (cdr lst) state) | ||||
| 	      (if (null? (cddr lst)) ; G? | ||||
| 		  (list (cddr lst) state) | ||||
| 		  (if (null? (cdddr lst)) ; B? | ||||
| 		      (list (cdddr lst) state) | ||||
| 		      (list (cddddr lst) | ||||
| 			    (let ((c (make-sgr-truecolor (cadr lst) | ||||
| 							 (caddr lst) | ||||
| 							 (cadddr lst)))) | ||||
| 			      (if background? | ||||
| 				  (set-sgr-state-background state c) | ||||
| 				  (set-sgr-state-foreground state c)))))))) | ||||
| 	 (else | ||||
| 	  (list (cdr lst) state))))) | ||||
| 
 | ||||
|  ;; Update given state by parsed SGR state list | ||||
|  (define* (update-sgr-state-from-list state lst | ||||
| 				      #:default (default empty-sgr-state)) | ||||
|    (if (or (null? lst) | ||||
| 	   (not (eq? (car lst) #\m))) | ||||
|        state | ||||
|        (let loop ((lst (cdr lst)) | ||||
| 		  (state state)) | ||||
| 	 (if (null? lst) | ||||
| 	     state | ||||
| 	     (case (car lst) | ||||
| 	       ((0) | ||||
| 		(loop (cdr lst) | ||||
| 		      default)) | ||||
| 	       ((1) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-intensity state 1))) | ||||
| 	       ((2) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-intensity state 2))) | ||||
| 	       ((3) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-intensity state #t))) | ||||
| 	       ((4) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-underline state 1))) | ||||
| 	       ((5) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-blink state #t))) | ||||
| 	       ((7) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-reverse state #t))) | ||||
| 	       ((9) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-crossed state #t))) | ||||
| 	       ((21) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-underline state 2))) | ||||
| 	       ((22) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-intensity state #f))) | ||||
| 	       ((23) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-italic state #f))) | ||||
| 	       ((24) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-underline state #f))) | ||||
| 	       ((25) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-blink state #f))) | ||||
| 	       ((27) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-reverse state #f))) | ||||
| 	       ((29) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-crossed state #f))) | ||||
| 	       ((30 31 32 33 34 35 36 37) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-foreground state (- (car lst) 30)))) | ||||
| 	       ((38) | ||||
| 		(apply loop (parse-extended-sgr-color (cdr lst) state #f))) | ||||
| 	       ((39) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-foreground state #f))) | ||||
| 	       ((40 41 42 43 44 45 46 47) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-background state (- (car lst) 40)))) | ||||
| 	       ((48) | ||||
| 		(apply loop (parse-extended-sgr-color (cdr lst) state #f))) | ||||
| 	       ((49) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-background state #f))) | ||||
| 	       ((90 91  92 93 94 95 96 97) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-foreground state (- (car lst) 82)))) | ||||
| 	       ((100 101 102 103 104 105 106 107) | ||||
| 		(loop (cdr lst) | ||||
| 		      (set-sgr-state-background state (- (car lst) 92)))) | ||||
| 	       (else | ||||
| 		(loop (cdr lst) | ||||
| 		      state))))))) | ||||
| 
 | ||||
|  ;; Updates given SGR state based on ESC sequence(s) given in the | ||||
|  ;; string. Optional starting position can be given. Processes only | ||||
|  ;; one sequence. | ||||
|  (define* (update-sgr-state-from-string state str | ||||
| 					(pos 0) | ||||
| 					#:default (default empty-sgr-state)) | ||||
|    (let ((lst (parse-csi-sgr-sequence str pos))) | ||||
|      (update-sgr-state-from-list state lst #:default default))) | ||||
| 
 | ||||
|  ;; Module self-tests | ||||
|  (define (sgr-state-tests!) | ||||
|    (run-tests | ||||
|     sgr-state | ||||
|     (test-true sgr-color-valid? | ||||
| 	       (sgr-color-valid? 1)) | ||||
|     (test-true sgr-color-valid? | ||||
| 	       (sgr-color-valid? #x1234567)) | ||||
|     (test-false sgr-color-valid? | ||||
| 		(sgr-color-valid? 1000)) | ||||
|     (test-false sgr-color-valid? | ||||
| 		(sgr-color-valid? #x2000000)) | ||||
|     (test-false sgr-truecolor? | ||||
| 		(sgr-truecolor? 123)) | ||||
|     (test-true sgr-truecolor? | ||||
| 	       (sgr-truecolor? #x1234567)) | ||||
|     (test-equal? make-sgr-truecolor | ||||
| 		 (make-sgr-truecolor 1 2 3) | ||||
| 		 #x1010203) | ||||
|     (test-equal? split-sgr-truecolor | ||||
| 		 (call-with-values | ||||
| 		     (lambda () | ||||
| 		       (split-sgr-truecolor #x1112233)) | ||||
| 		   list) | ||||
| 		 '(17 34 51)) | ||||
|     (test-equal? sgr-state-intensity-mask | ||||
| 		 sgr-state-intensity-mask | ||||
| 		 3) | ||||
|     (test-equal? sgr-state-italic-mask | ||||
| 		 sgr-state-italic-mask | ||||
| 		 4) | ||||
|     (test-equal? sgr-state-italic | ||||
| 		 (sgr-state-italic 4) | ||||
| 		 1) | ||||
|     (test-equal? set-sgr-state-italic | ||||
| 		 (set-sgr-state-italic 0 1) | ||||
| 		 4) | ||||
|     (test-equal? set-sgr-state-italic | ||||
| 		 (set-sgr-state-italic 15 0) | ||||
| 		 11) | ||||
|     (test-equal? set-sgr-state-italic | ||||
| 		 (set-sgr-state-italic 15 #f) | ||||
| 		 11) | ||||
|     (test-equal? set-sgr-state-foreground | ||||
| 		 (set-sgr-state-foreground 0 255) | ||||
| 		 (set-sgr-state-fg256 0 511)) | ||||
|     (test-equal? set-sgr-state-foreground | ||||
| 		 (set-sgr-state-foreground 0 256) | ||||
| 		 (cons 0 (cons #x1000100 #f))) | ||||
|     (test-equal? set-sgr-state-foreground | ||||
| 		 (set-sgr-state-foreground | ||||
| 		  (set-sgr-state-foreground 0 256) 255) | ||||
| 		 (set-sgr-state-fg256 0 511)) | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-intensity empty-sgr-state 1)) | ||||
| 		 "\x1b[1m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-italic | ||||
| 					     empty-sgr-state 1) 1) | ||||
| 					   empty-sgr-state) | ||||
| 		 "\x1b[0m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-italic | ||||
| 					     empty-sgr-state #t) 2) | ||||
| 					   (set-sgr-state-italic | ||||
| 					    empty-sgr-state #t)) | ||||
| 		 "\x1b[22m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-italic | ||||
| 					     empty-sgr-state #t) 2) | ||||
| 					   (set-sgr-state-intensity | ||||
| 					    empty-sgr-state 2)) | ||||
| 		 "\x1b[23m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    empty-sgr-state 2) | ||||
| 					   (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-italic | ||||
| 					     empty-sgr-state #t) 2)) | ||||
| 		 "\x1b[3m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    empty-sgr-state 2) | ||||
| 					   (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-underline | ||||
| 					     empty-sgr-state 2) 2)) | ||||
| 		 "\x1b[21m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    empty-sgr-state 2) | ||||
| 					   (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-blink | ||||
| 					     empty-sgr-state #t) 2)) | ||||
| 		 "\x1b[5m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    empty-sgr-state 2) | ||||
| 					   (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-reverse | ||||
| 					     empty-sgr-state #t) 2)) | ||||
| 		 "\x1b[7m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string (set-sgr-state-intensity | ||||
| 					    empty-sgr-state 2) | ||||
| 					   (set-sgr-state-intensity | ||||
| 					    (set-sgr-state-crossed | ||||
| 					     empty-sgr-state #t) 2)) | ||||
| 		 "\x1b[9m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-foreground | ||||
| 					    empty-sgr-state | ||||
| 					    5)) | ||||
| 		 "\x1b[35m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-foreground | ||||
| 					    empty-sgr-state | ||||
| 					    15)) | ||||
| 		 "\x1b[97m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-foreground | ||||
| 					    empty-sgr-state | ||||
| 					    115)) | ||||
| 		 "\x1b[38;5;115m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-foreground | ||||
| 					    empty-sgr-state | ||||
| 					    #x1112233)) | ||||
| 		 "\x1b[38;2;17;34;51m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-background | ||||
| 					    empty-sgr-state | ||||
| 					    5)) | ||||
| 		 "\x1b[45m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-background | ||||
| 					    empty-sgr-state | ||||
| 					    15)) | ||||
| 		 "\x1b[107m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-background | ||||
| 					    empty-sgr-state | ||||
| 					    115)) | ||||
| 		 "\x1b[48;5;115m") | ||||
|     (test-equal? sgr-state-change->string | ||||
| 		 (sgr-state-change->string empty-sgr-state | ||||
| 					   (set-sgr-state-background | ||||
| 					    empty-sgr-state | ||||
| 					    #x1112233)) | ||||
| 		 "\x1b[48;2;17;34;51m") | ||||
|     (test-equal? parse-csi-sgr-sequence | ||||
| 		 (parse-csi-sgr-sequence "\x1b[38;2;1;2;3m") | ||||
| 		 '(#\m 38 2 1 2 3)) | ||||
|     (test-equal? update-sgr-state-from-string | ||||
| 		 (update-sgr-state-from-string | ||||
| 		  empty-sgr-state | ||||
| 		  "\x1b[1m") | ||||
| 		 1) | ||||
|     (test-equal? update-sgr-state-from-string | ||||
| 		 (update-sgr-state-from-string | ||||
| 		  empty-sgr-state | ||||
| 		  "\x1b[31m") | ||||
| 		 (set-sgr-state-foreground 0 1)) | ||||
|     (test-equal? update-sgr-state-from-string | ||||
| 		 (update-sgr-state-from-string | ||||
| 		  empty-sgr-state | ||||
| 		  "\x1b[91m") | ||||
| 		 (set-sgr-state-foreground 0 9)) | ||||
|     (test-equal? update-sgr-state-from-string | ||||
| 		 (update-sgr-state-from-string | ||||
| 		  empty-sgr-state | ||||
| 		  "\x1b[38;2;17;34;51m") | ||||
| 		 (set-sgr-state-foreground empty-sgr-state #x1112233)) | ||||
|     )) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										240
									
								
								src/table-border.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								src/table-border.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,240 @@ | |||
| ;; | ||||
| ;; table-border.scm | ||||
| ;; | ||||
| ;; Table border rendering. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit table-border)) | ||||
| 
 | ||||
| (module | ||||
|  table-border | ||||
|  ( | ||||
|   table-rows-border | ||||
| 
 | ||||
|   table-border-vertical | ||||
| 
 | ||||
|   table-border-between-rows? | ||||
| 
 | ||||
|   table-col-separators? | ||||
| 
 | ||||
|   table-row-merge | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 racket-kwargs | ||||
| 	 box-drawing | ||||
| 	 util-utf8 | ||||
| 	 sgr-block) | ||||
| 
 | ||||
|  ;; Vertical separator char (horizontal lines) | ||||
|  (define* (table-border-char-vertical top-borders bottom-borders (unicode? #t)) | ||||
|    (let* ((top-south (line-cell-south top-borders)) | ||||
| 	  (bottom-north (line-cell-north bottom-borders)) | ||||
| 	  (combined (combine-line-style top-south bottom-north)) | ||||
| 	  (cell (make-straight-horizontal-line-cell* combined))) | ||||
|      (if unicode? | ||||
| 	 (line-cell->unicode-char cell) | ||||
| 	 (line-cell->ascii-char cell)))) | ||||
| 
 | ||||
|  ;; Horizontal separator char (vertical lines) | ||||
|  (define* (table-border-char-horizontal left-borders right-borders (unicode? #t)) | ||||
|    (let* ((left-east (line-cell-east left-borders)) | ||||
| 	  (right-west (line-cell-west right-borders)) | ||||
| 	  (combined (combine-line-style left-east right-west)) | ||||
| 	  (cell (make-straight-vertical-line-cell* combined))) | ||||
|      (if unicode? | ||||
| 	 (line-cell->unicode-char cell) | ||||
| 	 (line-cell->ascii-char cell)))) | ||||
| 
 | ||||
|  ;; Corner between four adjacent cells | ||||
|  (define* (table-border-char-cross tl-b tr-b bl-b br-b (unicode? #t)) | ||||
|    (let* ((tl-east (line-cell-east tl-b)) | ||||
| 	  (tl-south (line-cell-south tl-b)) | ||||
| 	  (tr-west (line-cell-west tr-b)) | ||||
| 	  (tr-south (line-cell-south tr-b)) | ||||
| 	  (bl-north (line-cell-north bl-b)) | ||||
| 	  (bl-east (line-cell-east bl-b)) | ||||
| 	  (br-north (line-cell-north br-b)) | ||||
| 	  (br-west (line-cell-west br-b)) | ||||
| 	  (north (combine-line-style tl-east tr-west)) | ||||
| 	  (west (combine-line-style tl-south bl-north)) | ||||
| 	  (east (combine-line-style tr-south br-north)) | ||||
| 	  (south (combine-line-style bl-east br-west)) | ||||
| 	  (cell (make-line-cell north west east south))) | ||||
|      (if unicode? | ||||
| 	 (line-cell->unicode-char cell) | ||||
| 	 (line-cell->ascii-char cell)))) | ||||
| 
 | ||||
|  ;; Appends to list, lst should be pointing at the last cons cell of | ||||
|  ;; the list, returns the new last cons cell. | ||||
|  (define (append-to-list lst ch n) | ||||
|    (cond | ||||
|     ((= 0 n) | ||||
|      lst) | ||||
|     (else | ||||
|      (set-cdr! lst (cons ch '())) | ||||
|      (append-to-list (cdr lst) ch (sub1 n))))) | ||||
| 
 | ||||
|  ;; Returns SGR-list of table-rows border | ||||
|  ;; TODO: add corner only if applicable (add separators argument) | ||||
|  (define* (table-rows-border col-widths top-row-borders bottom-row-borders | ||||
| 			     col-separators (unicode? #t)) | ||||
|    (let ((ch0 (cons #f '())) | ||||
| 	 (trb0 (append (or top-row-borders | ||||
| 			   (map (lambda x 0) bottom-row-borders)) | ||||
| 		       (list 0))) | ||||
| 	 (brb0 (append (or bottom-row-borders | ||||
| 			   (map (lambda x 0) top-row-borders)) | ||||
| 		       (list 0)))) | ||||
|      (let loop ((chl ch0) | ||||
| 		(trb:l (cons 0 trb0)) | ||||
| 		(brb:l (cons 0 brb0)) | ||||
| 		(trb:r trb0) | ||||
| 		(brb:r brb0) | ||||
| 		(cws (append col-widths | ||||
| 			     (list 0))) | ||||
| 		(col-seps col-separators)) | ||||
|        (if (null? trb:r) | ||||
| 	   (list (list (cons (list->utf8-string (cdr ch0)) | ||||
| 			     (length (cdr ch0))))) | ||||
| 	   (let* ((tl (car trb:l)) | ||||
| 		  (tr (car trb:r)) | ||||
| 		  (bl (car brb:l)) | ||||
| 		  (br (car brb:r)) | ||||
| 		  (lc (table-border-char-cross tl tr bl br unicode?)) | ||||
| 		  (sc (table-border-char-vertical tr br unicode?)) | ||||
| 		  (cw (car cws))) | ||||
| 	     (loop (append-to-list (if (car col-seps) | ||||
| 				       (append-to-list chl lc 1) | ||||
| 				       chl) | ||||
| 				   sc cw) | ||||
| 		   (cdr trb:l) | ||||
| 		   (cdr brb:l) | ||||
| 		   (cdr trb:r) | ||||
| 		   (cdr brb:r) | ||||
| 		   (cdr cws) | ||||
| 		   (cdr col-seps))))))) | ||||
| 
 | ||||
|  ;; Returns a SGR-block of correct height | ||||
|  (define* (table-border-vertical height left-borders right-borders (unicode? #t)) | ||||
|    (let* ((ch (table-border-char-horizontal left-borders right-borders unicode?)) | ||||
| 	  (str (utf8-char->string ch))) | ||||
|      (let loop ((height height) | ||||
| 		(res '())) | ||||
|        (if (= height 0) | ||||
| 	   res | ||||
| 	   (loop (sub1 height) | ||||
| 		 (cons (list 0 (cons str 1)) | ||||
| 		       res)))))) | ||||
| 
 | ||||
|  ;; Returns true if the border should be drawn between these two rows | ||||
|  (define (table-border-between-rows? row0 row1) | ||||
|    (let loop ((row0 (if row0 | ||||
| 			row0 | ||||
| 			(map (lambda x 0) row1))) | ||||
| 	      (row1 (if row1 | ||||
| 			row1 | ||||
| 			(map (lambda x 0) row0))) | ||||
| 	      (res #f)) | ||||
|      (if (null? row0) | ||||
| 	 res | ||||
| 	 (loop (cdr row0) | ||||
| 	       (cdr row1) | ||||
| 	       (or res | ||||
| 		   (not (eq? (table-border-char-vertical (car row0) | ||||
| 							 (car row1) #f) | ||||
| 			     #\space))))))) | ||||
| 
 | ||||
|  ;; Returns a list of boolean values representing the presence of | ||||
|  ;; column delimiters | ||||
|  (define (table-border-between-row-columns? row) | ||||
|    (let loop ((row row) | ||||
| 	      (res '()) | ||||
| 	      (prev 0)) | ||||
|      (if (null? row) | ||||
| 	 (reverse (cons (not (eq? (table-border-char-horizontal prev 0 #f) | ||||
| 				  #\space)) | ||||
| 			res)) | ||||
| 	 (loop (cdr row) | ||||
| 	       (cons (not (eq? (table-border-char-horizontal prev | ||||
| 							     (car row) #f) | ||||
| 			       #\space)) | ||||
| 		     res) | ||||
| 	       (car row))))) | ||||
| 
 | ||||
|  ;; Returns a list of booleans representing the fact that there is a | ||||
|  ;; column separator at given position. The list contains one more | ||||
|  ;; value than the number of columns. | ||||
|  (define (table-col-separators? borders) | ||||
|    (if (null? borders) | ||||
|        '() | ||||
|        (let loop ((rows (cdr borders)) | ||||
| 		  (res (table-border-between-row-columns? (car borders)))) | ||||
| 	 (if (null? rows) | ||||
| 	     res | ||||
| 	     (loop (cdr rows) | ||||
| 		   (let rloop ((res0 res) | ||||
| 			       (row0 (table-border-between-row-columns? (car rows))) | ||||
| 			       (res '())) | ||||
| 		     (if (null? res0) | ||||
| 			 (reverse res) | ||||
| 			 (rloop (cdr res0) | ||||
| 				(cdr row0) | ||||
| 				(cons (or (car res0) | ||||
| 					  (car row0)) | ||||
| 				      res))))))))) | ||||
| 
 | ||||
|  ;; Merges row SGR blocks and intersperses them with separators when | ||||
|  ;; appropriate. | ||||
|  (define (table-row-merge row col-separators borders unicode?) | ||||
|    (if (null? row) | ||||
|        '() | ||||
|        (let ((height (sgr-block-height (car row)))) | ||||
| 	 (let loop ((row row) | ||||
| 		    (res '()) | ||||
| 		    (seps col-separators) ;; always left first | ||||
| 		    (borders borders) | ||||
| 		    (prev-border 0) | ||||
| 		    ) | ||||
| 	   (if (null? row) | ||||
| 	       (apply sgr-block-happend | ||||
| 		      (reverse (if (car seps) | ||||
| 				   (cons (table-border-vertical height | ||||
| 								prev-border | ||||
| 								0 | ||||
| 								unicode?) | ||||
| 					 res) | ||||
| 				   res))) | ||||
| 	       (loop (cdr row) | ||||
| 		     (cons (car row) | ||||
| 			   (if (car seps) | ||||
| 			       (cons (table-border-vertical height | ||||
| 							    prev-border | ||||
| 							    (car borders) | ||||
| 							    unicode?) | ||||
| 				     res) | ||||
| 			       res)) | ||||
| 		     (cdr seps) | ||||
| 		     (cdr borders) | ||||
| 		     (car borders))))))) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										208
									
								
								src/table-processor.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								src/table-processor.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,208 @@ | |||
| ;; | ||||
| ;; table-processor.scm | ||||
| ;; | ||||
| ;; Table data preprocessing (before rendering) | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit table-processor)) | ||||
| 
 | ||||
| (module | ||||
|  table-processor | ||||
|  ( | ||||
|   table-prepare | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 (chicken format) | ||||
| 	 (chicken sort) | ||||
| 	 sgr-cell | ||||
| 	 template-list-expander) | ||||
| 
 | ||||
|  ;; Makes the list of lists rectangular, makes sure all cells are | ||||
|  ;; sgr-cell (sgr-block actually). | ||||
|  (define (table-prepare-cells tbl) | ||||
|    (let ((width (apply max (map length tbl)))) | ||||
|      (map (lambda (row) | ||||
| 	    (let loop ((row row) | ||||
| 		       (rrow '()) | ||||
| 		       (rlen 0)) | ||||
| 	      (if (= rlen width) | ||||
| 		  (let () | ||||
| 		    (reverse rrow)) | ||||
| 		  (loop (if (null? row) | ||||
| 			    row | ||||
| 			    (cdr row)) | ||||
| 			(let ((cell (if (null? row) | ||||
| 					"" | ||||
| 					(car row)))) | ||||
| 			  (cons (string->sgr-cell | ||||
| 				 (if (string? cell) | ||||
| 				     cell | ||||
| 				     (format "~A" cell))) | ||||
| 				rrow)) | ||||
| 			(add1 rlen))))) | ||||
| 	  tbl))) | ||||
| 
 | ||||
|  ;; Max from both lists (must be the same length) | ||||
|  (define (combine-column-widths cwidths rwidths) | ||||
|    (let mloop ((cwidths cwidths) | ||||
| 	       (rwidths rwidths) | ||||
| 	       (res '())) | ||||
|      (if (null? cwidths) | ||||
| 	 (reverse res) | ||||
| 	 (mloop (cdr cwidths) | ||||
| 		(cdr rwidths) | ||||
| 		(cons (max (car cwidths) | ||||
| 			   (car rwidths)) | ||||
| 		      res))))) | ||||
| 
 | ||||
|  ;; Returns maximum value for each column | ||||
|  (define (table-columns-max-query tbl cell-query) | ||||
|    (if (null? tbl) | ||||
|        '() | ||||
|        (let loop ((widths (map cell-query (car tbl))) | ||||
| 		  (tbl (cdr tbl))) | ||||
| 	 (if (null? tbl) | ||||
| 	     widths | ||||
| 	     (loop (combine-column-widths widths | ||||
| 					  (map sgr-cell-min-width (car tbl))) | ||||
| 		   (cdr tbl)))))) | ||||
| 
 | ||||
|  ;; Minimal widths | ||||
|  (define (table-min-column-widths tbl) | ||||
|    (table-columns-max-query tbl sgr-cell-min-width)) | ||||
| 
 | ||||
|  ;; Weights | ||||
|  (define (table-column-weights tbl) | ||||
|    (table-columns-max-query tbl sgr-cell-width)) | ||||
| 
 | ||||
|  ;; Distribute width according to weights. | ||||
|  (define (compute-weighted-width-adds width weights) | ||||
|    (let ((sorted-weights | ||||
| 	  (sort | ||||
| 	   (let loop ((weights weights) | ||||
| 		      (idx 0) | ||||
| 		      (res '())) | ||||
| 	     (if (null? weights) | ||||
| 		 res | ||||
| 		 (loop (cdr weights) | ||||
| 		       (add1 idx) | ||||
| 		       (cons (cons (car weights) | ||||
| 				   idx) | ||||
| 			     res)))) | ||||
| 	   (lambda (a b) | ||||
| 	     (< (car a) | ||||
| 		(car b)))))) | ||||
|      (let loop ((weights (map car sorted-weights)) | ||||
| 		(indexes (map cdr sorted-weights)) | ||||
| 		(remaining-width width) | ||||
| 		(res '())) | ||||
|        (if (null? weights) | ||||
| 	   (map car | ||||
| 		(sort res | ||||
| 		      (lambda (a b) | ||||
| 			(< (cdr a) | ||||
| 			   (cdr b))))) | ||||
| 	   (let* ((total-weight (apply + weights)) | ||||
| 		  (this-weight (car weights)) | ||||
| 		  (this-width (quotient (* this-weight remaining-width) total-weight)) | ||||
| 		  (this-index (car indexes))) | ||||
| 	     (loop (cdr weights) | ||||
| 		   (cdr indexes) | ||||
| 		   (- remaining-width this-width) | ||||
| 		   (cons (cons this-width this-index) res))))))) | ||||
| 
 | ||||
|  ;; Sums the two widths | ||||
|  (define (distribute-surplus widths adds) | ||||
|    (let loop ((widths widths) | ||||
| 	      (adds adds) | ||||
| 	      (res '())) | ||||
|      (if (null? widths) | ||||
| 	 (reverse res) | ||||
| 	 (loop (cdr widths) | ||||
| 	       (cdr adds) | ||||
| 	       (cons (+ (car widths) | ||||
| 			(car adds)) | ||||
| 		     res))))) | ||||
| 
 | ||||
|  ;; For all rows, performs 1st pass render (wrapping) | ||||
|  (define (render-cells-widths tbl widths) | ||||
|    (map (lambda (row) | ||||
| 	  (let loop ((row row) | ||||
| 		     (widths widths) | ||||
| 		     (res '())) | ||||
| 	    (if (null? row) | ||||
| 		(reverse res) | ||||
| 		(loop (cdr row) | ||||
| 		      (cdr widths) | ||||
| 		      (cons (sgr-cell-render (car row) | ||||
| 					     #:width (car widths)) | ||||
| 			    res))))) | ||||
| 	tbl)) | ||||
| 
 | ||||
|  ;; Get maximum height, expand using last state and empty rows | ||||
|  (define (expand-row-height row) | ||||
|    (let ((height (apply max (map sgr-cell-height row)))) | ||||
|      (map (lambda (cell) | ||||
| 	    (sgr-cell-vexpand cell height)) | ||||
| 	  row))) | ||||
| 
 | ||||
|  ;; Get minimal column widths, combine to minimal wanted widths, get | ||||
|  ;; column weights, distribute the surplus (if any). Render all cells | ||||
|  ;; to get row heights. Second pass, expand vertically all cells, | ||||
|  ;; return result. Widths must be expanded from template spec. | ||||
|  (define (table-prepare tbl width-arg widths-spec) | ||||
|    (if (or (null? tbl) | ||||
| 	   (null? (car tbl))) | ||||
|        '() | ||||
|        (let* ((ptbl (table-prepare-cells tbl)) | ||||
| 	      ;;(_ (print ptbl)) | ||||
| 	      (num-columns (length (car ptbl))) | ||||
| 	      (widths (expand-template-list widths-spec num-columns)) | ||||
| 	      ;;(_ (print widths)) | ||||
| 	      (min-widths0 (table-min-column-widths ptbl)) | ||||
| 	      ;;(_ (print min-widths0)) | ||||
| 	      (min-widths (combine-column-widths min-widths0 widths)) | ||||
| 	      ;;(_ (print min-widths)) | ||||
| 	      (col-weights (table-column-weights ptbl)) | ||||
| 	      ;;(_ (print col-weights)) | ||||
| 	      (min-width (foldl + 0 min-widths)) | ||||
| 	      ;;(_ (print min-width)) | ||||
| 	      (width (if (and width-arg | ||||
| 			      (> width-arg min-width)) | ||||
| 			 width-arg | ||||
| 			 min-width)) | ||||
| 	      ;;(_ (print width)) | ||||
| 	      (width-surplus (- width min-width)) | ||||
| 	      ;;(_ (print width-surplus)) | ||||
| 	      (widths-adds (compute-weighted-width-adds width-surplus col-weights)) | ||||
| 	      ;;(_ (print widths-adds)) | ||||
| 	      (col-widths (distribute-surplus widths-adds min-widths)) | ||||
| 	      ;;(_ (print col-widths)) | ||||
| 	      (tbl1 (render-cells-widths ptbl col-widths)) | ||||
| 	      ;;(_ (print tbl1)) | ||||
| 	      (tbl2 (map expand-row-height tbl1))) | ||||
| 	 ;; Just return the result - both the table and cached column widths | ||||
| 	 (values tbl2 | ||||
| 		 col-widths)))) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										170
									
								
								src/table-style.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								src/table-style.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,170 @@ | |||
| ;; | ||||
| ;; table-style.scm | ||||
| ;; | ||||
| ;; Converts and expands table border styles. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit table-style)) | ||||
| 
 | ||||
| (module | ||||
|  table-style | ||||
|  ( | ||||
|   expand-table-style | ||||
|    | ||||
|   table-style-tests! | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 (chicken keyword) | ||||
| 	 box-drawing | ||||
| 	 testing | ||||
| 	 template-list-expander) | ||||
| 
 | ||||
|  ;; Consumes single border specification from cell borders. Returns: | ||||
|  ;; sides, line-style-spec and rest. | ||||
|  (define (table-border-style-consume lst) | ||||
|    (let loop ((sides '()) | ||||
| 	      (sides-done? #f) | ||||
| 	      (line-style-spec '()) | ||||
| 	      (lst lst)) | ||||
|      (if (null? lst) | ||||
| 	 ;; Last style in the list, just finish | ||||
| 	 (values (reverse sides) | ||||
| 		 (reverse line-style-spec) | ||||
| 		 lst) | ||||
| 	 (let ((tk (car lst))) | ||||
| 	   (cond ((and sides-done? | ||||
| 		       (keyword? tk)) | ||||
| 		  ;; Next side spec continues | ||||
| 		  (values (reverse sides) | ||||
| 			  (reverse line-style-spec) | ||||
| 			  lst)) | ||||
| 		 ((keyword? tk) | ||||
| 		  ;; Still adding sides | ||||
| 		  (loop (cons tk sides) | ||||
| 			sides-done? ;; Should be always #f | ||||
| 			line-style-spec ;; Should always be '() | ||||
| 			(cdr lst))) | ||||
| 		 (else | ||||
| 		  ;; Line specification | ||||
| 		  (loop sides | ||||
| 			#t | ||||
| 			(cons tk line-style-spec) | ||||
| 			(cdr lst)))))))) | ||||
| 
 | ||||
|  ;; Compiles correct box drawing cell and merges it with given cell | ||||
|  (define (combine-cell-border cell sides line-style-spec) | ||||
|    (let ((line-style (spec->line-style line-style-spec))) | ||||
|      ;; Overide given sides | ||||
|      (if (null? sides) | ||||
| 	 (make-line-cell line-style | ||||
| 			 line-style | ||||
| 			 line-style | ||||
| 			 line-style) | ||||
| 	 (let loop ((sides sides) | ||||
| 		    (cell cell)) | ||||
| 	   (if (null? sides) | ||||
| 	       cell | ||||
| 	       (loop (cdr sides) | ||||
| 		     (case (car sides) | ||||
| 		       ((#:north #:top #:up) (set-line-cell-north cell line-style)) | ||||
| 		       ((#:west #:left) (set-line-cell-west cell line-style)) | ||||
| 		       ((#:east #:right) (set-line-cell-east cell line-style)) | ||||
| 		       ((#:south #:bottom #:down) (set-line-cell-south cell line-style)) | ||||
| 		       (else cell)))))))) | ||||
| 
 | ||||
|  ;; Parses border style specification for single cell, returns | ||||
|  ;; box-drawing cell with slightly different meaning of NWES sides. | ||||
|  (define (parse-table-cell-border-style spec-arg) | ||||
|    (let ((spec (if (list? spec-arg) | ||||
| 		   spec-arg | ||||
| 		   (list spec-arg)))) | ||||
|      (let loop ((spec spec) | ||||
| 		(cell line-cell-none)) | ||||
|        (if (null? spec) | ||||
| 	   ;; Finished, return, whatever we accumulated | ||||
| 	   cell | ||||
| 	   (let-values (((sides line-style-spec rest) | ||||
| 			 (table-border-style-consume spec))) | ||||
| 	     (loop rest | ||||
| 		   (combine-cell-border cell sides line-style-spec))))))) | ||||
| 
 | ||||
|  ;; Converts all "cells" using parse-table-cell-border-style | ||||
|  (define (compile-table-style-spec spec) | ||||
|    (map (lambda (row) | ||||
| 	  (if (template-expansion-token? row) | ||||
| 	      row | ||||
| 	      (map (lambda (cell) | ||||
| 		     (if (template-expansion-token? cell) | ||||
| 			 cell | ||||
| 			 (parse-table-cell-border-style cell))) | ||||
| 		   row))) | ||||
| 	spec)) | ||||
| 
 | ||||
|  ;; Converts the template skipping dots, expands the result | ||||
|  (define (expand-table-style spec width height) | ||||
|    (let ((cspec (compile-table-style-spec spec))) | ||||
|      (expand-template-list | ||||
|       (map (lambda (row) | ||||
| 	     (if (template-expansion-token? row) | ||||
| 		 row | ||||
| 		 (expand-template-list row width))) | ||||
| 	   cspec) | ||||
|       height))) | ||||
| 
 | ||||
|  ;; Module self-tests | ||||
|  (define (table-style-tests!) | ||||
|    (run-tests | ||||
|     table-style | ||||
|     (test-equal? parse-table-cell-border-style | ||||
| 		 (parse-table-cell-border-style 'light) | ||||
| 		 #b1001100110011001) | ||||
|     (test-equal? parse-table-cell-border-style | ||||
| 		 (parse-table-cell-border-style '(light dashed)) | ||||
| 		 #b101010101010101) | ||||
|     (test-equal? parse-table-cell-border-style | ||||
| 		 (parse-table-cell-border-style '(light #:top none)) | ||||
| 		 #b1001100110010000) | ||||
|     (test-equal? compile-table-style-spec | ||||
| 		 (compile-table-style-spec | ||||
| 		  '(((heavy dashed) ...) | ||||
| 		    ((light #:left #:right none) ...) | ||||
| 		    ... | ||||
| 		    (dashed ...))) | ||||
| 		 '((#b0110011001100110 ...) | ||||
| 		   (#b1001000000001001 ...) | ||||
| 		   ... | ||||
| 		   (#b0101010101010101 ...))) | ||||
|     (test-equal? expand-table-style | ||||
| 		 (expand-table-style | ||||
| 		  '(((heavy dashed) ...) | ||||
| 		    ((light #:left #:right none) ...) | ||||
| 		    ... | ||||
| 		    (dashed ...)) | ||||
| 		  4 4) | ||||
| 		 '((#b0110011001100110 #b0110011001100110 #b0110011001100110 #b0110011001100110) | ||||
| 		   (#b1001000000001001 #b1001000000001001 #b1001000000001001 #b1001000000001001) | ||||
| 		   (#b1001000000001001 #b1001000000001001 #b1001000000001001 #b1001000000001001) | ||||
| 		   (#b0101010101010101 #b0101010101010101 #b0101010101010101 #b0101010101010101))) | ||||
|     )) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										112
									
								
								src/table.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/table.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | |||
| ;; | ||||
| ;; table.scm | ||||
| ;; | ||||
| ;; Table surface API. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit table)) | ||||
| 
 | ||||
| (module | ||||
|  table | ||||
|  ( | ||||
|   print-table | ||||
|   table->string | ||||
|   table->string-list | ||||
|   table->sgr-lists | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 (chicken string) | ||||
| 	 sgr-list | ||||
| 	 sgr-block | ||||
| 	 racket-kwargs | ||||
| 	 table-processor | ||||
| 	 table-border | ||||
| 	 table-style) | ||||
| 
 | ||||
|  (define (print-table . args) | ||||
|    (print (apply table->string args))) | ||||
| 
 | ||||
|  (define (table->string . args) | ||||
|    (string-intersperse | ||||
|     (apply table->string-list args) | ||||
|     "\n")) | ||||
| 
 | ||||
|  (define (table->string-list . args) | ||||
|    (map sgr-list->string | ||||
| 	(apply table->sgr-lists args))) | ||||
| 
 | ||||
|  (define (merge-rows ptbl borders col-separators unicode?) | ||||
|    (let loop ((rows ptbl) | ||||
| 	      (borders borders) | ||||
| 	      (res '())) | ||||
|      (if (null? rows) | ||||
| 	 (reverse res) | ||||
| 	 (loop (cdr rows) | ||||
| 	       (cdr borders) | ||||
| 	       (cons (table-row-merge (car rows) | ||||
| 				      col-separators | ||||
| 				      (car borders) | ||||
| 				      unicode?) | ||||
| 		     res))))) | ||||
| 
 | ||||
|  (define* (table->sgr-lists tbl | ||||
| 			    #:border (border-spec '((none ...) ...)) | ||||
| 			    #:widths (widths-spec '(0 ...)) | ||||
| 			    #:width (width #f) | ||||
| 			    #:unicode? (unicode? #t)) | ||||
|    (let-values (((ptbl col-widths) | ||||
| 		 (table-prepare tbl width widths-spec))) | ||||
|      (let* ((num-columns (length (car tbl))) | ||||
| 	    (num-rows (length tbl)) | ||||
| 	    (borders (expand-table-style border-spec num-columns num-rows)) | ||||
| 	    (col-separators (table-col-separators? borders)) | ||||
| 	    (rows (merge-rows ptbl borders col-separators unicode?))) | ||||
|        (let loop ((rows rows) | ||||
| 		  (borders borders) | ||||
| 		  (res '()) | ||||
| 		  (prev-borders #f)) | ||||
| 	 (if (null? rows) | ||||
| 	     (apply append | ||||
| 		    (reverse (if (table-border-between-rows? prev-borders #f) | ||||
| 				 (cons (table-rows-border col-widths | ||||
| 							  prev-borders | ||||
| 							  #f | ||||
| 							  col-separators | ||||
| 							  unicode?) | ||||
| 				       res) | ||||
| 				 res))) | ||||
| 	     (loop (cdr rows) | ||||
| 		   (cdr borders) | ||||
| 		   (if (table-border-between-rows? prev-borders (car borders)) | ||||
| 		       (cons (car rows) | ||||
| 			     (cons (table-rows-border col-widths | ||||
| 						      prev-borders | ||||
| 						      (car borders) | ||||
| 						      col-separators | ||||
| 						      unicode?) | ||||
| 				   res)) | ||||
| 		       (cons (car rows) res)) | ||||
| 		   (car borders) | ||||
| 		   )))))) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										153
									
								
								src/template-list-expander.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/template-list-expander.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,153 @@ | |||
| ;; | ||||
| ;; template-list-expander.scm | ||||
| ;; | ||||
| ;; Dynamic length lists based on simple templates with head and tail | ||||
| ;; patterns. | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit template-list-expander)) | ||||
| 
 | ||||
| (module | ||||
|  template-list-expander | ||||
|  ( | ||||
|   template-expansion-token? | ||||
| 
 | ||||
|   expand-template-list | ||||
| 
 | ||||
|   template-list-expander-tests! | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 testing) | ||||
| 
 | ||||
|  (define (template-expansion-token? token) | ||||
|    (and (symbol? token) | ||||
| 	(let* ((tokenstr (symbol->string token)) | ||||
| 	       (tokenlen (string-length tokenstr))) | ||||
| 	  (and (>= tokenlen 3) | ||||
| 	       (string=? (substring tokenstr 0 3) "..."))))) | ||||
| 
 | ||||
|  (define (split-template-list temp-lst) | ||||
|    (let loop ((lst temp-lst) | ||||
| 	      (rhead '())) | ||||
|      (if (null? lst) | ||||
| 	 (values temp-lst '() '()) | ||||
| 	 (let* ((token (car lst)) | ||||
| 		(tokenstr (if (symbol? token) | ||||
| 			      (symbol->string token) | ||||
| 			      "")) | ||||
| 		(tokenlen (if (symbol? token) | ||||
| 			      (string-length tokenstr) | ||||
| 			      0))) | ||||
| 	   (cond ((and (symbol? token) | ||||
| 		       (>= tokenlen 3) | ||||
| 		       (string=? (substring tokenstr 0 3) "...")) | ||||
| 		  (let rloop ((cnt (- tokenlen 2)) | ||||
| 			      (rhead2 rhead) | ||||
| 			      (rrep '())) | ||||
| 		    (if (= cnt 0) | ||||
| 			(values (reverse rhead2) | ||||
| 				rrep | ||||
| 				(cdr lst)) | ||||
| 			(rloop (sub1 cnt) | ||||
| 			       (cdr rhead2) | ||||
| 			       (cons (car rhead2) rrep))))) | ||||
| 		 (else | ||||
| 		  (loop (cdr lst) | ||||
| 			(cons token rhead)))))))) | ||||
| 
 | ||||
|  (define (repeat-list-for lst len) | ||||
|    (let loop ((cnt len) | ||||
| 	      (rep lst) | ||||
| 	      (res '())) | ||||
|      (if (= cnt 0) | ||||
| 	 (reverse res) | ||||
| 	 (loop (sub1 cnt) | ||||
| 	       (if (null? (cdr rep)) lst (cdr rep)) | ||||
| 	       (cons (car rep) res))))) | ||||
| 
 | ||||
|  (define (take-from-list lst cnt) | ||||
|    (let loop ((lst lst) | ||||
| 	      (res '()) | ||||
| 	      (cnt cnt)) | ||||
|      (if (= cnt 0) | ||||
| 	 (reverse res) | ||||
| 	 (loop (cdr lst) | ||||
| 	       (cons (car lst) res) | ||||
| 	       (sub1 cnt))))) | ||||
| 
 | ||||
|  (define (expand-template-list lst len) | ||||
|    (let-values (((head rep tail) (split-template-list lst))) | ||||
|      (let ((headlen (length head)) | ||||
| 	   (taillen (length tail))) | ||||
|        ;;(print "----------------") | ||||
|        ;;(print "head = " head) | ||||
|        ;;(print "rep = " rep) | ||||
|        ;;(print "tail = " tail) | ||||
|        (cond | ||||
| 	((= len headlen) | ||||
| 	 head) | ||||
| 	((< len headlen) | ||||
| 	 (take-from-list head len)) | ||||
| 	(else | ||||
| 	 (let ((head+taillen (+ headlen taillen))) | ||||
| 	   (cond | ||||
| 	    ((= len head+taillen) | ||||
| 	     (append head tail)) | ||||
| 	    ((< len head+taillen) | ||||
| 	     (append head (take-from-list tail (- len head+taillen)))) | ||||
| 	    (else | ||||
| 	     (append head | ||||
| 		     (repeat-list-for rep (- len head+taillen)) | ||||
| 		     tail))))))))) | ||||
| 
 | ||||
|  (define (template-list-expander-tests!) | ||||
|    (run-tests | ||||
|     template-list-expander | ||||
|     (test-equal? expand-template-list | ||||
| 		 (expand-template-list '() 0) | ||||
| 		 '()) | ||||
|     (test-equal? expand-template-list | ||||
| 		 (expand-template-list '((a) ... (b)) 5) | ||||
|                  '((a) (a) (a) (a) (b))) | ||||
|     (test-equal? expand-template-list | ||||
| 		 (expand-template-list '((c) (a) (d) ... (b) (e)) 9) | ||||
|                  '((c) (a) (d) (d) (d) (d) (d) (b) (e))) | ||||
|     (test-equal? expand-template-list | ||||
| 		 (expand-template-list '((c) (a) (d) .... (b) (e)) 9) | ||||
|                  '((c) (a) (d) (a) (d) (a) (d) (b) (e))) | ||||
|     (test-true template-expansion-token? | ||||
| 	       (template-expansion-token? '...)) | ||||
|     (test-true template-expansion-token? | ||||
| 	       (template-expansion-token? '....)) | ||||
|     (test-false template-expansion-token? | ||||
| 		(template-expansion-token? '..)) | ||||
|     (test-false template-expansion-token? | ||||
| 		(template-expansion-token? 'hello)) | ||||
|     (test-false template-expansion-token? | ||||
| 		(template-expansion-token? "hello")) | ||||
|     (test-equal? expand-template-list | ||||
| 		 (expand-template-list '(a b ... c) 2) | ||||
| 		 '(a c)) | ||||
|     )) | ||||
| 
 | ||||
|  ) | ||||
							
								
								
									
										305
									
								
								src/util-utf8.scm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								src/util-utf8.scm
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,305 @@ | |||
| ;; | ||||
| ;; util-utf8.scm | ||||
| ;; | ||||
| ;; UTF-8 support | ||||
| ;; | ||||
| ;; ISC License | ||||
| ;; | ||||
| ;; Copyright 2023 Dominik Pantůček <dominik.pantucek@trustica.cz> | ||||
| ;; | ||||
| ;; Permission to use, copy, modify, and/or distribute this software | ||||
| ;; for any purpose with or without fee is hereby granted, provided | ||||
| ;; that the above copyright notice and this permission notice appear | ||||
| ;; in all copies. | ||||
| ;;  | ||||
| ;; THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL | ||||
| ;; WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED | ||||
| ;; WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE | ||||
| ;; AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR | ||||
| ;; CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | ||||
| ;; OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, | ||||
| ;; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
| ;; CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
| ;; | ||||
| 
 | ||||
| (declare (unit util-utf8)) | ||||
| 
 | ||||
| (module | ||||
|  util-utf8 | ||||
|  ( | ||||
|   utf8-char->string | ||||
|    | ||||
|   make-utf8-string | ||||
|    | ||||
|   string-append-utf8-char | ||||
|    | ||||
|   utf8-string->lists | ||||
|    | ||||
|   utf8-bytes->lists | ||||
| 
 | ||||
|   utf8-string-length | ||||
| 
 | ||||
|   utf8-string-next-char | ||||
| 
 | ||||
|   utf8-string->list | ||||
|   list->utf8-string | ||||
| 
 | ||||
|   util-utf8-tests! | ||||
|   ) | ||||
| 
 | ||||
|  (import scheme | ||||
| 	 (chicken base) | ||||
| 	 (chicken bitwise) | ||||
| 	 testing) | ||||
| 
 | ||||
|  ;; Encodes given character as UTF-8 | ||||
|  (define (utf8-char->string ch) | ||||
|    (let ((n (char->integer ch))) | ||||
|      (cond ((>= n #x10000) | ||||
| 	    (let ((r (make-string 4)) | ||||
| 		  (b0 (bitwise-and n #b111111)) | ||||
| 		  (b1 (bitwise-and (arithmetic-shift n -6) #b111111)) | ||||
| 		  (b2 (bitwise-and (arithmetic-shift n -12) #b111111)) | ||||
| 		  (b3 (bitwise-and (arithmetic-shift n -18) #b111))) | ||||
| 	      (string-set! r 0 (integer->char (bitwise-ior b3 #b11110000))) | ||||
| 	      (string-set! r 1 (integer->char (bitwise-ior b2 #b10000000))) | ||||
| 	      (string-set! r 2 (integer->char (bitwise-ior b1 #b10000000))) | ||||
| 	      (string-set! r 3 (integer->char (bitwise-ior b0 #b10000000))) | ||||
| 	      r)) | ||||
| 	   ((>= n #x800) | ||||
| 	    (let ((r (make-string 3)) | ||||
| 		  (b0 (bitwise-and n #b111111)) | ||||
| 		  (b1 (bitwise-and (arithmetic-shift n -6) #b111111)) | ||||
| 		  (b2 (bitwise-and (arithmetic-shift n -12) #b1111))) | ||||
| 	      (string-set! r 0 (integer->char (bitwise-ior b2 #b11100000))) | ||||
| 	      (string-set! r 1 (integer->char (bitwise-ior b1 #b10000000))) | ||||
| 	      (string-set! r 2 (integer->char (bitwise-ior b0 #b10000000))) | ||||
| 	      r)) | ||||
| 	   ((>= n #x80) | ||||
| 	    (let ((r (make-string 2)) | ||||
| 		  (b0 (bitwise-and n #b111111)) | ||||
| 		  (b1 (bitwise-and (arithmetic-shift n -6) #b11111))) | ||||
| 	      (string-set! r 0 (integer->char (bitwise-ior b1 #b11000000))) | ||||
| 	      (string-set! r 1 (integer->char (bitwise-ior b0 #b10000000))) | ||||
| 	      r)) | ||||
| 	   (else | ||||
| 	    (make-string 1 ch))))) | ||||
| 
 | ||||
|  ;; UTF-8 version of make-string | ||||
|  (define (make-utf8-string n . chs) | ||||
|    (let* ((ch (if (null? chs) #\space (car chs))) | ||||
| 	  (s (utf8-char->string ch)) | ||||
| 	  (sl (string-length s)) | ||||
| 	  (rl (* n sl)) | ||||
| 	  (r (make-string rl))) | ||||
|        (let loop ((ri 0) | ||||
| 		  (si 0)) | ||||
| 	 (if (= ri rl) | ||||
| 	     r | ||||
| 	     (let ((nsi (add1 si))) | ||||
| 	       (string-set! r ri (string-ref s si)) | ||||
| 	       (loop (add1 ri) | ||||
| 		     (if (= nsi sl) 0 nsi))))))) | ||||
| 
 | ||||
|  ;; UTF-8 character append | ||||
|  (define (string-append-utf8-char s ch) | ||||
|    (string-append s (utf8-char->string ch))) | ||||
| 
 | ||||
|  ;; Converts a UTF-8 string into two lists: list of UTF-8 characters | ||||
|  ;; of the string and a list of remaining bytes (as integers). | ||||
|  (define (utf8-string->lists str) | ||||
|    (utf8-bytes->lists | ||||
|     (map char->integer | ||||
| 	 (string->list str)))) | ||||
| 
 | ||||
|  ;; The same as above but accepts a list of bytes (as integers) | ||||
|  (define (utf8-bytes->lists chars) | ||||
|    (let loop ((bytes chars) | ||||
| 	      (rpending '()) | ||||
| 	      (pending 0) | ||||
| 	      (expected #f) | ||||
| 	      (res '())) | ||||
|      (if (null? bytes) | ||||
| 	 (values (reverse res) | ||||
| 		 (reverse rpending)) | ||||
| 	 (let ((byte (car bytes))) | ||||
| 	   (cond (expected | ||||
| 		  ;; Decode UTF-8 sequence | ||||
| 		  (cond ((= expected 1) | ||||
| 			 ;; Last byte | ||||
| 			 (let ((char (integer->char (bitwise-ior pending | ||||
| 								 (bitwise-and byte #b111111))))) | ||||
| 			   (loop (cdr bytes) | ||||
| 				 '() | ||||
| 				 0 | ||||
| 				 #f | ||||
| 				 (cons char res)))) | ||||
| 			(else | ||||
| 			 ;; Intermediate bytes | ||||
| 			 (loop (cdr bytes) | ||||
| 			       (cons byte rpending) | ||||
| 			       (arithmetic-shift (bitwise-ior pending | ||||
| 							      (bitwise-and byte #b111111)) 6) | ||||
| 			       (sub1 expected) | ||||
| 			       res)))) | ||||
| 		 (else | ||||
| 		  ;; ASCII or first of UTF-8 sequence | ||||
| 		  (cond ((= (bitwise-and byte #b10000000) 0) | ||||
| 			 ;; ASCII | ||||
| 			 (loop (cdr bytes) | ||||
| 			       '() | ||||
| 			       0 | ||||
| 			       #f | ||||
| 			       (cons (integer->char byte) res))) | ||||
| 			(else | ||||
| 			 ;; First byte of UTF-8 sequence | ||||
| 			 (let-values | ||||
| 			     (((first-byte char-bytes) | ||||
| 			       (cond ((= (bitwise-and byte #b11000000) #b11000000) | ||||
| 				      (values (bitwise-and byte #b11111) | ||||
| 					      2)) | ||||
| 				     ((= (bitwise-and byte #b11100000) #b11100000) | ||||
| 				      (values (bitwise-and byte #b1111) | ||||
| 					      3)) | ||||
| 				     ((= (bitwise-and byte #b11110000) #b11110000) | ||||
| 				      (values (bitwise-and byte #b111) | ||||
| 					      4))))) | ||||
| 			   (loop (cdr bytes) | ||||
| 				 (list byte) | ||||
| 				 (arithmetic-shift first-byte 6) | ||||
| 				 (sub1 char-bytes) | ||||
| 				 res)))))))))) | ||||
| 
 | ||||
|  ;; Returns the position right after the character at specified | ||||
|  ;; position. | ||||
|  (define (utf8-string-next-char str . sis) | ||||
|    (let ((len (string-length str)) | ||||
| 	 (si0 (if (null? sis) 0 (car sis)))) | ||||
|      (let loop ((si si0) | ||||
| 		(pc 0)) | ||||
|        (if (or (= si len) | ||||
| 	       (and (> si si0) | ||||
| 		    (eq? pc 0))) | ||||
| 	   si | ||||
| 	   (let ((b (char->integer (string-ref str si)))) | ||||
| 	     (loop (add1 si) | ||||
| 		   (if (= pc 0) | ||||
| 		       (if (= (bitwise-and b #b11111000) #b11110000) | ||||
| 			   3 | ||||
| 			   (if (= (bitwise-and b #b11110000) #b11100000) | ||||
| 			       2 | ||||
| 			       (if (= (bitwise-and b 128) 128) | ||||
| 				   1 | ||||
| 				   0))) | ||||
| 		       (if (= (bitwise-and b 128) 128) | ||||
| 			   (sub1 pc) | ||||
| 			   0)))))))) | ||||
| 
 | ||||
|  ;; Calculates the length of given UTF-8 string | ||||
|  (define (utf8-string-length s) | ||||
|    (let ((l (string-length s))) | ||||
|      (let loop ((si 0) | ||||
| 		(ci 0) | ||||
| 		(pc 0)) | ||||
|        (if (= si l) | ||||
| 	   ci | ||||
| 	   (let ((b (char->integer (string-ref s si)))) | ||||
| 	     (loop (add1 si) | ||||
| 		   (if (or (= pc 0) | ||||
| 			   (= (bitwise-and b 128) 0)) | ||||
| 		       (add1 ci) | ||||
| 		       ci) | ||||
| 		   (if (= pc 0) | ||||
| 		       (if (= (bitwise-and b #b11111000) #b11110000) | ||||
| 			   3 | ||||
| 			   (if (= (bitwise-and b #b11110000) #b11100000) | ||||
| 			       2 | ||||
| 			       (if (= (bitwise-and b 128) 128) | ||||
| 				   1 | ||||
| 				   0))) | ||||
| 		       (if (= (bitwise-and b 128) 128) | ||||
| 			   (sub1 pc) | ||||
| 			   0)))))))) | ||||
| 
 | ||||
|  ;; Converts utf8 string to list of unicode characters | ||||
|  (define (utf8-string->list s) | ||||
|    (let-values (((lst _) (utf8-string->lists s))) | ||||
|      lst)) | ||||
| 
 | ||||
|  ;; Prepends 1-byte characters representing utf8 encoding of given | ||||
|  ;; unicode character to the list | ||||
|  (define (prepend-unicode-char-to-utf8-list ch lst) | ||||
|    (let ((n (char->integer ch))) | ||||
|      (cond ((>= n #x10000) | ||||
| 	    (let ((r (make-string 4)) | ||||
| 		  (b0 (bitwise-and n #b111111)) | ||||
| 		  (b1 (bitwise-and (arithmetic-shift n -6) #b111111)) | ||||
| 		  (b2 (bitwise-and (arithmetic-shift n -12) #b111111)) | ||||
| 		  (b3 (bitwise-and (arithmetic-shift n -18) #b111))) | ||||
| 	      (cons (integer->char (bitwise-ior b0 #b10000000)) | ||||
| 		    (cons (integer->char (bitwise-ior b1 #b10000000)) | ||||
| 			  (cons (integer->char (bitwise-ior b2 #b10000000)) | ||||
| 				(cons (integer->char (bitwise-ior b3 #b11110000)) | ||||
| 				      lst)))))) | ||||
| 	   ((>= n #x800) | ||||
| 	    (let ((r (make-string 3)) | ||||
| 		  (b0 (bitwise-and n #b111111)) | ||||
| 		  (b1 (bitwise-and (arithmetic-shift n -6) #b111111)) | ||||
| 		  (b2 (bitwise-and (arithmetic-shift n -12) #b1111))) | ||||
| 	      (cons (integer->char (bitwise-ior b0 #b10000000)) | ||||
| 		    (cons (integer->char (bitwise-ior b1 #b10000000)) | ||||
| 			  (cons (integer->char (bitwise-ior b2 #b11100000)) | ||||
| 				lst))))) | ||||
| 	   ((>= n #x80) | ||||
| 	    (let ((r (make-string 2)) | ||||
| 		  (b0 (bitwise-and n #b111111)) | ||||
| 		  (b1 (bitwise-and (arithmetic-shift n -6) #b11111))) | ||||
| 	      (cons (integer->char (bitwise-ior b0 #b10000000)) | ||||
| 		    (cons (integer->char (bitwise-ior b1 #b11000000)) | ||||
| 			  lst)))) | ||||
| 	   (else | ||||
| 	    (cons ch lst))))) | ||||
| 
 | ||||
|  ;; Converts list of unicode characters into utf8 string | ||||
|  (define (list->utf8-string lst) | ||||
|    (let loop ((lst lst) | ||||
| 	      (res '())) | ||||
|      (if (null? lst) | ||||
| 	 (list->string (reverse res)) | ||||
| 	 (loop (cdr lst) | ||||
| 	       (prepend-unicode-char-to-utf8-list (car lst) res))))) | ||||
| 
 | ||||
|  ;; Module self-tests | ||||
|  (define (util-utf8-tests!) | ||||
|    (run-tests | ||||
|     util-utf8 | ||||
|     (test-equal? utf8-char->string | ||||
| 		 (utf8-char->string #\ř) | ||||
| 		 "ř") | ||||
|     (test-equal? make-utf8-string | ||||
| 		 (make-utf8-string 4 #\č) | ||||
| 		 "čččč") | ||||
|     (test-equal? string-append-utf8-char | ||||
| 		 (string-append-utf8-char "ččč" #\ř) | ||||
| 		 "čččř") | ||||
|     (test-equal? utf8-string->list | ||||
| 		 (utf8-string->list "ěščř") | ||||
| 		 '(#\ě #\š #\č #\ř)) | ||||
|     (test-equal? list->utf8-string | ||||
| 		 (list->utf8-string '(#\ě #\š #\č #\ř)) | ||||
| 		 "ěščř") | ||||
|     (test-equal? utf8-string-length | ||||
| 		 (utf8-string-length "ěščř") | ||||
| 		 4) | ||||
|     (test-equal? utf8-string-length | ||||
| 		 (utf8-string-length "ěxšy") | ||||
| 		 4) | ||||
|     (test-equal? utf8-string-next-char | ||||
| 		 (utf8-string-next-char "ěščř") | ||||
| 		 2) | ||||
|     (test-equal? utf8-string-next-char | ||||
| 		 (utf8-string-next-char "ěščř" 2) | ||||
| 		 4) | ||||
|     )) | ||||
| 
 | ||||
|  ) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue