diff --git a/trivial/untyped.rkt b/trivial/untyped.rkt new file mode 100644 index 0000000..e087ea0 --- /dev/null +++ b/trivial/untyped.rkt @@ -0,0 +1,81 @@ +#lang racket/base + +;; TODO can't retrive values from define:number + +(provide + (for-syntax set-trivial-print) + (rename-out + [define: define] + [let: let] + ;; + [format: format] + [printf: printf] + [+: +] + [-: -] + [*: *] + [/: /] + [quotient: quotient] + [expt: expt] + ;; + [pair?: pair?] + [null?: null?] + [cons: cons] + [car: car] + [cdr: cdr] + [list?: list?] + [length: length] + [list-ref: list-ref] + [list-tail: list-tail] + [append: append] + [reverse: reverse] + ;; + [regexp-match: regexp-match] + [regexp: regexp] + [pregexp: pregexp] + [byte-regexp: byte-regexp] + [byte-pregexp: byte-pregexp] + ;; + [vector-length: vector-length] + [vector-ref: vector-ref] + [vector-set!: vector-set!] + [vector-map: vector-map] + [vector-map!: vector-map!] + [vector-append: vector-append] + [vector->list: vector->list] + [vector->immutable-vector: vector->immutable-vector] + [vector-fill!: vector-fill!] + [vector-take: vector-take] + [vector-take-right: vector-take-right] + [vector-drop: vector-drop] + [vector-drop-right: vector-drop-right] +)) + +(require + (for-syntax + trivial/private/common + racket/base + (only-in trivial/private/parameters set-trivial-print)) + trivial/untyped/format + trivial/untyped/list + trivial/untyped/math + trivial/untyped/regexp + trivial/untyped/vector +) + +(define-syntax define: (make-keyword-alias 'define + (lambda (stx) + (or (format-define stx) + (num-define stx) + (lst-define stx) + (rx-define stx) + ;(fun-define stx) + (vec-define stx))))) + +(define-syntax let: (make-keyword-alias 'let + (lambda (stx) + (or (format-let stx) + ;(fun-let stx) + (num-let stx) + (lst-let stx) + (rx-let stx) + (vec-let stx))))) diff --git a/trivial/untyped/README.md b/trivial/untyped/README.md new file mode 100644 index 0000000..739d0af --- /dev/null +++ b/trivial/untyped/README.md @@ -0,0 +1,9 @@ +private +=== + +Files that no law-abiding library user should `require`. + +- `common.rkt` Helper functions common to a few macros. +- `set-bang.rkt` Restrict `set!` to respect our syntax property metadata. +- `test-common.rkt` Helpers for unit testing +- `db/` Support for the `db.rkt` implementation diff --git a/trivial/untyped/format.rkt b/trivial/untyped/format.rkt new file mode 100644 index 0000000..47a867a --- /dev/null +++ b/trivial/untyped/format.rkt @@ -0,0 +1,93 @@ +#lang racket/base + +;; Statically-checked format strings + +(provide + format: + printf: + + (for-syntax + format-define + format-let) +) + +(require + (for-syntax + trivial/private/common + typed/racket/base + syntax/parse)) + +;; ============================================================================= + +(begin-for-syntax + ;; Count the number of format escapes in a string. + ;; Returns a list of optional types (to be spliced into the source code). + ;; Example: If result is '(#f Integer), then + ;; - The format string expects 2 arguments + ;; - First argument has no type constraints, second must be an Integer + (define (format-parser stx) + (define str (if (string? (syntax-e stx)) (syntax-e stx) (quoted-stx-value? stx))) + (cond + [(string? str) + (define last-index (- (string-length str) 1)) + (let loop ([i 0] [acc '()]) + (cond + [(>= i last-index) + (reverse acc)] + [(eq? #\~ (string-ref str i)) + ;; From fprintf docs @ http://docs.racket-lang.org/reference/Writing.html + (case (string-ref str (+ i 1)) + [(#\% #\n #\~ #\space #\tab #\newline) + ;; Need 0 arguments + (loop (+ i 2) acc)] + [(#\a #\A #\s #\S #\v #\V #\e #\E) + ;; Need 1 argument, can be anything + (loop (+ i 2) (cons #f acc))] + [(#\.) + ;; Need at most 1, can be anything + (if (and (< (+ 1 i) last-index) + (memq (string-ref str (+ i 2)) '(#\a #\A #\s #\S #\v #\V))) + (loop (+ i 3) (cons #f acc)) + (loop (+ i 3) acc))] + [(#\c #\C) + ;; Need 1 `char?` + (loop (+ i 2) (cons #f acc))] + [(#\b #\B #\o #\O #\x #\X) + ;; Need 1 `exact?` + (loop (+ i 2) (cons #f acc))] + [else + ;; Invalid format sequence + (raise-user-error "format: unrecognized pattern string '~~~c'" + (string-ref str (+ i 1)))])] + [else + (loop (+ i 1) acc)]))] + [else #f])) + + (define-values (_key fmt? format-define format-let) + (make-value-property 'string:format format-parser)) + + (define-syntax-class/predicate string/format fmt?) +) + +;; ----------------------------------------------------------------------------- + +(define-syntax format: (make-alias #'format + (lambda (stx) (syntax-parse stx + [(_ fmt:string/format arg* ...) + ;; -- 1. Parse expected types from the template + #:when (let ([num-expected (length (syntax-e #'fmt.evidence))] + [num-given (length (syntax-e #'(arg* ...)))]) + (unless (= num-expected num-given) + (apply raise-arity-error + 'format: + num-expected + (map syntax->datum (syntax-e #'(arg* ...)))))) + (syntax/loc stx (format fmt.expanded arg* ...))] + [_ #f])))) + +(define-syntax printf: (make-alias #'printf + (lambda (stx) (syntax-parse stx + [(_ arg* ...) + (syntax/loc stx (display (format: arg* ...)))] + [_ #f])))) + diff --git a/trivial/untyped/function.rkt b/trivial/untyped/function.rkt new file mode 100644 index 0000000..ec110ab --- /dev/null +++ b/trivial/untyped/function.rkt @@ -0,0 +1,89 @@ +#lang racket/base + +;; TODO +;; map passing, but cury failig; can't make a lambda like I'd like to + +;; ----------------------------------------------------------------------------- + +;; Track procedure arity +;; Applications: +;; - vectorized ops +;; - (TODO) improve apply/map? ask Leif +;; - TODO get types, not arity + +(provide + curry: + map: + + ;; -- + (for-syntax + fun-define + fun-let) +) + +;; ----------------------------------------------------------------------------- + +(require + (for-syntax + typed/racket/base + syntax/parse + racket/syntax + trivial/private/common +)) + +;; ============================================================================= + +(begin-for-syntax + (define TYPE-KEY 'type-label) + + (define (formal->type x) + #f) + + (define (parse-procedure-arity stx) + (syntax-parse stx #:literals (: #%plain-lambda lambda) + [(#%plain-lambda (x*:id ...) e* ...) + (map formal->type (syntax-e #'(x* ...)))] + ;; TODO polydots, keywords, optional args + ;; TODO standard library functions + [_ #f])) + + (define-values (arity-key fun? fun-define fun-let) + (make-value-property 'procedure:arity parse-procedure-arity)) + + (define-syntax-class/predicate procedure/arity fun?) +) + +;; ----------------------------------------------------------------------------- + +(define-syntax (curry: stx) + (syntax-parse stx + [(_ p:procedure/arity) + #:with x* (for/list ([_t (in-list (syntax-e #'p.evidence))]) (gensym)) + #:with p+ (for/fold ([e (quasisyntax/loc stx (p.expanded #,@#'x*))]) + ([x (in-list (reverse (syntax-e #'x*)))]) + (quasisyntax/loc stx + (lambda (#,x) #,e))) + (syntax/loc stx p+)] + [_ + (raise-user-error 'curry "Fail at: ~a" (syntax->datum stx))])) + +;; TODO try the other direction, inferring type from arguments. +;; (may not be practical here, may need to be inside TR) +(define-syntax map: (make-alias #'map + (lambda (stx) (syntax-parse stx + [(_ p:procedure/arity e* ...) + ;; -- + #:when + (let ([num-expected (length (syntax-e #'p.evidence))] + [num-actual (length (syntax-e #'(e* ...)))]) + (unless (= num-expected num-actual) + (apply raise-arity-error + 'map: + num-expected + (map syntax->datum (syntax-e #'(e* ...)))))) + ;; -- + #:with (e+* ...) + (for/list ([e (in-list (syntax-e #'(e* ...)))]) + (quasisyntax/loc stx #,e)) + (syntax/loc stx (map p.expanded e+* ...))])))) + diff --git a/trivial/untyped/list.rkt b/trivial/untyped/list.rkt new file mode 100644 index 0000000..db9328c --- /dev/null +++ b/trivial/untyped/list.rkt @@ -0,0 +1,200 @@ +#lang racket/base + +(provide + define-list: + let-list: + pair?: + null?: + cons: + car: + cdr: + list?: + length: + list-ref: + list-tail: + append: + reverse: + ;map: + ;andmap: + ;ormap: + ;for-each: + ;foldl: + ;foldr: + ;filter: + ;remove: + ;remq: + ;remv: + ;remove*: + ;remq*: + ;remv*: + ;sort: + ;member: + + ;; --- private + (for-syntax + lst-define + lst-let + parse-list-length + lst-length-key ;; TODO generic "data structure length" key? + ) +) + +;; ----------------------------------------------------------------------------- + +(require + trivial/private/math + typed/racket/unsafe + racket/list + (for-syntax + trivial/private/common + trivial/private/sequence + typed/racket/base + syntax/parse)) + +;; Thank you based Asumu +(require (only-in racket/unsafe/ops + unsafe-car + unsafe-cdr + unsafe-cons-list + unsafe-list-ref + unsafe-list-tail +)) + +;; ============================================================================= + +(begin-for-syntax + (define (parse-list-length stx) + (syntax-parse stx #:literals (#%plain-app cons list list* make-list build-list null) + [(~or '(e* ...) + (list e* ...) + (#%plain-app list e* ...)) + (length (syntax-e #'(e* ...)))] + [(~or (make-list n e* ...) + (#%plain-app make-list n e* ...) + (build-list n e* ...) + (#%plain-app build-list n e* ...)) + #:with n-stx (stx->num #'n) + #:when (syntax-e #'n-stx) + (syntax-e #'n-stx)] + [(~or (cons e es) + (#%plain-app cons e es)) + #:with n+ (parse-list-length #'es) + #:when (syntax-e #'n+) + (+ 1 (syntax-e #'n+))] + [(~or (list* e* ... es) + (#%plain-app list* e* ... es)) + #:with n+ (parse-list-length #'es) + #:when (syntax-e #'n+) + (+ (length (syntax-e #'(e* ...))) (syntax-e #'n+))] + [(~or (null) + (#%plain-app null)) + 0] + [+ #f])) + + (define-values (lst-length-key lst? lst-define lst-let) + (make-value-property 'list:length parse-list-length)) + (define-syntax-class/predicate list/length lst?) +) + +;; ----------------------------------------------------------------------------- + +(define-syntax define-list: (make-keyword-alias 'define lst-define)) +(define-syntax let-list: (make-keyword-alias 'let lst-let)) + +(define-syntax pair?: (make-alias #'pair? + (lambda (stx) (syntax-parse stx + [(_ l:list/length) + (quasisyntax/loc stx '#,(not (zero? (syntax-e #'l.evidence))))] + [_ #f])))) + +(define-syntax null?: (make-alias #'null? + (lambda (stx) (syntax-parse stx + [(_ l:list/length) + (quasisyntax/loc stx '#,(zero? (syntax-e #'l.evidence)))] + [_ #f])))) + +(define-syntax cons: (make-alias #'cons + (lambda (stx) (syntax-parse stx + [(_ x l:list/length) + #:with l+ (syntax-property + (syntax/loc stx (unsafe-cons-list x l.expanded)) + lst-length-key (+ 1 (syntax-e #'l.evidence))) + (syntax/loc stx l+)] + [_ #f])))) + +(define-syntax car: (make-alias #'car + (lambda (stx) (syntax-parse stx + [(_ l:list/length) + (when (zero? (syntax-e #'l.evidence)) + (bounds-error 'car: #'l 0)) + (syntax/loc stx (unsafe-car l.expanded))] + [_ #f])))) + +(define-syntax cdr: (make-alias #'cdr + (lambda (stx) (syntax-parse stx + [(_ l:list/length) + (when (zero? (syntax-e #'l.evidence)) + (bounds-error 'cdr: #'l 0)) + (syntax/loc stx (unsafe-cdr l.expanded))] + [_ #f])))) + +(define-syntax list?: (make-alias #'list? + (lambda (stx) (syntax-parse stx + [(_ l:list/length) + (syntax/loc stx '#t)] + [_ #f])))) + +(define-syntax length: (make-alias #'length + (lambda (stx) (syntax-parse stx + [(_ l:list/length) + (syntax/loc stx 'l.evidence)] + [_ #f])))) + +(define-syntax list-ref: (make-alias #'list-ref + (lambda (stx) (syntax-parse stx + [(_ l:list/length e) + #:with i-stx (stx->num #'e) + #:when (syntax-e #'i-stx) + (let ([i (syntax-e #'i-stx)]) + (unless (< -1 i (syntax-e #'l.evidence)) + (bounds-error 'list-ref: #'l i)) + (syntax/loc stx (unsafe-list-ref l.expanded 'i-stx)))] + [_ #f])))) + +(define-syntax list-tail: (make-alias #'list-tail + (lambda (stx) (syntax-parse stx + [(_ l:list/length e) + #:with i-stx (stx->num #'e) + #:when (syntax-e #'i-stx) + (let ([i (syntax-e #'i-stx)]) + (unless (< i (syntax-e #'l.evidence)) + (bounds-error 'list-tail: #'l i)) + (syntax/loc stx (unsafe-list-tail l.expanded 'i-stx)))] + [_ #f])))) + +(define-syntax append: (make-alias #'append + (lambda (stx) (syntax-parse stx + [(_ l1:list/length l2:list/length) + #:with l+ (syntax-property (syntax/loc stx (append l1.expanded l2.expanded)) + lst-length-key (+ (syntax-e #'l1.evidence) (syntax-e #'l2.evidence))) + (quasisyntax/loc stx l+)] + [_ #f])))) + +(define-syntax reverse: (make-alias #'reverse + (lambda (stx) (syntax-parse stx + [(_ l:list/length) + #:with l+ (syntax-property (syntax/loc stx (reverse l.expanded)) + lst-length-key (syntax-e #'l.evidence)) + (quasisyntax/loc stx l+)] + [_ #f])))) + +(define-syntax map: (make-alias #'map + (lambda (stx) (syntax-parse stx + [(_ f l:list/length l*:list/length ...) + #:with l+ (syntax-property + (syntax/loc stx (map f l.expanded l*.expanded ...)) + lst-length-key + (syntax-e #'l.evidence)) + (syntax/loc stx l+)] + [_ #f])))) + diff --git a/trivial/untyped/main.rkt b/trivial/untyped/main.rkt new file mode 100644 index 0000000..a64554d --- /dev/null +++ b/trivial/untyped/main.rkt @@ -0,0 +1,57 @@ +#lang racket/base + +;; TODO define/let + +(provide + (for-syntax set-trivial-print) + (rename-out + [format: format] + [printf: printf] + [+: +] + [-: -] + [*: *] + [/: /] + [quotient: quotient] + [expt: expt] + ;; + [pair?: pair?] + [null?: null?] + [cons: cons] + [car: car] + [cdr: cdr] + [list?: list?] + [length: length] + [list-ref: list-ref] + [list-tail: list-tail] + [append: append] + [reverse: reverse] + ;; + [regexp-match: regexp-match] + [regexp: regexp] + [pregexp: pregexp] + [byte-regexp: byte-regexp] + [byte-pregexp: byte-pregexp] + ;; + [vector-length: vector-length] + [vector-ref: vector-ref] + [vector-set!: vector-set!] + [vector-map: vector-map] + [vector-map!: vector-map!] + [vector-append: vector-append] + [vector->list: vector->list] + [vector->immutable-vector: vector->immutable-vector] + [vector-fill!: vector-fill!] + [vector-take: vector-take] + [vector-take-right: vector-take-right] + [vector-drop: vector-drop] + [vector-drop-right: vector-drop-right] +)) + +(require + (for-syntax (only-in trivial/private/parameters set-trivial-print)) + trivial/untyped/format + trivial/untyped/list + trivial/untyped/math + trivial/untyped/regexp + trivial/untyped/vector +) diff --git a/trivial/untyped/math.rkt b/trivial/untyped/math.rkt new file mode 100644 index 0000000..a77d919 --- /dev/null +++ b/trivial/untyped/math.rkt @@ -0,0 +1,139 @@ +#lang racket/base + +;; Constant-folding math operators. +;; Where possible, they simplify their arguments. + +(provide + +: -: *: /: + ;; Same signature as the racket/base operators, + ;; but try to simplify arguments during expansion. + + expt: + quotient: + + define-num: let-num: + + ;; -- + (for-syntax + stx->num + nat/expand + int/expand + num/expand + num-key + num-define + num-let) +) + +(require (for-syntax + racket/base + (only-in racket/format ~a) + (only-in racket/syntax format-id) + syntax/id-table + syntax/parse + trivial/private/common +)) + +;; ============================================================================= + +(begin-for-syntax + (define (division-by-zero stx) + (raise-syntax-error '/ "division by zero" stx)) + + (define (stx->num stx) + (syntax-parse stx + [v:num/expand + (if (identifier? #'v.expanded) + (quoted-stx-value? #'v.evidence) + (quoted-stx-value? #'v.expanded))] + [_ #f])) + + ;; Simplify a list of expressions using an associative binary operator. + ;; Return either: + ;; - A numeric value + ;; - A list of syntax objects, to be spliced back in the source code + (define (reduce/op op stx) + (define expr* (syntax-e stx)) + (cond + [(list? expr*) + (let loop ([prev #f] ;; (U #f Number), candidate for reduction + [acc '()] ;; (Listof Syntax), irreducible arguments + [e* expr*]) ;; (Listof Syntax), arguments to process + (if (null? e*) + ;; then: finished, return a number (prev) or list of expressions (acc) + (if (null? acc) + prev + (reverse (if prev (cons prev acc) acc))) + ;; else: pop the next argument from e*, fold if it's a constant + (let ([v (stx->num (car e*))]) + (if v + ;; then: reduce the number + (if prev + ;; Watch for division-by-zero + (if (and (zero? v) (eq? / op)) + (division-by-zero stx) + (loop (op prev v) acc (cdr e*))) + (loop v acc (cdr e*))) + ;; else: save value in acc + (let ([acc+ (cons (car e*) (if prev (cons prev acc) acc))]) + (loop #f acc+ (cdr e*)))))))] + [else #f])) + + (define-values (nat-key nat? nat-define nat-let) + (make-value-property 'number:natural (lift-predicate exact-nonnegative-integer?))) + (define-syntax-class/predicate nat/expand nat?) + + (define-values (int-key int? int-define int-let) + (make-value-property 'number:integer (lift-predicate integer?))) + (define-syntax-class/predicate int/expand int?) + + (define-values (num-key num? num-define num-let) + (make-value-property 'number:number (lift-predicate number?))) + (define-syntax-class/predicate num/expand num?) +) + +;; ----------------------------------------------------------------------------- + +(define-syntax define-num: (make-keyword-alias 'define num-define)) +(define-syntax let-num: (make-keyword-alias 'let num-let)) + +(define-syntax make-numeric-operator + (syntax-parser + [(_ f:id) + #:with f: (format-id #'f "~a:" (syntax-e #'f)) + #'(define-syntax f: (make-alias #'f + (lambda (stx) (syntax-parse stx + [(_ e* (... ...)) + #:with f-id (format-id stx "~a" 'f) + (let ([e+ (reduce/op f #'(e* (... ...)))]) + (if (list? e+) + (quasisyntax/loc stx (#%app f-id #,@e+)) + (quasisyntax/loc stx #,e+)))] + [_ #f]))))])) + +(make-numeric-operator +) +(make-numeric-operator -) +(make-numeric-operator *) +(make-numeric-operator /) + +(define-syntax expt: (make-alias #'expt + (lambda (stx) (syntax-parse stx + [(_ n1 n2) + (let ([v1 (stx->num #'n1)] + [v2 (stx->num #'n2)]) + (cond + [(and v1 v2) + (quasisyntax/loc stx #,(expt v1 v2))] + [(and v2 (<= 0 v2 10)) + (quasisyntax/loc stx (* #,@(for/list ([_i (in-range v2)]) (quasisyntax/loc stx n1))))] + [else + #f]))] + [_ #f])))) + +(define-syntax quotient: (make-alias #'quotient + (lambda (stx) (syntax-parse stx + [(_ n1 n2) + (let ([v1 (stx->num #'n1)] + [v2 (stx->num #'n2)]) + (and v1 v2 + (quasisyntax/loc stx #,(quotient v1 v2))))] + [_ #f])))) diff --git a/trivial/untyped/regexp.rkt b/trivial/untyped/regexp.rkt new file mode 100644 index 0000000..87915ae --- /dev/null +++ b/trivial/untyped/regexp.rkt @@ -0,0 +1,244 @@ +#lang racket/base + +;; Stronger types for regular expression matching. + +;; Specification: +;; - Racket docs: +;; http://docs.racket-lang.org/reference/regexp.html +;; +;; - Pregexp docs: +;; http://ds26gte.github.io/pregexp/index.html +;; +;; - Racket source: +;; https://github.com/racket/racket/blob/master/racket/src/racket/src/regexp.c + +(provide + regexp: + pregexp: + byte-regexp: + byte-pregexp: + define-regexp: + let-regexp: + + regexp-match: + + (for-syntax + rx-key + rx-define + rx-let) +) + +(require + (for-syntax + (only-in racket/syntax format-id) + typed/racket/base + (only-in racket/list range) + (only-in racket/format ~a) + syntax/parse + trivial/private/common)) + +;; ============================================================================= + +(begin-for-syntax + (define errloc-key 'regexp-match:) + + (define (group-error str reason) + (raise-argument-error + errloc-key + (format "Invalid regexp pattern (unmatched ~a)" reason) + str)) + + ;; Dispatch for counting groups + (define (parse-groups v-stx) + (define v (quoted-stx-value? v-stx)) + (cond + [(string? v) (parse-groups/string v #:src v-stx)] + [(regexp? v) (parse-groups/regexp v #:src v-stx)] + [(pregexp? v) (parse-groups/pregexp v #:src v-stx)] + [(bytes? v) (parse-groups/bytes v #:src v-stx)] + [(byte-regexp? v) (parse-groups/byte-regexp v #:src v-stx)] + [(byte-pregexp? v) (parse-groups/byte-pregexp v #:src v-stx)] + [else #f])) + + ;; Handle pipes + ;; If there is a pipe, everything is nullable, but we know the number of groups + (define (parse-groups/untyped str #:src stx) + (define alt* (string->alt* str)) + (cond + [(null? alt*) + (list 0 '())] + [(null? (cdr alt*)) + (parse-groups-for-alt (car alt*) #:src stx)] + [else + (define num-groups + (for/fold ([num-groups 0]) + ([alt (in-list alt*)]) + (define ng+null* (parse-groups-for-alt alt #:src stx)) + (+ num-groups (car ng+null*)))) + (list num-groups (range num-groups))])) + + ;; Count the number of matched parentheses in a regexp pattern. + ;; Raise an exception if there are unmatched parens. + (define (parse-groups-for-alt str #:src stx) + (define last-index (- (string-length str) 1)) + (define in-square? (box #f)) + (let loop ([i 0] [in-paren '()] [num-groups 0] [null-idx* '()]) + (if (> i last-index) + (cond + [(not (null? in-paren)) + (group-error str (format "'(' at index ~a" (car in-paren)))] + [(unbox in-square?) + (group-error str (format "'[' at index ~a" (car in-paren)))] + [else + (list num-groups null-idx*)]) + (if (unbox in-square?) + (if (eq? #\] (string-ref str i)) + (begin (set-box! in-square? #f) + (loop (+ i 1) (cdr in-paren) num-groups null-idx*)) + (loop (+ i 1) in-paren num-groups null-idx*)) + (case (string-ref str i) + [(#\[) + ;; Ignore things between [ ... ] + (set-box! in-square? #t) + (loop (+ i 1) (cons i in-paren) num-groups null-idx*)] + [(#\() + ;; Watch for (? patterns + (if (and (< i last-index) + (eq? #\? (string-ref str (+ i 1)))) + (loop (+ i 2) (cons #f in-paren) num-groups null-idx*) + (loop (+ i 1) (cons i in-paren) num-groups null-idx*))] + [(#\)) + (cond + [(null? in-paren) + (group-error str (format "')' at index ~a" i))] + [(eq? #f (car in-paren)) + ;; Matched closing paren, but does not count as a group + (loop (+ i 1) (cdr in-paren) num-groups null-idx*)] + [(and (< i last-index) + (or + (eq? #\? (string-ref str (+ i 1))) + (eq? #\* (string-ref str (+ i 1))))) + ;; group = may be #f + (loop (+ i 1) (cdr in-paren) (+ 1 num-groups) (cons num-groups null-idx*))] + [else + (loop (+ i 1) (cdr in-paren) (+ 1 num-groups) null-idx*)])] + [(#\\) + (if (and (< i last-index) + (eq? #\\ (string-ref str (+ i 1)))) + (loop (+ i 3) in-paren num-groups null-idx*) + (loop (+ i 2) in-paren num-groups null-idx*))] + [(#\|) + ;; Nope! Can't handle pipes + (error 'internal-error "Found '|' character in regexp string.")] + [else + (loop (+ i 1) in-paren num-groups null-idx*)]))))) + + (define (parse-groups/string str #:src stx) + (let ([ng (parse-groups/untyped str #:src stx)]) + (and ng (cons 'String ng)))) + + (define (parse-groups/bytes b #:src stx) + (let ([ng (parse-groups/untyped (~a b) #:src stx)]) + (and ng (cons 'Bytes ng)))) + + (define (parse-groups/regexp rx #:src stx) + (parse-groups/string (~a rx) #:src stx)) + + (define parse-groups/pregexp + parse-groups/regexp) + + (define (parse-groups/byte-regexp bx #:src stx) + (parse-groups/bytes (~a bx) #:src stx)) + + (define parse-groups/byte-pregexp + parse-groups/byte-regexp) + + (define-values (rx-key rx? rx-define rx-let) + (make-value-property 'rx:groups parse-groups)) + (define-syntax-class/predicate pattern/groups rx?) + +) +;; ----------------------------------------------------------------------------- +;; --- Other helpers + +(begin-for-syntax + + ;; Divide string into |-separated substrings (regex alternates) + ;; Be wary of escaped | characters. + (define (string->alt* str) + (define L (string-length str)) + (let loop ([prev-i 0] [i 0]) + (cond + [(= i L) + ;; End of string, return last alternate + (list (substring str prev-i i))] + [(and (eq? (string-ref str i) #\|) + (< 1 i) + (not (and (eq? (string-ref str (- i 1)) #\\) + (eq? (string-ref str (- i 2)) #\\)))) + ;; Found a pipe, save current alternate + (cons (substring str prev-i i) + (loop (+ i 1) (+ i 1)))] + [else + ;; Nothing interesting, continue building alternate + (loop prev-i (+ i 1))]))) + + (define (intlist-union i* j*) + (cond + [(null? i*) + j*] + [(null? j*) + i*] + [(< (car i*) (car j*)) + (cons (car i*) (intlist-union (cdr i*) j*))] + [(> (car i*) (car j*)) + (cons (car j*) (intlist-union i* (cdr j*)))] + [else + (cons (car i*) (intlist-union (cdr i*) (cdr j*)))])) + + (define (infer-return-type pattern-sym arg-stx) + (if (and + (or (eq? pattern-sym 'String) + (eq? pattern-sym 'Regexp)) + (or (syntax-parse arg-stx + ((x:str) #t) + ((x) #:when (bytes? (syntax-e #'x)) #f) + (_ #t)))) + 'String + 'Bytes)) +) + +;; ----------------------------------------------------------------------------- + +(define-syntax (define-matcher* stx) + (syntax-parse stx + [(_ f*:id ...) + #:with (f+* ...) (for/list ([f (in-list (syntax-e #'(f* ...)))]) + (format-id stx "~a:" (syntax-e f))) + #`(begin + (define-syntax f+* (make-alias #'f* + (lambda (stx) (syntax-parse stx + [(_ pat:pattern/groups) + (syntax-property + (syntax/loc stx (f* pat.expanded)) + rx-key + #'pat.evidence)] + [_ #f])))) ...)])) + +(define-matcher* regexp pregexp byte-regexp byte-pregexp) + +(define-syntax define-regexp: (make-keyword-alias 'define rx-define)) +(define-syntax let-regexp: (make-keyword-alias 'let rx-let)) + +(define-syntax regexp-match: (make-alias #'regexp-match + (lambda (stx) (syntax-parse stx + [(_ pat:pattern/groups arg* ...) + #:with (type-sym num-groups null-idx*) (syntax/loc stx pat.evidence) + ;; TODO keep source location in type-sym, stop using format-id + ;; (Is it really that bad?) + (syntax/loc stx + (let ([maybe-match (regexp-match pat.expanded arg* ...)]) + (if maybe-match + maybe-match + #f)))] + [_ #f])))) diff --git a/trivial/untyped/sequence.rkt b/trivial/untyped/sequence.rkt new file mode 100644 index 0000000..d1f0f55 --- /dev/null +++ b/trivial/untyped/sequence.rkt @@ -0,0 +1,20 @@ +#lang racket/base + +(provide + small-sequence-size? + bounds-error +) + +;; ============================================================================= + +(define (small-sequence-size? n) + (< n 20)) + +(define (bounds-error sym v-stx i) + (raise-syntax-error + sym + "Index out-of-bounds" + (syntax->datum v-stx) + i + (list v-stx))) + diff --git a/trivial/untyped/set-bang.rkt b/trivial/untyped/set-bang.rkt new file mode 100644 index 0000000..c2d0f9b --- /dev/null +++ b/trivial/untyped/set-bang.rkt @@ -0,0 +1,33 @@ +#lang racket/base + +;(provide +; (rename-out [set!: set!]) +;) +; +;(require +; (for-syntax +; racket/base +; syntax/parse +; trivial/private/common) +; (only-in trivial/private/db connection-key) +; (only-in trivial/private/math num-key) +; (only-in trivial/private/regexp rx-key) +; (only-in trivial/private/vector vector-length-key) +;) +; +;;; ============================================================================= +; +;(define-for-syntax (has-important-syntax-property? stx) +; (or #t)) ;; Safe over-approximation +;; (syntax-property stx connection-key) +;; (syntax-property stx num-key) +;; (syntax-property stx rx-key) +;; (syntax-property stx vector-length-key))) +; +;(define-syntax set!: (make-keyword-alias 'set! +; (lambda (stx) (syntax-parse stx +; [(_ name val) +; #:when (has-important-syntax-property? #'name) +; (raise-syntax-error 'trivial "mutation not allowed"); stx); #'name) +; #'(void)] +; [_ #f])))) diff --git a/trivial/untyped/vector.rkt b/trivial/untyped/vector.rkt new file mode 100644 index 0000000..182ce55 --- /dev/null +++ b/trivial/untyped/vector.rkt @@ -0,0 +1,255 @@ +#lang racket/base + +;; TODO: +;; - use unsafe-vector* operations? (will we ever have an impersonator? not yet) + +(provide + define-vector: + let-vector: + vector-length: + vector-ref: + vector-set!: + vector-map: + vector-map!: + vector-append: + vector->list: + vector->immutable-vector: + vector-fill!: + vector-take: + vector-take-right: + vector-drop: + vector-drop-right: +; vector-split-at: +; vector-split-at-right: + + ;; --- private + (for-syntax + vec-define + vec-let + parse-vector-length + vector-length-key) +) + +;; ----------------------------------------------------------------------------- + +(require + (only-in racket/unsafe/ops + unsafe-vector-set! + unsafe-vector-ref) + trivial/private/math + racket/vector + (for-syntax + trivial/private/common + trivial/private/sequence + racket/base + syntax/parse)) + +;; ============================================================================= + +(begin-for-syntax + (define (parse-vector-length stx) + (syntax-parse stx #:literals (#%plain-app vector make-vector build-vector) + [(~or '#(e* ...) + #(e* ...) + ;; TODO #{} #[] #6{} ... + (#%plain-app vector e* ...) + (#%plain-app vector e* ...) + (vector e* ...)) + (length (syntax-e #'(e* ...)))] + [(~or (make-vector n e* ...) + (#%plain-app make-vector n e* ...) + (build-vector n e* ...) + (#%plain-app build-vector n e* ...)) + #:with n-stx (stx->num #'n) + #:when (syntax-e #'n-stx) + (syntax-e #'n-stx)] + [_ #f])) + + (define-values (vector-length-key vec? vec-define vec-let) + (make-value-property 'vector:length parse-vector-length)) + (define-syntax-class/predicate vector/length vec?) +) + +;; ----------------------------------------------------------------------------- + +(define-syntax define-vector: (make-keyword-alias 'define vec-define)) +(define-syntax let-vector: (make-keyword-alias 'let vec-let)) + +(define-syntax vector-length: (make-alias #'vector-length + (lambda (stx) (syntax-parse stx + [(_ v:vector/length) + (syntax/loc stx 'v.evidence)] + [_ #f])))) + +(define-syntax vector-ref: (make-alias #'vector-ref + (lambda (stx) (syntax-parse stx + [(_ v:vector/length e) + #:with i-stx (stx->num #'e) + #:when (syntax-e #'i-stx) + (let ([i (syntax-e #'i-stx)]) + (unless (< i (syntax-e #'v.evidence)) + (bounds-error 'vector-ref: #'v i)) + (syntax/loc stx (unsafe-vector-ref v.expanded 'i-stx)))] + [_ #f])))) + +(define-syntax vector-set!: (make-alias #'vector-set! + (lambda (stx) (syntax-parse stx + [(_ v:vector/length e val) + #:with i-stx (stx->num #'e) + #:when (syntax-e #'i-stx) + (let ([i (syntax-e #'i-stx)]) + (unless (< i (syntax-e #'v.evidence)) + (bounds-error 'vector-set!: #'v i)) + (syntax/loc stx (unsafe-vector-set! v.expanded 'i-stx val)))] + [_ #f])))) + +(define-syntax vector-map: (make-alias #'vector-map + (lambda (stx) (syntax-parse stx + [(_ f v:vector/length) + #:with f+ (gensym 'f) + #:with v+ (gensym 'v) + #:with v++ (syntax-property + (if (small-sequence-size? (syntax-e #'v.evidence)) + (with-syntax ([(i* ...) (for/list ([i (in-range (syntax-e #'v.evidence))]) i)]) + (syntax/loc stx + (let ([f+ f] [v+ v.expanded]) + (vector (f+ (unsafe-vector-ref v+ 'i*)) ...)))) + (syntax/loc stx + (let ([f+ f] [v+ v.expanded]) + (build-vector 'v.evidence (lambda (i) ;;bg + (f+ (vector-ref: v+ i))))))) + vector-length-key + (syntax-e #'v.evidence)) + (syntax/loc stx v++)] + [_ #f])))) + +(define-syntax vector-map!: (make-alias #'vector-map! + (lambda (stx) (syntax-parse stx + [(_ f v:vector/length) + #:with f+ (gensym 'f) + #:with v+ (gensym 'v) + #:with v++ (syntax-property + #'(let ([f+ f] + [v+ v.expanded]) + (for ([i (in-range 'v.evidence)]) + (unsafe-vector-set! v+ i (f+ (unsafe-vector-ref v+ i)))) + v+) + vector-length-key + (syntax-e #'v.evidence)) + (syntax/loc stx v++)] + [_ #f])))) + +(define-syntax vector-append: (make-alias #'vector-append + (lambda (stx) (syntax-parse stx + [(_ v1:vector/length v2:vector/length) + #:with v1+ (gensym 'v1) + #:with v2+ (gensym 'v2) + (define l1 (syntax-e #'v1.evidence)) + (define l2 (syntax-e #'v2.evidence)) + (syntax-property + (if (and (small-sequence-size? l1) + (small-sequence-size? l2)) + (with-syntax ([(i1* ...) (for/list ([i (in-range l1)]) i)] + [(i2* ...) (for/list ([i (in-range l2)]) i)]) + (syntax/loc stx + (let ([v1+ v1.expanded] + [v2+ v2.expanded]) + (vector (vector-ref: v1+ i1*) ... + (vector-ref: v2+ i2*) ...)))) + (quasisyntax/loc stx + (let ([v1+ v1.expanded] + [v2+ v2.expanded]) + (build-vector + #,(+ l1 l2) + (lambda (i) + (if (< i '#,l1) + (unsafe-vector-ref v1+ i) + (unsafe-vector-ref v2+ i))))))) + vector-length-key + (+ l1 l2))] + [_ #f])))) + +(define-syntax vector->list: (make-alias #'vector->list + (lambda (stx) (syntax-parse stx + [(_ v:vector/length) + #:with v+ (gensym 'v) + (define len (syntax-e #'v.evidence)) + (if (small-sequence-size? len) + (with-syntax ([(i* ...) (for/list ([i (in-range len)]) i)]) + (syntax/loc stx + (let ([v+ v.expanded]) + (list (unsafe-vector-ref v+ i*) ...)))) + (syntax/loc stx + (let ([v+ v.expanded]) + (build-list 'v.evidence (lambda (i) (unsafe-vector-ref v+ i))))))] + [_ #f])))) + +(define-syntax vector->immutable-vector: (make-alias #'vector->immutable-vector + (lambda (stx) (syntax-parse stx + [(_ v:vector/length) + (syntax-property + (syntax/loc stx (vector->immutable-vector v.expanded)) + vector-length-key + (syntax-e #'v.evidence))] + [_ #f])))) + +(define-syntax vector-fill!: (make-alias #'vector-fill! + (lambda (stx) (syntax-parse stx + [(_ v:vector/length val) + #:with v+ (gensym 'v) + (define len (syntax-e #'v.evidence)) + (syntax-property + (syntax/loc stx + (let ([v+ v.expanded]) + (for ([i (in-range 'v.evidence)]) + (unsafe-vector-set! v+ i val)))) + vector-length-key + (syntax-e #'v.evidence))] + [_ #f])))) + +(begin-for-syntax (define-syntax-rule (make-slice-op left? take?) + (lambda (stx) + (syntax-parse stx + [(op-name v:vector/length n) + #:with n-stx (stx->num #'n) + #:when (exact-nonnegative-integer? (syntax-e #'n-stx)) + #:with (lo hi) + (if 'take? + (if 'left? + (list 0 (syntax-e #'n-stx)) + (list + (- (syntax-e #'v.evidence) (syntax-e #'n-stx)) + (syntax-e #'v.evidence))) + (if 'left? + (list (syntax-e #'n-stx) (syntax-e #'v.evidence)) + (list 0 (- (syntax-e #'v.evidence) (syntax-e #'n-stx))))) + #:with n+ (gensym 'n) + #:with v+ (gensym 'v) + (unless (<= (syntax-e #'n-stx) (syntax-e #'v.evidence)) + (bounds-error (syntax-e #'op-name) #'v + (if 'take? (if 'left? (syntax-e #'hi) (syntax-e #'lo)) + (if 'left? (syntax-e #'lo) (syntax-e #'hi))))) + (syntax-property + (syntax/loc stx + (let ([v+ v.expanded] + [n+ (-: 'hi 'lo)]) + (build-vector n+ (lambda ([i : Integer]) (unsafe-vector-ref v+ (+: i 'lo)))))) + vector-length-key + (syntax-e #'v.evidence))] + [(op-name v n:int/expand) + (bounds-error (syntax-e #'op-name) #'v (stx->num #'n.expanded))] + [_ #f])))) + +(define-syntax vector-take: + (make-alias #'vector-take (make-slice-op #t #t))) + +(define-syntax vector-take-right: + (make-alias #'vector-take-right (make-slice-op #f #t))) + +(define-syntax vector-drop-right: + (make-alias #'vector-drop-right (make-slice-op #f #f))) + +(define-syntax vector-drop: + (make-alias #'vector-drop (make-slice-op #t #f))) + +