diff --git a/quad2/atomize.rkt b/quad2/atomize.rkt deleted file mode 100644 index 1b748045..00000000 --- a/quad2/atomize.rkt +++ /dev/null @@ -1,25 +0,0 @@ -#lang debug racket/base -(require racket/match - "compile.rkt" - "quad.rkt") -(provide atomize) - -#| - -My idea here is that instead of comparing attrs by using eq? with a hash, -we can use eq? (not equal?) on an association list -the idea being that if only update the attrs by consing onto the front, -then the process is strictly accretive, that is: -we are never allocating a fresh list to hold existing values -For instance, the top-level attrs represent a list object -that will eventually be the tail of the attrs in every atomized quad. -|# - -(define-pass (atomize q) - #:pre quad? - #:post (list-of quad?) - (match (quad-elems q) - [(cons str _) - (for/list ([c (in-string str)]) - (make-quad #:attrs (make-quad-attrs (list (cons 'char c)))))] - [_ (error 'atomize-branch-unimplemented)])) \ No newline at end of file diff --git a/quad2/compile.rkt b/quad2/compile.rkt deleted file mode 100644 index e0fe04b7..00000000 --- a/quad2/compile.rkt +++ /dev/null @@ -1,31 +0,0 @@ -#lang debug racket/base -(require racket/match - "quad.rkt") -(provide (all-defined-out)) - -(struct compiler (passes) - #:constructor-name make-compiler - #:guard (λ (procs name) - (unless ((list-of procedure?) procs) - (raise-argument-error 'bad-input-to-compiler-constructor "list of procedures" procs)) - procs) - #:property prop:procedure - (λ (self input) ((apply compose1 (reverse (compiler-passes self))) input))) - -(define (compiler-append c passes) - (make-compiler (append (compiler-passes c) passes))) - -(define-syntax-rule (define-pass (PASS-NAME ARG OTHER-ARG ...) - #:pre PRECOND-PROC - #:post POSTCOND-PROC - EXPRS ...) - (define PASS-NAME - (make-compiler - (list (procedure-rename - (λ (ARG OTHER-ARG ...) - (unless (PRECOND-PROC ARG) - (raise-argument-error 'PASS-NAME (format "~a" 'PRECOND-PROC) ARG)) - (define res (let () EXPRS ...)) - (unless (POSTCOND-PROC res) - (raise-argument-error 'PASS-NAME (format "~a" 'POSTCOND-PROC) res)) - res) 'PASS-NAME))))) \ No newline at end of file diff --git a/quad2/draw.rkt b/quad2/draw.rkt index cd65976d..8ee08a65 100644 --- a/quad2/draw.rkt +++ b/quad2/draw.rkt @@ -4,7 +4,7 @@ racket/format racket/match "quad.rkt" - "compile.rkt" + "pipeline.rkt" "struct.rkt" "layout.rkt") (provide (all-defined-out)) diff --git a/quad2/layout.rkt b/quad2/layout.rkt index 6595bc1e..87d1d4bb 100644 --- a/quad2/layout.rkt +++ b/quad2/layout.rkt @@ -1,6 +1,6 @@ #lang debug racket/base (require racket/contract - "compile.rkt" + "pipeline.rkt" "quad.rkt") (provide layout) diff --git a/quad2/linearize.rkt b/quad2/linearize.rkt new file mode 100644 index 00000000..6c2eeced --- /dev/null +++ b/quad2/linearize.rkt @@ -0,0 +1,87 @@ +#lang debug racket/base +(require racket/match + racket/hash + racket/list + racket/string + "pipeline.rkt" + "quad.rkt") +(provide (all-defined-out)) + +(define (simple-quad? x) (and (quad? x) (<= (length (quad-elems x)) 1))) + +(define-pass (linearize q) + ;; convert a single quad into a list of quads, with the attributes propagated downward + ;; every resulting quad should have at most one element + #:pre quad? + #:post (list-of simple-quad?) + (let loop ([q q][attrs-context (make-quad-attrs)]) ;; returns (list-of quad?) + (define current-attrs (let ([qas (make-quad-attrs)]) + (hash-union! #:combine (λ (v1 v2) v2) qas attrs-context (quad-attrs q)) + qas)) + (define (mq es) (make-quad #:tag (quad-tag q) #:attrs current-attrs #:elems es)) + (match (quad-elems q) + [(? null?) (list (mq null))] + [(? pair? elems) + (apply append (for/list ([e (in-list elems)]) + (cond + [(quad? e) (loop e current-attrs)] + [else (list (mq (list e)))])))]))) + +(module+ test + (define q (make-quad #:attrs (hasheq 'foo 42) #:elems (list (make-quad #:elems (list "Hi" " idiot" (make-quad #:attrs (hasheq 'bar 84) #:elems '("There")) " Eve" "ry" "one" (make-quad #:attrs (hasheq 'zam 108) #:elems null)))))) + (define lqs (linearize q)) + lqs) + + +(define-pass (merge-adjacent-strings sqs) + ;; merge quads with the same attrs, and one or zero string elements, + ;; into a single quad with one string element + #:pre (list-of simple-quad?) + #:post (list-of simple-quad?) + (let merge ([sqs sqs]) + (match sqs + [_ #:when (<= (length sqs) 1) ; nothing to merge + sqs] + [(cons e0 rest) + ;; because we copied attrs downward in linearize, we can use eq? to compare if they're the same + ;; (instead of a key-by-key comparison) + (define (attrs-same? e) (eq? (quad-attrs e0) (quad-attrs e))) + (define-values (head tail) (splitf-at rest attrs-same?)) + (cons + (cond + [(null? head) e0] + [else + (define qs-to-merge (cons e0 head)) + (make-quad #:tag (quad-tag e0) + #:attrs (quad-attrs e0) + #:elems (list (string-join (append-map quad-elems qs-to-merge) "")))]) + (merge tail))]))) + +(module+ test + (define mlqs (merge-adjacent-strings lqs)) + mlqs) + + +(define-pass (split-whitespace qs) + #:pre (list-of simple-quad?) + #:post (list-of simple-quad?) + (define whitespace-pat #px"\\s+") + (define word-space " ") + (apply append + (for/list ([q (in-list qs)]) + (match (quad-elems q) + [(list (? string? str)) + (define tag (quad-tag q)) + (define attrs (quad-attrs q)) + ;; the "gaps" (parts that don't match pattern) are guaranteed to be at even indexes + ;; If string starts with a "gap", a zero-length separator is appended to the start. + ;; so we just ignore those. + (for/list ([(substr idx) (in-indexed (regexp-match* whitespace-pat str #:gap-select? #t))] + #:unless (zero? (string-length substr))) + (make-quad #:tag tag + #:attrs attrs + #:elems (list (if (even? idx) substr word-space))))] + [_ (list q)])))) + +(module+ test + (split-whitespace mlqs)) diff --git a/quad2/main.rkt b/quad2/main.rkt index 99782c00..4053938d 100644 --- a/quad2/main.rkt +++ b/quad2/main.rkt @@ -2,8 +2,8 @@ (require "layout.rkt" "render.rkt" "quad.rkt" - "compile.rkt" - "atomize.rkt" + "pipeline.rkt" + "linearize.rkt" "layout.rkt" "draw.rkt" racket/string @@ -17,17 +17,32 @@ [(and (list (? quad?) ...) qs) (make-quad #:elems qs)] [other (make-quad #:elems (list other))])) -(define quad-compile (make-compiler (list +(define-pass (make-weirdo-char-quads qs) + #:pre (list-of simple-quad?) + #:post (list-of simple-quad?) + (apply append + (for/list ([q (in-list qs)]) + (match q + [(quad _ _ (list (? string? str))) + (for/list ([c (in-string str)]) + (define new-attrs (make-hasheq (cons (cons 'char c) (hash->list (quad-attrs q))))) + (make-quad #:tag (quad-tag q) + #:attrs new-attrs + #:elems null))] + [_ (list q)])))) + +(define quad-compile (make-pipeline (list bootstrap - atomize + linearize + merge-adjacent-strings + split-whitespace + make-weirdo-char-quads layout - make-drawing-insts))) - -(define quad-compile-to-stack (compiler-append quad-compile - (list stackify))) + make-drawing-insts + stackify))) (define drawing-insts (parameterize ([current-wrap-width 13]) - (quad-compile-to-stack "Hello this is the earth"))) + (quad-compile "Hello this is the earth"))) (displayln drawing-insts) diff --git a/quad2/pipeline.rkt b/quad2/pipeline.rkt new file mode 100644 index 00000000..70309f58 --- /dev/null +++ b/quad2/pipeline.rkt @@ -0,0 +1,37 @@ +#lang debug racket/base +(require racket/match + (for-syntax racket/base) + "quad.rkt") +(provide (all-defined-out)) + +(struct pipeline (passes) + #:constructor-name make-pipeline + #:guard (λ (procs name) + (unless ((list-of procedure?) procs) + (raise-argument-error 'bad-input-to-compiler-constructor "list of procedures" procs)) + procs) + #:property prop:procedure + (λ args (apply (apply compose1 (reverse (pipeline-passes (car args)))) (cdr args)))) + +(define (compiler-append c passes) + (make-pipeline (append (pipeline-passes c) passes))) + +(define-syntax (define-pass stx) + (syntax-case stx () + [(_ (PASS-NAME ARG OTHER-ARG ...) + #:pre PRECOND-PROC + #:post POSTCOND-PROC + EXPRS ...) + #`(define PASS-NAME + (make-pipeline + (list + (procedure-rename + #,(syntax/loc stx + (λ (ARG OTHER-ARG ...) + (unless (PRECOND-PROC ARG) + (raise-argument-error 'PASS-NAME (format "~a" 'PRECOND-PROC) ARG)) + (define res (let () EXPRS ...)) + (unless (POSTCOND-PROC res) + (raise-argument-error 'PASS-NAME (format "~a" 'POSTCOND-PROC) res)) + res)) + 'PASS-NAME))))])) \ No newline at end of file diff --git a/quad2/quad.rkt b/quad2/quad.rkt index 6be24ac0..fa3deeae 100644 --- a/quad2/quad.rkt +++ b/quad2/quad.rkt @@ -25,7 +25,7 @@ (define (quad-tag? x) (match x [(or (? symbol?) #false) #true] [_ #false])) -(define (make-quad-attrs alist) (make-hasheq alist)) +(define (make-quad-attrs [alist null]) (make-hasheq alist)) (define (quad-attrs? x) (hash-eq? x)) (define (quad-elems? x) (list? x)) diff --git a/quad2/render.rkt b/quad2/render.rkt index 2f7052b5..c9f64872 100644 --- a/quad2/render.rkt +++ b/quad2/render.rkt @@ -1,5 +1,7 @@ #lang debug racket/base -(require "compile.rkt" "draw.rkt" "layout.rkt") +(require "pipeline.rkt" + "draw.rkt" + "layout.rkt") (provide (all-defined-out)) (struct $renderer (doc-start-func