diff --git a/coalton.asd b/coalton.asd index 53163f63f..bcffa4711 100644 --- a/coalton.asd +++ b/coalton.asd @@ -63,7 +63,8 @@ (:file "complex") (:file "elementary") (:file "dyadic") - (:file "dual"))) + (:file "dual") + (:file "package"))) (:file "randomaccess") (:file "cell") (:file "tuple") @@ -257,6 +258,7 @@ (:file "struct-tests") (:file "alias-tests") (:file "list-tests") + (:file "lisparray-tests") (:file "red-black-tests") (:file "seq-tests") (:file "pattern-matching-tests") diff --git a/docs/coalton-lisp-interop.md b/docs/coalton-lisp-interop.md index 05e71c850..1aada90a9 100644 --- a/docs/coalton-lisp-interop.md +++ b/docs/coalton-lisp-interop.md @@ -196,17 +196,17 @@ There are two ways to call Coalton from Lisp. The safe way to call Coalton is to use the `coalton` operator. This will type check, compile, and evaluate a Coalton expression and return its value to Lisp. Note that `coalton` does not accept definitions or top-level forms. For example: ```lisp -CL-USER> (format t "~R" (coalton:coalton coalton-library::(length (cons 1 (cons 2 nil))))) +CL-USER> (format t "~R" (coalton:coalton (coalton-prelude:length (coalton:cons 1 (coalton:cons 2 coalton:nil))))) two ; <- printed output NIL ; <- returned value -CL-USER> (format t "~R" (coalton:coalton coalton-library::(length 1))) +CL-USER> (format t "~R" (coalton:coalton (coalton-prelude:length (coalton:the coalton:UFix 1)))) ; (Guaranteed Compile-Time Error:) -; -; Failed to unify types COALTON:INTEGER -; and (COALTON-LIBRARY:LIST :B) -; in unification of types (COALTON:INTEGER → :A) -; and ((COALTON-LIBRARY:LIST :B) → COALTON:INTEGER) -; in COALTON +; error: Type mismatch +; --> repl input:1:46 +; | +; 1 | (COALTON:COALTON (COALTON-LIBRARY/LIST:LENGTH (COALTON:THE COALTON:UFIX 1))) +; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected type '(COALTON:LIST #T53400)' but got 'COALTON:UFIX' +; [Condition of type COALTON-IMPL/TYPECHECKER/BASE:TC-ERROR] ``` ### Unsafe Calls @@ -214,10 +214,10 @@ CL-USER> (format t "~R" (coalton:coalton coalton-library::(length 1))) Using the aforementioned promises, it's possible to call into raw Coalton-compiled code by using the generated functions. These calls are not checked in any way! ```lisp -CL-USER> (format t "~R" coalton-library::(length (cons 1 (cons 2 nil)))) +CL-USER> (format t "~R" (coalton-prelude:length (coalton:cons 1 (coalton:cons 2 coalton:nil)))) two ; <- printed output NIL ; <- returned value -CL-USER> (format t "~R" coalton-library::(length 1)) +CL-USER> (format t "~R" (coalton-prelude:length 1)) ; (Possible Run-Time Error #1:) ; ; The value diff --git a/docs/intro-to-coalton.md b/docs/intro-to-coalton.md index 7c768f770..3ac9088ea 100644 --- a/docs/intro-to-coalton.md +++ b/docs/intro-to-coalton.md @@ -526,16 +526,14 @@ COALTON-USER> (type-of '/) ∀ :A :B. DIVIDABLE :A :B ⇒ (:A → :A → :B) ``` -Because of the generic nature of division, if you're computing some values at the REPL, "raw division" simply does not work. +Because of [Instance Defaulting](#instance-defaulting), division of `Integer` constants without any additional context defaults to `Double-Float` division: ``` COALTON-USER> (coalton (/ 1 2)) -Failed to reduce context for DIVIDABLE INTEGER :A -in COALTON - [Condition of type COALTON-IMPL/TYPECHECKER::COALTON-TYPE-ERROR-CONTEXT] +0.5d0 ``` -You have to constrain the result type of the `Dividable` instance. You can do this with the `the` operator. There are lots of `Dividable` instances made for you. +We can inform Coalton that our constants are of another type by constraining them with `the` or relying on type inference. For example, in order to get a non-Double-Float result from `Integer` inputs, you have to constrain the result type to your desired type (as long as the type has a defined instance of the `Dividable` type class): ``` COALTON-USER> (coalton (the Single-Float (/ 4 2))) @@ -544,13 +542,17 @@ COALTON-USER> (coalton (the Fraction (/ 4 2))) #.(COALTON-LIBRARY::%FRACTION 2 1) ``` -But division of integers does not work. +An `Integer` result from division with `/` is not possible, as the instance `Dividable Integer Integer` is not defined: ``` COALTON-USER> (coalton (the Integer (/ 4 2))) -Failed to reduce context for DIVIDABLE INTEGER :A -in COALTON - [Condition of type COALTON-IMPL/TYPECHECKER::COALTON-TYPE-ERROR-CONTEXT] +; error: Unable to codegen +; --> repl input:1:22 +; | +; 1 | (COALTON (THE INTEGER (/ 4 2))) +; | ^^^^^^^ expression has type ∀. (RECIPROCABLE INTEGER) => INTEGER with unresolved constraint (RECIPROCABLE INTEGER) +; | ------- Add a type assertion with THE to resolve ambiguity +; [Condition of type COALTON-IMPL/TYPECHECKER/BASE:TC-ERROR] ``` Why shouldn't this just be `2`?! The unfortunate answer is because `/` might not *always* produce an integer `2`, and when it doesn't divide exactly, Coalton doesn't force a particular way of rounding. As such, the proper way to do it is divide exactly, then round as you please with `floor`, `ceiling`, or `round`. @@ -596,13 +598,13 @@ Lists must be homogeneous. This means the following produces a type error. ``` COALTON-USER> (coalton-toplevel - (define wut (make-list 1 2 3.0))) - -Failed to unify types SINGLE-FLOAT and INTEGER -in unification of types (INTEGER → (LIST SINGLE-FLOAT) → :A) and (:B → (LIST :B) → (LIST :B)) -in definition of WUT -in COALTON-TOPLEVEL - [Condition of type COALTON-IMPL/TYPECHECKER::COALTON-TYPE-ERROR-CONTEXT] + (define wut (make-list 1.0d0 2.0d0 3.0))) +; error: Type mismatch +; --> repl input:3:4 +; | +; 3 | (MAKE-LIST 1.0d0 2.0d0 3.0))) +; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected type '(LIST DOUBLE-FLOAT)' but got '(LIST SINGLE-FLOAT)' +; [Condition of type COALTON-IMPL/TYPECHECKER/BASE:TC-ERROR] ``` Lists can also be deconstructed with `match`. @@ -622,14 +624,14 @@ Coalton code is statically typechecked. Types are inferred. ```lisp (coalton-toplevel (define (fun x) - (map (+ 2) (parse-int x)))) + (map (+ 2) (string:parse-int x)))) ``` The type of a variable or function can be checked with `coalton:type-of`. ``` COALTON-USER> (type-of 'fun) -(STRING -> (OPTIONAL INT) +(STRING -> (OPTIONAL INTEGER) ``` Type declarations can always be added manually. @@ -638,7 +640,7 @@ Type declarations can always be added manually. (coalton-toplevel (declare fun (String -> (Optional Integer))) (define (fun x) - (map (+ 2) (parse-int x)))) + (map (+ 2) (string:parse-int x)))) ``` Type declarations can also be added in let expressions @@ -719,13 +721,19 @@ The into method is used only when a conversion can always be performed from one ((Some (Some x_)) (Some x_)) (_ None))) - - ;; Literal values can also be matched on + ;; Integers or Strings can also be matched on (define (is-5-or-7 x) (match x (5 True) (7 True) - (_ False)))) + (_ False))) + + (define (is-five-or-seven x) + (match x + ("five" True) + ("seven" True) + (_ False)))) + ``` Functions can pattern match on their arguments, but the patterns must be exhaustive. diff --git a/library/big-float/impl-default.lisp b/library/big-float/impl-default.lisp index f4b897c9f..ad4e822ec 100644 --- a/library/big-float/impl-default.lisp +++ b/library/big-float/impl-default.lisp @@ -352,7 +352,7 @@ ((BFNegInf) (error "Cannot rationalize -Inf")) ((BFNaN) (error "Cannot rationalize NaN")))) (define (best-approx x) - (coalton-library/math/real::rational-approx (get-precision) x))) + (real-approx (get-precision) x))) (define-instance (Quantizable Big-Float) (define (proper x) @@ -434,7 +434,7 @@ ((BFInf) 0) (_ BFNaN)))) -(coalton-library/math/complex::%define-standard-complex-instances Big-Float) +(complex::%define-standard-complex-instances Big-Float) (coalton-toplevel ;; SeriesSplit/SeriesResult could be extended to any ring (e.g. polynomials) diff --git a/library/big-float/impl-sbcl.lisp b/library/big-float/impl-sbcl.lisp index a4fb47431..8758a7552 100644 --- a/library/big-float/impl-sbcl.lisp +++ b/library/big-float/impl-sbcl.lisp @@ -244,7 +244,7 @@ (define-instance (Real Big-Float) (define (real-approx prec x) - (coalton-library/math/real::rational-approx prec x))) + (real-approx prec x))) (define-instance (Rational Big-Float) (define (to-fraction x) @@ -269,7 +269,7 @@ (/ (fromInt a) (fromInt b)) (into (exact/ a b)))))) -(coalton-library/math/complex::%define-standard-complex-instances Big-Float) +(complex::%define-standard-complex-instances Big-Float) (coalton-toplevel diff --git a/library/big-float/package.lisp b/library/big-float/package.lisp index fa721ff57..4e7fb8ed7 100644 --- a/library/big-float/package.lisp +++ b/library/big-float/package.lisp @@ -6,11 +6,11 @@ (:use #:coalton #:coalton-library/classes #:coalton-library/functions - #:coalton-library/math - #:coalton-library/math/integral) + #:coalton-library/math) (:import-from #:coalton-library/math/dyadic #:Dyadic) (:local-nicknames (#:dyadic #:coalton-library/math/dyadic) + (#:complex #:coalton-library/math/complex) (#:bits #:coalton-library/bits)) (:export #:RoundingMode diff --git a/library/cell.lisp b/library/cell.lisp index 42dafa6ab..98cdea7ce 100644 --- a/library/cell.lisp +++ b/library/cell.lisp @@ -144,6 +144,10 @@ (define-instance (Into (Cell :a) :a) (define into read)) + (define-instance (Into :a String => Into (Cell :a) String) + (define (into c) + (into (read c)))) + (define-instance (Default :a => Default (Cell :a)) (define (default) (new (default))))) diff --git a/library/computable-reals/computable-reals.lisp b/library/computable-reals/computable-reals.lisp index ed02ba4a0..6b4d1b73b 100644 --- a/library/computable-reals/computable-reals.lisp +++ b/library/computable-reals/computable-reals.lisp @@ -104,6 +104,12 @@ This threshold is used to ensure `Eq` and `Ord` instances terminate. (In general (lisp Creal (n) (cr:/r n))))) +(coalton-toplevel + + (define-instance (Dividable Integer CReal) + (define (general/ a b) + (/ (fromint a) (fromint b))))) + (coalton-toplevel (define-instance (math:Exponentiable Creal) diff --git a/library/lisparray.lisp b/library/lisparray.lisp index f4a249f7d..e81f8a1dd 100644 --- a/library/lisparray.lisp +++ b/library/lisparray.lisp @@ -72,7 +72,41 @@ WARNING: The consequences are undefined if an uninitialized element is read befo "Set the `i`th value of the `LispArray` `v` to `x`." (lisp Unit (v i x) (cl:setf (cl:aref v i) x) - Unit))) + Unit)) + + (lisp-toplevel () + (cl:eval-when (:compile-toplevel :load-toplevel) + (cl:defmacro define-lisparray-specialization (coalton-type lisp-type) + "Specialize lisparray access to known primitive types. This allows the lisp compiler to inline array access." + (cl:let ((ref (cl:intern (cl:format cl:nil "aref/~a" coalton-type))) + (set (cl:intern (cl:format cl:nil "set!/~a" coalton-type)))) + `(progn + (specialize aref ,ref (LispArray ,coalton-type -> UFix -> ,coalton-type)) + (declare ,ref (LispArray ,coalton-type -> UFix -> ,coalton-type)) + (define (,ref v i) + (lisp ,coalton-type (v i) + (cl:aref (cl:the (cl:simple-array ,lisp-type) v) i))) + (specialize set! ,set (LispArray ,coalton-type -> UFix -> ,coalton-type -> Unit)) + (declare ,set (LispArray ,coalton-type -> UFix -> ,coalton-type -> Unit)) + (define (,set v i x) + (lisp Unit (v i x) + (cl:setf (cl:aref (cl:the (cl:simple-array ,lisp-type) v) i) x) + Unit)))))) + ) + + (define-lisparray-specialization Single-Float cl:single-float) + (define-lisparray-specialization Double-Float cl:double-float) + (define-lisparray-specialization IFix cl:fixnum) + (define-lisparray-specialization UFix (cl:and cl:fixnum cl:unsigned-byte)) + (define-lisparray-specialization I8 (cl:signed-byte 8)) + (define-lisparray-specialization U8 (cl:unsigned-byte 8)) + (define-lisparray-specialization I16 (cl:signed-byte 16)) + (define-lisparray-specialization U16 (cl:unsigned-byte 16)) + (define-lisparray-specialization I32 (cl:signed-byte 32)) + (define-lisparray-specialization U32 (cl:unsigned-byte 32)) + (define-lisparray-specialization I64 (cl:signed-byte 64)) + (define-lisparray-specialization U64 (cl:unsigned-byte 64)) +) #+sb-package-locks (sb-ext:lock-package "COALTON-LIBRARY/LISPARRAY") diff --git a/library/list.lisp b/library/list.lisp index d472cfd66..cd5db47d0 100644 --- a/library/list.lisp +++ b/library/list.lisp @@ -10,7 +10,7 @@ (:local-nicknames (#:cell #:coalton-library/cell) (#:iter #:coalton-library/iterator) - (#:arith #:coalton-library/math/arith)) + (#:math #:coalton-library/math)) (:export #:head #:tail @@ -250,10 +250,10 @@ "Returns the nth-cdr of a list." (cond ((null? l) Nil) - ((arith:zero? n) + ((math:zero? n) l) (True - (nth-cdr (arith:1- n) (cdr l))))) + (nth-cdr (math:1- n) (cdr l))))) (declare elemIndex (Eq :a => :a -> List :a -> Optional UFix)) (define (elemIndex x xs) diff --git a/library/math/package.lisp b/library/math/package.lisp new file mode 100644 index 000000000..9c01ac948 --- /dev/null +++ b/library/math/package.lisp @@ -0,0 +1,15 @@ +(uiop:define-package #:coalton-library/math + (:use-reexport + #:coalton-library/math/arith + #:coalton-library/math/num + #:coalton-library/math/bounded + #:coalton-library/math/conversions + #:coalton-library/math/fraction + #:coalton-library/math/integral + #:coalton-library/math/real + #:coalton-library/math/complex + #:coalton-library/math/elementary + #:coalton-library/math/dual)) + +#+sb-package-locks +(sb-ext:lock-package "COALTON-LIBRARY/MATH") diff --git a/library/prelude.lisp b/library/prelude.lisp index 34edc2363..f7756e22c 100644 --- a/library/prelude.lisp +++ b/library/prelude.lisp @@ -2,22 +2,6 @@ ;;;; ;;;; Collections of packages -(uiop:define-package #:coalton-library/math - (:use-reexport - #:coalton-library/math/arith - #:coalton-library/math/num - #:coalton-library/math/bounded - #:coalton-library/math/conversions - #:coalton-library/math/fraction - #:coalton-library/math/integral - #:coalton-library/math/real - #:coalton-library/math/complex - #:coalton-library/math/elementary - #:coalton-library/math/dual)) - -#+sb-package-locks -(sb-ext:lock-package "COALTON-LIBRARY/MATH") - (uiop:define-package #:coalton-prelude (:use-reexport #:coalton-library/classes diff --git a/library/seq.lisp b/library/seq.lisp index 2bd81f621..e1fe7a19a 100644 --- a/library/seq.lisp +++ b/library/seq.lisp @@ -6,7 +6,7 @@ #:coalton-library/classes) (:local-nicknames (#:types #:coalton-library/types) - (#:math #:coalton-library/math/integral) + (#:math #:coalton-library/math) (#:optional #:coalton-library/optional) (#:cell #:coalton-library/cell) (#:vector #:coalton-library/vector) diff --git a/library/slice.lisp b/library/slice.lisp index c15c516ea..d9df32300 100644 --- a/library/slice.lisp +++ b/library/slice.lisp @@ -4,8 +4,7 @@ #:coalton-library/builtin #:coalton-library/functions #:coalton-library/classes - #:coalton-library/math/arith - #:coalton-library/math/integral) + #:coalton-library/math) (:local-nicknames (#:types #:coalton-library/types) (#:cell #:coalton-library/cell) diff --git a/library/system.lisp b/library/system.lisp index e817d5898..42c733cbb 100644 --- a/library/system.lisp +++ b/library/system.lisp @@ -3,11 +3,19 @@ #:coalton #:coalton-library/builtin #:coalton-library/classes) + (:local-nicknames + (#:math #:coalton-library/math)) (:export #:gc #:time #:sleep) (:export + #:get-real-time + #:get-run-time + #+sbcl #:get-bytes-consed + #:Profile + #:capture-profile) + (:export #:LispCondition @@ -55,12 +63,75 @@ While the result will always contain microseconds, some implementations may retu (cl:* 1000000 (cl:- end start)) cl:internal-time-units-per-second))))) - (declare sleep (Integer -> Unit)) + (declare sleep ((math:Rational :a) => :a -> Unit)) (define (sleep n) - "Sleep for `n` seconds." - (lisp Unit (n) - (cl:sleep n) - Unit))) + "Sleep for `n` seconds, where `n` can be of any type with an instance of `Rational`. + +Sleep uses type class `Rational`'s `best-approx` instead of `Real`'s `real-approx` because it handles the approximation without arbitrary precision. The only `Real` type excluded by this decision is `CReal`." + (if (math:negative? n) + (error "sleep must be a nonnegative number.") + (let ((frac (math:best-approx n))) + (lisp Unit (frac) + (cl:sleep frac) + Unit))))) + +;;; +;;; Pofiling +;;; + +(coalton-toplevel + + (declare get-run-time (Unit -> UFix)) + (define (get-run-time) + "Gets the run-time." + (lisp UFix () + (cl:get-internal-run-time))) + + (declare get-real-time (Unit -> UFix)) + (define (get-real-time) + "Gets the real-time." + (lisp UFix () + (cl:get-internal-real-time))) + + #+sbcl + (declare get-bytes-consed (Unit -> UFix)) + #+sbcl + (define (get-bytes-consed) + "Gets the number of bytes consed (only implemented for SBCL" + (lisp UFix () + (sb-ext:get-bytes-consed))) + + (define-struct (Profile :a) + "A profile of a run function." + (output + "The output of the function" :a) + (run-time + "The run time of the run" UFix) + (real-time + "The real time of the run" UFix) + #+sbcl + (bytes-consed + "The number of bytes consed during the run." UFix)) + + (declare capture-profile ((Unit -> :a) -> (Profile :a))) + (define (capture-profile f) + "Runs a function, recording profile information and returning a Profile object." + (gc) + (let (#+sbcl + (start-bytes-consed (get-bytes-consed)) + (start-run-time (get-run-time)) + (start-real-time (get-real-time)) + (value (f)) + #+sbcl + (end-bytes-consed (get-bytes-consed)) + (end-run-time (get-run-time)) + (end-real-time (get-real-time))) + (Profile + value + (- end-run-time start-run-time) + (- end-real-time start-real-time) + #+sbcl + (- end-bytes-consed start-bytes-consed))))) ;;; ;;; Gathering System information diff --git a/src/parser/toplevel.lisp b/src/parser/toplevel.lisp index bd1a8887d..2ffb24b09 100644 --- a/src/parser/toplevel.lisp +++ b/src/parser/toplevel.lisp @@ -544,8 +544,8 @@ If MODE is :macro, a package form is forbidden, and an explicit check is made fo (unless (zerop (length attributes)) (parse-error "Orphan attribute" - (note source (cdr (aref attributes 0)) - "attribute must be attached to another form"))) + (source:note (aref attributes 0) + "attribute must be attached to another form"))) (setf (program-types program) (nreverse (program-types program))) (setf (program-aliases program) (nreverse (program-aliases program))) @@ -783,10 +783,69 @@ If the outermost form matches (eval-when (compile-toplevel) ..), evaluate the en :location (form-location source form)) (program-lisp-forms program))) + +;;; Functions for working with attributes (repr, monomorphize) + +(defun consume-repr (attributes toplevel-form message) + "Return the unique repr attribute in ATTRIBUTES, or NIL. +If the attribute is not unique, or a monomorphize attribute is present, signal a parse error." + (let (repr) + (loop :for attribute :across attributes + :do (etypecase attribute + (attribute-repr + (when repr + (parse-error "Duplicate repr attribute" + (source:note attribute "repr attribute here") + (source:secondary-note repr "previous attribute here") + (source:secondary-note toplevel-form message))) + (setf repr attribute)) + (attribute-monomorphize + (parse-error "Invalid target for monomorphize attribute" + (source:note attribute "monomorphize must be attached to a define or declare form") + (source:secondary-note toplevel-form message))))) + (setf (fill-pointer attributes) 0) + repr)) + +(defun consume-monomorphize (attributes toplevel-form message) + "Return the unique monomorphize attribute in ATTRIBUTES, or NIL. +If the attribute is not unique, or a repr attribute is present, signal a parse error." + (let (monomorphize) + (loop :for attribute :across attributes + :do (etypecase attribute + (attribute-repr + (parse-error "Invalid target for repr attribute" + (source:note attribute "repr must be attached to a define-type") + (source:secondary-note toplevel-form message))) + (attribute-monomorphize + (when monomorphize + (parse-error "Duplicate monomorphize attribute" + (source:note attribute "monomorphize attribute here") + (source:secondary-note monomorphize "previous attribute here") + (source:secondary-note toplevel-form message))) + (setf monomorphize attribute)))) + (setf (fill-pointer attributes) 0) + monomorphize)) + +(defun forbid-attributes (attributes form source) + "If ATTRIBUTES is non-zero length, signal a parse error using FORM and SOURCE for location context." + (unless (zerop (length attributes)) + (let ((toplevel-form-name (string-downcase (cst:raw (cst:first form))))) + (parse-error (format nil "Invalid attribute for ~A" toplevel-form-name) + (source:note (aref attributes 0) "~A cannot have attributes" toplevel-form-name) + (secondary-note source form "when parsing ~A" toplevel-form-name))))) + + +;;; This is the parser for complete toplevel Coalton attributes, +;;; declarations and definitions. It selects a sub-parser by examining +;;; the first symbol in the form. + (defun parse-toplevel-form (form program attributes source) + "Parse a toplevel Coalton form in FORM, recording source locations that refer to SOURCE. +If the parsed form is a program definition, add it to PROGRAM and return T. +If the parsed form is an attribute (e.g., repr or monomorphize), add it to to ATTRIBUTES and return NIL." (declare (type cst:cst form) (type program program) - (type (vector (cons attribute cst:cst)) attributes) + (type (vector attribute) attributes) (values boolean &optional)) (when (cst:atom form) @@ -800,110 +859,30 @@ If the outermost form matches (eval-when (compile-toplevel) ..), evaluate the en (case (cst:raw (cst:first form)) ((coalton:monomorphize) - (vector-push-extend - (cons - (parse-monomorphize form source) - form) - attributes) + (vector-push-extend (parse-monomorphize form source) attributes) nil) ((coalton:repr) - (vector-push-extend - (cons - (parse-repr form source) - form) - attributes) + (vector-push-extend (parse-repr form source) attributes) nil) ((coalton:define) - (let ((define (parse-define form source)) - monomorphize - monomorphize-form) - (loop :for (attribute . attribute-form) :across attributes - :do (etypecase attribute - (attribute-repr - (parse-error "Invalid target for repr attribute" - (note source attribute-form - "repr must be attached to a define-type") - (source:secondary-note (toplevel-define-name define) - "when parsing define"))) - - (attribute-monomorphize - (when monomorphize - (parse-error "Duplicate monomorphize attribute" - (note source attribute-form - "monomorphize attribute here") - (secondary-note source monomorphize-form - "previous attribute here") - (source:secondary-note (toplevel-define-name define) - "when parsing define"))) - - (setf monomorphize attribute) - (setf monomorphize-form attribute-form)))) - - (setf (fill-pointer attributes) 0) + (let* ((define (parse-define form source)) + (monomorphize (consume-monomorphize attributes define "when parsing define"))) (setf (toplevel-define-monomorphize define) monomorphize) (push define (program-defines program)) t)) ((coalton:declare) - (let ((declare (parse-declare form source)) - - monomorphize - monomorphize-form) - - (loop :for (attribute . attribute-form) :across attributes - :do (etypecase attribute - (attribute-repr - (parse-error "Invalid target for repr attribute" - (note source attribute-form - "repr must be attached to a define-type") - (secondary-note source form "when parsing declare"))) - - (attribute-monomorphize - (when monomorphize - (parse-error "Duplicate monomorphize attribute" - (note source attribute-form - "monomorphize attribute here") - (secondary-note source monomorphize-form - "previous attribute here") - (secondary-note source form "when parsing declare"))) - - (setf monomorphize attribute) - (setf monomorphize-form attribute-form)))) - - (setf (fill-pointer attributes) 0) + (let* ((declare (parse-declare form source)) + (monomorphize (consume-monomorphize attributes declare "when parsing declare"))) (setf (toplevel-declare-monomorphize declare) monomorphize) (push declare (program-declares program)) t)) ((coalton:define-type) (let* ((type (parse-define-type form source)) - - repr - repr-form) - - (loop :for (attribute . attribute-form) :across attributes - :do (etypecase attribute - (attribute-repr - (when repr - (parse-error "Duplicate repr attribute" - (note source attribute-form - "repr attribute here") - (secondary-note source repr-form - "previous attribute here") - (source:secondary-note type "when parsing define-type"))) - - (setf repr attribute) - (setf repr-form attribute-form)) - - (attribute-monomorphize - (parse-error "Invalid target for monomorphize attribute" - (note source attribute-form - "monomorphize must be attached to a define or declare form") - (source:secondary-note type "when parsing define-type"))))) - - (setf (fill-pointer attributes) 0) + (repr (consume-repr attributes type "when parsing define-type"))) (setf (toplevel-define-type-repr type) repr) (push type (program-types program)) t)) @@ -930,66 +909,26 @@ If the outermost form matches (eval-when (compile-toplevel) ..), evaluate the en t)) ((coalton:define-struct) - - (let ((struct (parse-define-struct form source)) - repr - repr-form) - - (loop :for (attribute . attribute-form) :across attributes - :do (etypecase attribute - (attribute-repr - (when repr - (parse-error "Duplicate repr attribute" - (note source attribute-form "repr attribute here") - (secondary-note source repr-form "previous attribute here") - (note source (toplevel-define-struct-head-location struct) - "when parsing define-struct"))) - - (unless (eq :transparent (keyword-src-name (attribute-repr-type attribute))) - (parse-error "Invalid repr attribute" - (note source attribute-form - "structs can only be repr transparent") - (secondary-note source (toplevel-define-struct-head-location struct) - "when parsing define-struct"))) - - (setf repr attribute) - (setf repr-form attribute-form)) - - (attribute-monomorphize - (parse-error "Invalid target for monomorphize attribute" - (note source attribute-form - "monomorphize must be attached to a define or declare form") - (secondary-note source (toplevel-define-struct-name struct) - "when parsing define-type"))))) - - (setf (fill-pointer attributes) 0) + (let* ((struct (parse-define-struct form source)) + (repr (consume-repr attributes struct "when parsing define-struct"))) + (when (and repr + (not (eq :transparent (keyword-src-name (attribute-repr-type repr))))) + (parse-error "Invalid repr attribute" + (source:note repr "structs can only be repr transparent") + (source:secondary-note struct "when parsing define-struct"))) (setf (toplevel-define-struct-repr struct) repr) (push struct (program-structs program)) t)) ((coalton:define-class) + (forbid-attributes attributes form source) (let ((class (parse-define-class form source))) - - (unless (zerop (length attributes)) - (parse-error "Invalid attribute for define-class" - (note source (cdr (aref attributes 0)) - "define-class cannot have attributes") - (source:secondary-note (toplevel-define-class-head-location class) - "while parsing define-class"))) - (push class (program-classes program)) t)) ((coalton:define-instance) + (forbid-attributes attributes form source) (let ((instance (parse-define-instance form source))) - - (unless (zerop (length attributes)) - (parse-error "Invalid attribute for define-instance" - (note source (cdr (aref attributes 0)) - "define-instance cannot have attributes") - (source:secondary-note (toplevel-define-instance-head-location instance) - "while parsing define-instance"))) - (push instance (program-instances program)) t)) @@ -998,35 +937,18 @@ If the outermost form matches (eval-when (compile-toplevel) ..), evaluate the en (parse-error "Invalid lisp-toplevel form" (note source form "lisp-toplevel is only allowed in library source code. To enable elsewhere, (pushnew :coalton-lisp-toplevel *features*)"))) - (unless (zerop (length attributes)) - (parse-error "Invalid lisp-toplevel form" - (note source (cdr (aref attributes 0)) - "lisp-toplevel cannot have attributes") - (secondary-note source form - "when parsing lisp-toplevel"))) + (forbid-attributes attributes form source) (parse-lisp-toplevel-form form program source) t) ((coalton:specialize) + (forbid-attributes attributes form source) (let ((spec (parse-specialize form source))) - - (unless (zerop (length attributes)) - (source:error "Invalid attribute for specialize" - (note source (cdr (aref attributes 0)) - "specialize cannot have attributes") - (secondary-note source form "when parsing specialize"))) - (push spec (program-specializations program)) t)) ((coalton:progn) - (unless (zerop (length attributes)) - (parse-error "Invalid attribute for progn" - (note source (cdr (aref attributes 0)) - "progn cannot have attributes") - (secondary-note source form - "when parsing progn"))) - + (forbid-attributes attributes form source) (loop :for inner-form := (cst:rest form) :then (cst:rest inner-form) :while (not (cst:null inner-form)) :do (when (and (parse-toplevel-form (cst:first inner-form) program attributes source) @@ -1036,10 +958,8 @@ consume all attributes"))) (unless (zerop (length attributes)) (parse-error "Trailing attributes in progn" - (note source (cdr (aref attributes 0)) - "progn cannot have trailing attributes") - (secondary-note source form - "when parsing progn"))) + (source:note (aref attributes 0) "progn cannot have trailing attributes") + (secondary-note source form "when parsing progn"))) t) (t diff --git a/src/typechecker/base.lisp b/src/typechecker/base.lisp index 701cdca23..5f73c254a 100644 --- a/src/typechecker/base.lisp +++ b/src/typechecker/base.lisp @@ -103,13 +103,11 @@ This requires a valid PPRINT-VARIABLE-CONTEXT") ;;; Assertions ;;; -(defun check-duplicates (elems f g callback) - "Check for duplicate elements in ELEMS. F maps items in ELEMS to -symbols which are compared for equality. G maps items in ELEMS to -source locations whose spans are compared for ordering." +(defun check-duplicates (elems f callback) + "Check for duplicate elements in ELEMS. F maps items in ELEMS to symbols which are compared for equality. +As soon as two duplicate elements are detected, CALLBACK is invoked with those two elements, ordered by source location." (declare (type list elems) (type function f) - (type function g) (type function callback)) (loop :with table := (make-hash-table :test #'eq) @@ -123,8 +121,8 @@ source locations whose spans are compared for ordering." :do (let ((first (gethash id table)) (second elem)) - (when (> (car (source:location-span (funcall g first))) - (car (source:location-span (funcall g second)))) + (unless (source:location< (source:location first) + (source:location second)) (psetf first second second first)) (funcall callback first second)) diff --git a/src/typechecker/define-class.lisp b/src/typechecker/define-class.lisp index 110362d16..a8ce500c0 100644 --- a/src/typechecker/define-class.lisp +++ b/src/typechecker/define-class.lisp @@ -62,7 +62,6 @@ (check-duplicates classes (alexandria:compose #'parser:identifier-src-name #'parser:toplevel-define-class-name) - #'source:location (lambda (first second) (tc:tc-error "Duplicate class definition" (tc:tc-location (parser:toplevel-define-class-head-location first) @@ -74,7 +73,6 @@ (check-duplicates (mapcan (alexandria:compose #'copy-list #'parser:toplevel-define-class-methods) classes) (alexandria:compose #'parser:identifier-src-name #'parser:method-definition-name) - #'source:location (lambda (first second) (tc:tc-error "Duplicate method definition" (tc:tc-note first "first definition here") @@ -85,7 +83,6 @@ (check-duplicates (parser:toplevel-define-class-vars class) #'parser:keyword-src-name - #'source:location (lambda (first second) (tc:tc-error "Duplicate class variable" (tc:tc-note first "first usage here") @@ -350,7 +347,6 @@ (check-duplicates vars #'parser:keyword-src-name - #'source:location (lambda (first second) (tc:tc-error "Duplicate variable in function dependency" (tc:tc-note first "first usage here") diff --git a/src/typechecker/define-instance.lisp b/src/typechecker/define-instance.lisp index 12dbdad44..1517a1e65 100644 --- a/src/typechecker/define-instance.lisp +++ b/src/typechecker/define-instance.lisp @@ -210,7 +210,6 @@ (check-duplicates (parser:toplevel-define-instance-methods unparsed-instance) (alexandria:compose #'parser:node-variable-name #'parser:instance-method-definition-name) - #'source:location (lambda (first second) (tc-error "Duplicate method definition" (tc-note first "first definition here") diff --git a/src/typechecker/define-type.lisp b/src/typechecker/define-type.lisp index 047177695..068161fb2 100644 --- a/src/typechecker/define-type.lisp +++ b/src/typechecker/define-type.lisp @@ -104,7 +104,6 @@ (check-duplicates (append types structs aliases) (alexandria:compose #'parser:identifier-src-name #'parser:type-definition-name) - #'source:location (lambda (first second) (tc:tc-error "Duplicate type definitions" (tc:tc-note first "first definition here") @@ -116,7 +115,6 @@ (mapcan (alexandria:compose #'copy-list #'parser:type-definition-ctors) (append types structs aliases)) (alexandria:compose #'parser:identifier-src-name #'parser:type-definition-ctor-name) - #'source:location (lambda (first second) (tc:tc-error "Duplicate constructor definitions" (tc:tc-note first "first definition here") @@ -127,7 +125,6 @@ :do (check-duplicates (parser:type-definition-vars type) #'parser:keyword-src-name - #'source:location (lambda (first second) (tc:tc-error "Duplicate type variable definitions" (tc:tc-note first "first definition here") diff --git a/src/typechecker/define.lisp b/src/typechecker/define.lisp index cc4d38010..68482d118 100644 --- a/src/typechecker/define.lisp +++ b/src/typechecker/define.lisp @@ -103,7 +103,6 @@ (check-duplicates defines (alexandria:compose #'parser:node-variable-name #'parser:toplevel-define-name) - #'source:location (lambda (first second) (tc-error "Duplicate definition" (tc-note (parser:toplevel-define-name first) @@ -115,7 +114,6 @@ (check-duplicates declares (alexandria:compose #'parser:identifier-src-name #'parser:toplevel-declare-name) - #'source:location (lambda (first second) (tc-error "Duplicate declaration" (tc-note (parser:toplevel-declare-name first) @@ -480,7 +478,6 @@ Returns (VALUES INFERRED-TYPE PREDICATES NODE SUBSTITUTIONS)") (check-duplicates (parser:pattern-variables (parser:node-abstraction-params node)) #'parser:pattern-var-name - #'source:location (lambda (first second) (tc-error "Duplicate parameters name" (tc-note first "first parameter here") @@ -571,7 +568,6 @@ Returns (VALUES INFERRED-TYPE PREDICATES NODE SUBSTITUTIONS)") (check-duplicates (parser:node-let-bindings node) (alexandria:compose #'parser:node-variable-name #'parser:node-let-binding-name) - #'source:location (lambda (first second) (tc-error "Duplicate definition in let" (tc-note first "first definition here") @@ -1497,7 +1493,6 @@ Returns (VALUES INFERRED-TYPE NODE SUBSTITUTIONS)") (check-duplicates (parser:pattern-variables pat) #'parser:pattern-var-name - #'source:location (lambda (first second) (tc-error "Duplicate pattern variable" (tc-note first "first definition here") @@ -2125,7 +2120,6 @@ Returns (VALUES INFERRED-TYPE NODE SUBSTITUTIONS)") (check-duplicates (parser:pattern-variables (parser:binding-parameters binding)) #'parser:pattern-var-name - #'source:location (lambda (first second) (tc-error "Duplicate parameters name" (tc-note first "first parameter here") diff --git a/tests/lisparray-tests.lisp b/tests/lisparray-tests.lisp new file mode 100644 index 000000000..78b07a78f --- /dev/null +++ b/tests/lisparray-tests.lisp @@ -0,0 +1,81 @@ +(cl:in-package #:coalton-native-tests) + +;; LispArray accessor is specialized for primitive types. Make sure +;; they all work. + +(coalton-toplevel + (declare array/generic (array:LispArray (Optional Integer))) + (define array/generic (array:make 10 None)) + + (declare array/i8 (array:LispArray I8)) + (define array/i8 (array:make 10 0)) + (declare array/u8 (array:LispArray U8)) + (define array/u8 (array:make 10 0)) + + (declare array/i16 (array:LispArray I16)) + (define array/i16 (array:make 10 0)) + (declare array/u16 (array:LispArray U16)) + (define array/u16 (array:make 10 0)) + + (declare array/i32 (array:LispArray I32)) + (define array/i32 (array:make 10 0)) + (declare array/u32 (array:LispArray U32)) + (define array/u32 (array:make 10 0)) + + (declare array/i64 (array:LispArray I64)) + (define array/i64 (array:make 10 0)) + (declare array/u64 (array:LispArray U64)) + (define array/u64 (array:make 10 0)) + + (declare array/ifix (array:LispArray IFix)) + (define array/ifix (array:make 10 0)) + (declare array/ufix (array:LispArray UFix)) + (define array/ufix (array:make 10 0)) + + (declare array/single-float (array:LispArray Single-Float)) + (define array/single-float (array:make 10 0.0)) + (declare array/double-float (array:LispArray Double-Float)) + (define array/double-float (array:make 10 0.0d0)) + ) + +(define-test array-length () + (is (== (array:length array/generic) 10)) + ) + +(define-test array-access () + (is (== (array:set! array/generic 0 (Some 3)) Unit)) + (is (== (array:aref array/generic 0) (Some 3))) + + (is (== (array:set! array/i8 0 -4) Unit)) + (is (== (array:aref array/i8 0) -4)) + (is (== (array:set! array/u8 0 57) Unit)) + (is (== (array:aref array/u8 0) 57)) + + (is (== (array:set! array/i16 0 -444) Unit)) + (is (== (array:aref array/i16 0) -444)) + (is (== (array:set! array/u16 0 575) Unit)) + (is (== (array:aref array/u16 0) 575)) + + (is (== (array:set! array/i32 0 -104444) Unit)) + (is (== (array:aref array/i32 0) -104444)) + (is (== (array:set! array/u32 0 575939) Unit)) + (is (== (array:aref array/u32 0) 575939)) + + (is (== (array:set! array/i64 0 -9223372036854775807) Unit)) + (is (== (array:aref array/i64 0) -9223372036854775807)) + (is (== (array:set! array/u64 0 18446744073709551615) Unit)) + (is (== (array:aref array/u64 0) 18446744073709551615)) + + (let ((ifixnum (lisp IFix () cl:most-negative-fixnum)) + (ufixnum (lisp UFix () cl:most-positive-fixnum))) + (is (== (array:set! array/ifix 0 ifixnum) Unit)) + (is (== (array:aref array/ifix 0) ifixnum)) + (is (== (array:set! array/ufix 0 ufixnum) Unit)) + (is (== (array:aref array/ufix 0) ufixnum)) + ) + + (is (== (array:set! array/single-float 0 3.1415) Unit)) + (is (== (array:aref array/single-float 0) 3.1415)) + (is (== (array:set! array/double-float 0 2.71828d0) Unit)) + (is (== (array:aref array/double-float 0) 2.71828d0)) + ) diff --git a/tests/package.lisp b/tests/package.lisp index d3ca3f4f3..d204fbea0 100644 --- a/tests/package.lisp +++ b/tests/package.lisp @@ -31,6 +31,7 @@ (#:cell #:coalton-library/cell) (#:iter #:coalton-library/iterator) (#:list #:coalton-library/list) + (#:array #:coalton-library/lisparray) (#:red-black/tree #:coalton-library/ord-tree) (#:red-black/map #:coalton-library/ord-map) (#:result #:coalton-library/result) diff --git a/tests/test-files/define-class.txt b/tests/test-files/define-class.txt index 459effd5e..a6db31fbb 100644 --- a/tests/test-files/define-class.txt +++ b/tests/test-files/define-class.txt @@ -453,7 +453,7 @@ error: Invalid attribute for define-class 3 | (repr :enum) | ^^^^^^^^^^^^ define-class cannot have attributes 4 | (define-class (C :a)) - | ------ while parsing define-class + | --------------------- when parsing define-class ================================================================================ 119 Malformed class definition diff --git a/tests/test-files/define-instance.txt b/tests/test-files/define-instance.txt index d2b27eca5..eb8a1028a 100644 --- a/tests/test-files/define-instance.txt +++ b/tests/test-files/define-instance.txt @@ -281,7 +281,7 @@ error: Invalid attribute for define-instance 3 | (repr :enum) | ^^^^^^^^^^^^ define-instance cannot have attributes 4 | (define-instance (C :a)) - | ------ while parsing define-instance + | ------------------------ when parsing define-instance ================================================================================ 110 Malformed instance definition diff --git a/tests/test-files/define.txt b/tests/test-files/define.txt index 5cc02ab77..8e694b2a0 100644 --- a/tests/test-files/define.txt +++ b/tests/test-files/define.txt @@ -139,7 +139,7 @@ error: Duplicate monomorphize attribute 4 | (monomorphize) | ^^^^^^^^^^^^^^ monomorphize attribute here 5 | (define f x) - | - when parsing define + | ------------ when parsing define ================================================================================ Malformed definition @@ -206,7 +206,7 @@ error: Invalid target for repr attribute 3 | (repr :enum) | ^^^^^^^^^^^^ repr must be attached to a define-type 4 | (define f x) - | - when parsing define + | ------------ when parsing define ================================================================================ Unknown variable diff --git a/tests/test-files/lisp-toplevel.txt b/tests/test-files/lisp-toplevel.txt index 807e2f7ee..b5755c621 100644 --- a/tests/test-files/lisp-toplevel.txt +++ b/tests/test-files/lisp-toplevel.txt @@ -53,7 +53,7 @@ error: Invalid lisp-toplevel form -------------------------------------------------------------------------------- -error: Invalid lisp-toplevel form +error: Invalid attribute for lisp-toplevel --> test:3:0 | 3 | (repr :lisp)