commit dfa5f226277fc9cafc4418c27455693cbc59380c
Author: Georges Dupéron <georges.duperon@gmail.com>
Date: Thu, 27 Apr 2017 23:41:41 +0200
Squashed commits
Diffstat:
19 files changed, 1082 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,6 @@
+*~
+\#*
+.\#*
+.DS_Store
+compiled/
+/doc/
diff --git a/.travis.yml b/.travis.yml
@@ -0,0 +1,31 @@
+language: c
+sudo: false
+
+env:
+ global:
+ # RACKET_DIR is an argument to install-racket.sh
+ - RACKET_DIR=~/racket
+ - PATH="$RACKET_DIR/bin:$PATH"
+ matrix:
+ # RACKET_VERSION is an argument to install-racket.sh
+ - RACKET_VERSION=6.5
+ - RACKET_VERSION=6.6
+ - RACKET_VERSION=6.7
+ - RACKET_VERSION=6.8
+ - RACKET_VERSION=RELEASE
+ - RACKET_VERSION=HEAD
+
+before_install:
+- curl -L https://raw.githubusercontent.com/greghendershott/travis-racket/master/install-racket.sh | bash
+- raco pkg install --deps search-auto doc-coverage cover cover-codecov # or cover-coveralls
+
+install:
+- raco pkg install --deps search-auto -j 2
+
+script:
+- raco test -x -p "$(basename "$TRAVIS_BUILD_DIR")"
+- raco setup --check-pkg-deps --no-zo --no-launcher --no-install --no-post-install --no-docs --pkgs "$(basename "$TRAVIS_BUILD_DIR")"
+- raco doc-coverage "$(basename "$TRAVIS_BUILD_DIR")"
+- raco cover -s main -s test -s doc -f codecov -f html -d ~/coverage . || true
+# TODO: add an option to cover to run the "outer" module too, not just the submodules.
+# TODO: deploy the coverage info.
+\ No newline at end of file
diff --git a/LICENSE-more.md b/LICENSE-more.md
@@ -0,0 +1,28 @@
+remember
+
+Parts of this this software were initially written as part of a project
+at Cortus, S.A.S. which can be reached at 97 Rue de Freyr, 34000
+Montpellier, France. I got their permission to redistribute the code in
+the Public Domain.
+
+
+
+This package is in distributed under the Creative Commons CC0 license
+https://creativecommons.org/publicdomain/zero/1.0/, as specified by
+the LICENSE.txt file.
+
+
+
+The CC0 license is equivalent to a dedication to the Public Domain
+in most countries, but is also effective in countries which do not
+recognize explicit dedications to the Public Domain.
+
+
+
+In order to avoid any potential licensing issues, this package is explicitly
+distributed under the Creative Commons CC0 license
+https://creativecommons.org/publicdomain/zero/1.0/, or under the GNU Lesser
+General Public License (LGPL) https://opensource.org/licenses/LGPL-3.0, or
+under the Apache License Version 2.0
+https://opensource.org/licenses/Apache-2.0, or under the MIT license
+https://opensource.org/licenses/MIT, at your option.
diff --git a/LICENSE.txt b/LICENSE.txt
@@ -0,0 +1,116 @@
+CC0 1.0 Universal
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific
+works ("Commons") that the public can reliably and without fear of later
+claims of infringement build upon, modify, incorporate in other works, reuse
+and redistribute as freely as possible in any form whatsoever and for any
+purposes, including without limitation commercial purposes. These owners may
+contribute to the Commons to promote the ideal of a free culture and the
+further production of creative, cultural and scientific works, or to gain
+reputation or greater distribution for their Work in part through the use and
+efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation
+of additional consideration or compensation, the person associating CC0 with a
+Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+and publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not limited
+to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+and Related Rights and associated claims and causes of action, whether now
+known or unknown (including existing as well as future claims and causes of
+action), in the Work (i) in all territories worldwide, (ii) for the maximum
+duration provided by applicable law or treaty (including future time
+extensions), (iii) in any current or future medium and for any number of
+copies, and (iv) for any purpose whatsoever, including without limitation
+commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+the Waiver for the benefit of each member of the public at large and to the
+detriment of Affirmer's heirs and successors, fully intending that such Waiver
+shall not be subject to revocation, rescission, cancellation, termination, or
+any other legal or equitable action to disrupt the quiet enjoyment of the Work
+by the public as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+judged legally invalid or ineffective under applicable law, then the Waiver
+shall be preserved to the maximum extent permitted taking into account
+Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+is so judged Affirmer hereby grants to each affected person a royalty-free,
+non transferable, non sublicensable, non exclusive, irrevocable and
+unconditional license to exercise Affirmer's Copyright and Related Rights in
+the Work (i) in all territories worldwide, (ii) for the maximum duration
+provided by applicable law or treaty (including future time extensions), (iii)
+in any current or future medium and for any number of copies, and (iv) for any
+purpose whatsoever, including without limitation commercial, advertising or
+promotional purposes (the "License"). The License shall be deemed effective as
+of the date CC0 was applied by Affirmer to the Work. Should any part of the
+License for any reason be judged legally invalid or ineffective under
+applicable law, such partial invalidity or ineffectiveness shall not
+invalidate the remainder of the License, and in such case Affirmer hereby
+affirms that he or she will not (i) exercise any of his or her remaining
+Copyright and Related Rights in the Work or (ii) assert any associated claims
+and causes of action with respect to the Work, in either case contrary to
+Affirmer's express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+<http://creativecommons.org/publicdomain/zero/1.0/>
diff --git a/README.md b/README.md
@@ -0,0 +1,49 @@
+[](https://travis-ci.org/jsmaniac/remember)
+[](https://codecov.io/gh/jsmaniac/remember)
+[](http://jsmaniac.github.io/travis-stats/#jsmaniac/remember)
+[](http://docs.racket-lang.org/remember/)
+[](https://github.com/jsmaniac/remember/issues)
+[](https://creativecommons.org/publicdomain/zero/1.0/)
+
+remember
+========
+
+This Racket library provides a compile-time memoize feature. It allows
+remembering a value with `(remember-write! 'category 'value)`. In subsequent
+compilations, `(get-remembered 'category)` will return a set of all
+previously-remembered values.
+
+Installation
+============
+
+raco pkg install remember
+
+Example use case: the `phc-adt` library
+=======================================
+
+This library is used to implement "interned" structure and constructor types
+in the [`phc-adt`](https://github.com/jsmaniac/phc-adt) library. The `phc-adt`
+library needs to know the set of all structure and constructor types used in
+the program, and uses `remember` to automatically memoize structure
+descriptors and constructor names.
+
+When the `structure` macro defined in
+[`structure.hl.rkt`](https://github.com/jsmaniac/phc-adt/blob/refactor/structure.hl.rkt)
+encounters an unknown list of field names, it uses the `remember` library to
+append the tuple of field names to a user-specified file. That file is loaded
+in subsequent compilations, so that the tuple of fields is known to `phc-adt`.
+
+The memoized descriptors are used to know all possible structs that can
+contain a field with the desired name when accessing it with `(get instance
+field-name)`. The `get` macro can then retrieve the field's value using the
+right accessor (for example `(struct123-fieldname instance)`). Knowing all
+existing structures allows `get` to perform some kind of dynamic dispatch to
+obtain the appropriate accessor, for example using a `cond` which tests for
+all possible types.
+
+The `constructor` macro defined in
+[`constructor.hl.rkt`](https://github.com/jsmaniac/phc-adt/blob/refactor/constructor.hl.rkt)
+works in the same way, but remembers the name of the constructor's tag instead
+of field names. The memoization feature is used so that all uses of a
+constructor with a given name are equivalent, across all files.
+
diff --git a/info.rkt b/info.rkt
@@ -0,0 +1,22 @@
+#lang info
+(define collection "remember")
+(define deps '("base"
+ "rackunit-lib"
+ "compatibility-lib"
+ "scribble-lib"
+ "typed-racket-lib"
+ "phc-toolkit"
+ "hyper-literate"))
+(define build-deps '("scribble-lib"
+ "racket-doc"
+ "typed-racket-doc"
+ "scribble-enhanced"))
+(define scribblings '(("scribblings/remember.scrbl" ())
+ ("remember-implementation.hl.rkt" () (omit-start))))
+(define compile-omit-paths '("test/test-error.rkt"))
+(define test-omit-paths '("test/test-error.rkt"))
+(define pkg-desc (string-append "Compile-time memoize across compilations."
+ " Writes values to a file, so that they will"
+ " be remembered during the next compilation."))
+(define version "0.9")
+(define pkg-authors '(|Georges Dupéron|))
diff --git a/licenses/bsd.txt b/licenses/bsd.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2000-2015 Dipanwita Sarkar, Andrew W. Keep, R. Kent Dybvig, Oscar Waddell
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+\ No newline at end of file
diff --git a/licenses/lgpl-3.0--license.txt b/licenses/lgpl-3.0--license.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/main.rkt b/main.rkt
@@ -0,0 +1,3 @@
+#lang racket
+(require "remember-implementation.hl.rkt")
+(provide (all-from-out "remember-implementation.hl.rkt"))
+\ No newline at end of file
diff --git a/remember-implementation.hl.rkt b/remember-implementation.hl.rkt
@@ -0,0 +1,303 @@
+#lang hyper-literate racket/base
+@(require scribble-enhanced/doc)
+@doc-lib-setup
+
+@title[#:style manual-doc-style
+ #:tag "remember"
+ #:tag-prefix "(lib remember/remember-implementation.hl.rkt)"
+ ]{Implementation of Remember}
+
+@(chunks-toc-prefix
+ '("(lib remember/remember-implementation.hl.rkt)"))
+
+@(table-of-contents)
+
+@section{@racket[remember]}
+
+This module allows macros to remember some values across
+compilations. Values are stored within the
+@tc[remembered-values] hash table, which associates a
+@racket[_category] (a symbol) with a set of values.
+
+@chunk[<remembered-values>
+ (begin-for-syntax
+ (define remembered-values (make-hash)))]
+
+A second set tracks values which were recently written, but
+not initially added via @racket[remembered!] or
+@racket[remembered-add!].
+
+@chunk[<remembered-values>
+ (begin-for-syntax
+ (define written-values (make-hash)))]
+
+The user can specify input files from which remembered
+values are loaded, and optionally an output file to which
+new, not-yet-remembered values will be appended:
+
+@CHUNK[<remember-file>
+ (define-for-syntax remember-output-file-parameter
+ (make-parameter #f (or? path-string? false?)))
+
+ (define-syntax (remember-output-file stx)
+ (syntax-case stx ()
+ [(_ new-value)
+ (string? (syntax-e #'new-value))
+ (begin (remember-output-file-parameter (syntax-e #'new-value))
+ #'(void))]
+ [(_)
+ (quasisyntax/loc stx remember-output-file-parameter)]))
+
+ (define-syntax (remember-input-file stx)
+ (syntax-case stx ()
+ [(_ name)
+ (string? (syntax-e #'name))
+ #'(require (only-in name))]))
+
+ (define-syntax-rule (remember-io-file name)
+ (begin (remember-input-file name)
+ (remember-output-file name)))]
+
+@CHUNK[<remember>
+ (define-syntax-rule (remembered! category value)
+ (begin-for-syntax
+ (remembered-add! 'category 'value)))
+
+ (define-for-syntax writable?
+ (disjoin number?
+ string?
+ symbol?
+ char?
+ null?
+ (λ (v) (and (pair? v)
+ (writable? (car v))
+ (writable? (cdr v))))
+ (λ (v) (and (vector? v)
+ (andmap writable? (vector->list v))))))
+
+ (define-for-syntax (remembered-add! category value)
+ (unless (writable? value)
+ (error "Value to remember does not seem to be safely writable:"
+ value))
+ (unless (symbol? category)
+ (error (format "The category was not a symbol, when remembering ~a:"
+ value)
+ category))
+ (hash-update! remembered-values
+ category
+ (λ (s) (set-add s value))
+ set))
+
+ (define-for-syntax (remembered-add-written! category value)
+ (unless (writable? value)
+ (error "Value to remember does not seem to be safely writable:"
+ value))
+ (unless (symbol? category)
+ (error (format "The category was not a symbol, when remembering ~a:"
+ value)
+ category))
+ (hash-update! written-values
+ category
+ (λ (s) (set-add s value))
+ set))
+
+ (define-for-syntax (remembered? category value)
+ (unless (writable? value)
+ (error "Value to remember does not seem to be safely writable:"
+ value))
+ (set-member? (hash-ref remembered-values category set) value))
+
+ (define-for-syntax (written? category value)
+ (unless (writable? value)
+ (error "Value to remember does not seem to be safely writable:"
+ value))
+ (set-member? (hash-ref written-values category set) value))
+
+ (define-for-syntax (remembered-or-written? category value)
+ (or (remembered? category value)
+ (written? category value)))
+
+ (define-for-syntax (remember-write! category value)
+ (unless (writable? value)
+ (error "Value to remember does not seem to be safely writable:"
+ value))
+ (unless (or (remembered? category value)
+ (written? category value))
+ (when (remember-output-file-parameter)
+ (with-output-file [port (remember-output-file-parameter)]
+ #:exists 'append
+ (writeln (list 'remembered! category value)
+ port)))
+ (remembered-add-written! category value)))]
+
+@chunk[<delayed-errors>
+ (begin-for-syntax
+ (define remember-errors-list '())
+ (define remember-lifted-error #f))]
+
+@chunk[<error>
+ (define-for-syntax (remembered-error! category
+ stx-value
+ [stx-errs (list stx-value)])
+ (set! remember-errors-list
+ (cons (list category stx-value stx-errs) remember-errors-list))
+
+ (unless (disable-remember-immediate-error)
+ (if (not (syntax-local-lift-context))
+ ;; Trigger the error right now
+ (remember-all-hard-error)
+ ;; Lift a delayed error, which will be triggered later on
+ (lift-maybe-delayed-errors))))
+
+ (define-for-syntax (remembered-add-error! category stx-value)
+ (remembered-add! category (syntax-e stx-value))
+ (remembered-error! category stx-value))]
+
+@CHUNK[<remember-all-hard-error>
+ ;; These two functions allow us to wait around 1000 levels of nested
+ ;; macro-expansion before triggering the error.
+ ;; If the error is triggered immediately when the lifted statements are
+ ;; added at the end of the module, then it can get executed before macros
+ ;; used in the righ-hand side of a (define …) are expanded, for example.
+ ;; Since these macros may need to remember more values, it's better to
+ ;; wait until they are all expanded.
+ ;; The number 1000 above in #`(delay-remember-all-hard-error1 1000) is
+ ;; arbitrary, but should be enough for most practical purposes, worst
+ ;; case the file would require a few more compilations to settle.
+ (define-syntax (delay-remember-all-hard-error1 stx)
+ (syntax-case stx ()
+ [(_ n)
+ (number? (syntax-e #'n))
+ (if (> (syntax-e #'n) 0)
+ #`(let ()
+ (define blob
+ (delay-remember-all-hard-error2 #,(- (syntax-e #'n) 1)))
+ (void))
+ (begin (syntax-local-lift-module-end-declaration
+ #`(remember-all-hard-error-macro))
+ #'(void)))]))
+
+ (define-syntax (delay-remember-all-hard-error2 stx)
+ (syntax-case stx ()
+ [(_ n)
+ (number? (syntax-e #'n))
+ (begin
+ (syntax-local-lift-module-end-declaration
+ #'(delay-remember-all-hard-error1 n))
+ #'n)]))
+
+ (define-for-syntax (remember-all-hard-error)
+ (define remember-errors-list-orig remember-errors-list)
+ (set! remember-errors-list '())
+ (unless (empty? remember-errors-list-orig)
+ (raise-syntax-error
+ 'remember
+ (format (~a "The values ~a were not remembered."
+ " Some of them may have been added to the"
+ " appropriate list automatically."
+ " Please recompile this file now.")
+ (string-join (remove-duplicates
+ (reverse
+ (stx-map (compose ~a syntax->datum)
+ (map cadr
+ remember-errors-list-orig))))
+ ", "))
+ #f
+ #f
+ (remove-duplicates
+ (append-map caddr remember-errors-list-orig)
+ #:key (λ (e)
+ (cons (syntax->datum e)
+ (build-source-location-list e)))))))
+ (define-syntax (remember-all-hard-error-macro stx)
+ (remember-all-hard-error)
+ #'(void))]
+
+The @racket[disable-remember-immediate-error] parameter allows code to
+temporarily prevent @racket[remembered-error!] from lifting a delayed error.
+This can be useful for example when calling @racket[remembered-error!] from a
+context where @racket[(syntax-local-lift-context)] is @racket[#false], e.g.
+outside of the expansion of a macro, but within a @racket[begin-for-syntax]
+block.
+
+@chunk[<disable-remember-errors>
+ (define-for-syntax disable-remember-immediate-error (make-parameter #f))]
+
+The error is still put aside, so that if a delayed error was triggered by
+another call to @racket[remembered-error!], the error will still be included
+with the other delayed errors. If no delayed error is triggered during
+macro-expansion, the error that was put aside will be ignored. To prevent
+that, the user can call @racket[lift-maybe-delayed-errors] within a context
+where lifts are possible.
+
+@chunk[<lift-maybe-delayed-errors>
+ (define-for-syntax (lift-maybe-delayed-errors)
+ (if (syntax-transforming-module-expression?)
+ ;; Lift a delayed error, attempting to allow several (1000) levels
+ ;; of nested let blocks to expand before pulling the alarm signal.
+ (unless remember-lifted-error
+ (set! remember-lifted-error #t)
+ (syntax-local-lift-module-end-declaration
+ #`(delay-remember-all-hard-error1 1000)))
+ ;; Lift a delayed error, which will be triggered after the current
+ ;; expansion pass (i.e. before the contents of any let form is
+ ;; expanded).
+ (syntax-local-lift-expression
+ #`(remember-all-hard-error-macro))))]
+
+
+@CHUNK[<get-remembered>
+ (define-for-syntax (get-remembered category)
+ (hash-ref remembered-values category set))]
+
+@chunk[<provide>
+ (begin-for-syntax
+ (provide get-remembered
+ remembered-add!
+ remembered?
+ remembered-or-written?
+ remember-write!
+ remembered-error!
+ remember-output-file-parameter
+ disable-remember-immediate-error
+ lift-maybe-delayed-errors))
+ (provide remember-input-file
+ remember-output-file
+ remember-io-file
+ remembered!)
+
+ (module+ private
+ (begin-for-syntax
+ (provide remembered-add-written!)))]
+
+@; TODO: circumvents bug https://github.com/racket/scribble/issues/44
+@(require racket/require)
+@chunk[<*>
+ (require mzlib/etc
+ ;; TODO: circumvent https://github.com/racket/scribble/issues/44
+ racket/require
+ (subtract-in phc-toolkit/untyped syntax/stx)
+ syntax/stx
+ (for-syntax racket/base
+ racket/function
+ racket/bool
+ racket/set
+ racket/list
+ mzlib/etc
+ ;;TODO: https://github.com/racket/scribble/issues/44
+ (subtract-in phc-toolkit/untyped
+ syntax/stx)
+ syntax/stx
+ syntax/srcloc
+ racket/string
+ racket/format))
+ <provide>
+ <remembered-values>
+ <remember-file>
+ <remember>
+ <get-remembered>
+ <delayed-errors>
+ <disable-remember-errors>
+ <lift-maybe-delayed-errors>
+ <remember-all-hard-error>
+ <error>]
+\ No newline at end of file
diff --git a/scribblings/remember.scrbl b/scribblings/remember.scrbl
@@ -0,0 +1,258 @@
+#lang scribble/manual
+@require[@for-label[remember
+ racket/base]]
+
+@title{Remember: storage for macros which is persistant across compilations}
+@author{Georges Dupéron}
+
+@defmodule[remember]
+
+This library is implemented using literate programming. The
+implementation details are presented in
+@other-doc['(lib "remember/remember-implementation.hl.rkt")].
+
+This module allows macros to remember some values across
+compilations. Values are grouped by @racket[_category], so
+that multiple macros can use this facility without
+interfering with each other. The @racket[_category] is
+simply a symbol given when remembering the value.
+
+The list of all remembered values for a given
+@racket[_category] is returned by @racket[get-remembered],
+and it is possible to check if a single value has been
+remembered using @racket[remembered?].
+
+Values are loaded from files using
+@racket[remember-input-file] and @racket[remember-io-file].
+An output file can be set with
+@racket[remember-output-file] and
+@racket[remember-io-file].
+
+When an output file has been declared, new values passed to
+@racket[remember-write!] are marked as
+@racket[remembered-or-written?] and appended to that file
+(more precisely, the expression
+@racket[(remembered! _category _value)] is appended to the
+file, followed by a newline).
+
+When initially created by the user, the output file should
+contain the code below, which will be followed by the
+automatically-generated
+@racket[(remembered! _category _value)] statements:
+
+@codeblock[#:keep-lang-line? #t]|{
+ #lang racket
+ (require remember)}|
+
+The @racket[remembered!] macro indicates an
+already-remembered value, and is typically used inside input
+files. The @racket[for-syntax] function
+@racket[remembered-add!] can also be used instead, to mark a
+value as @racket[remembered?] without adding it to any file
+(this can be useful for values which should implicitly be
+remembered).
+
+@defproc[#:kind "for-syntax procedure"
+ (get-remembered [category symbol?]) list?]{
+ Returns a list of all values that have been remembered for
+ the given @racket[category] (i.e. all values passed as the
+ second argument to @racket[remembered-add!],
+ @racket[remember-write!] or @racket[remembered!], with the given
+ category as the first argument).}
+
+@defproc[#:kind "for-syntax procedure"
+ (remembered-add! [category symbol?] [value any/c]) void?]{
+ Marks the given @racket[value] as remembered in the given
+ @racket[category]. If the same value is remembered twice
+ for the same category, the second occurrence is ignored
+ (i.e. values are stored in a distinct @racket[set] for each
+ category).
+
+ This @racket[for-syntax] procedure is called by the
+ @racket[remembered!] macro, but can also be executed on its
+ own.}
+
+@defproc[#:kind "for-syntax procedure"
+ (remembered? [category symbol?] [value any/c]) boolean?]{
+ Checks whether the given @racket[value] has already been
+ added to the set of remembered values for the given
+ @racket[category].}
+
+@defproc[#:kind "for-syntax procedure"
+ (remembered-or-written? [category symbol?] [value any/c]) boolean?]{
+ Checks whether the given @racket[value] has already been
+ added to the set of remembered values for the given
+ @racket[category], or if it was freshly written to a file
+ during the current expansion.}
+
+@defproc[#:kind "for-syntax procedure"
+ (remember-write! [category symbol?] [value any/c]) void?]{
+ Adds the given @racket[value] to the current
+ @racket[remember-output-file] for the given category. More
+ precisely, the expression
+ @racket[(remembered! category value)] is appended to the
+ file, followed by a newline.
+
+ If the value is already @racket[remembered-or-written?],
+ then the file is left unchanged, i.e. two or more calls to
+ @racket[remember-write!] with the same @racket[category]
+ and @racket[value] will only append an expression to the
+ file the first time.
+
+ The value is also added to the set of
+ @racket[remembered-or-written?] values, so that subsequent
+ calls to @racket[remembered-or-written?] return
+ @racket[#t] for that category and value. Calls to
+ @racket[remembered?] will be unaffected, and will still
+ return @racket[#f]. If some declarations are created by a
+ library based on the @racket[get-remembered] set, it is
+ therefore possible to check whether a value was already
+ present, or if it was added by a subsequent
+ @racket[remember-write!].}
+
+@defproc[#:kind "for-syntax procedure"
+ (remembered-error! [category symbol] [stx-value syntax?]) void?]{
+ Produces a delayed error indicating that this value has
+ not been remembered, but was added to the output file.
+
+ This procedure just triggers the error, and is not
+ concerned with actually adding the value to the output
+ file.
+
+ The error is added in a lifted declaration which is
+ inserted at the end of the current module, using
+ @racket[syntax-local-lift-module-end-declaration]. It
+ should therefore be triggered only when the compilation
+ reaches the end of the file, if no other error was raised
+ before.
+
+ This allows as many @racket[remembered-error!] errors as
+ possible to be accumulated; all of these are then shown
+ when the file is fully expanded. The goal is to be able to
+ add all values to the output file in a single run, instead
+ of aborting after each value which is not remembered. This
+ would otherwise require recompiling the program once for
+ each value which is not initially remembered.
+
+ TODO: it would be nice to factor out the delayed error
+ mechanism into a separate package, so that multiple
+ libraries can add errors, and all of them get reported,
+ without one preventing the others from executing. This
+ function would likely keep the same signature, and just
+ delegate to the delayed-error library.}
+
+@defparam[disable-remember-immediate-error disable? boolean? #:value #f]{
+ The @racket[disable-remember-immediate-error] parameter allows code to
+ temporarily prevent @racket[remembered-error!] from lifting a delayed error.
+ This can be useful for example when calling @racket[remembered-error!] from a
+ context where @racket[(syntax-local-lift-context)] is @racket[#false], e.g.
+ outside of the expansion of a macro, but within a @racket[begin-for-syntax]
+ block.
+
+ The error is still put aside, so that if a delayed error was triggered by
+ another call to @racket[remembered-error!], the error will still be included
+ with the other delayed errors. If no delayed error is triggered during
+ macro-expansion, the error that was put aside will be ignored. To prevent
+ this from happening, call @racket[lift-maybe-delayed-errors] within a context
+ where lifts are possible.}
+
+@defproc[(lift-maybe-delayed-errors) void?]{
+ Uses @racket[syntax-local-lift-module-end-declaration] or
+ @racket[syntax-local-lift-expression], depending on the context, to lift an
+ expression which will trigger delayed errors, if any. If no delayed errors
+ have been recorded by @racket[remembered-error!] when the lifted form is
+ executed, then nothing will happen and expansion will proceed.
+
+ Note that when @racket[(syntax-transforming-module-expression?)] returns
+ @racket[#false], @racket[syntax-local-lift-expression] is used. The lifted
+ form is then run as part of the current expansion pass, before the contents of
+ any @racket[let] forms are expanded. This means that calls to
+ @racket[remembered-error!] must not happen within the expansion of nested
+ @racket[let] forms (with respect to the @racket[let] form being expanded (if
+ any) when @racket[lift-maybe-delayed-errors] is called), as they would add
+ delayed errors too late, i.e. after the lifted form got executed.}
+
+@defform[(remember-input-file name)
+ #:grammar ([name string?])]{
+ The file is loaded with @racket[require], but no
+ identifier is imported from that module. Instead,
+ @racket[remembered?] relies on its internal mutable
+ @racket[for-syntax] hash table which stores remembered
+ values associated to their category.
+
+ @racket[remembered-values]. Values are added to the hash
+ via the @racket[remembered!] macro. The @racket[name] file
+ should therefore @racket[require] the
+ @racketmodname[remember] library, and contain a number of
+ calls to @racket[remembered!], each adding a new value to
+ the mutable hash.}
+
+@deftogether[
+ (@defform*[((remember-output-file)
+ (remember-output-file name))
+ #:grammar ([name (or/c string? false?)])]
+ @defproc*[#:kind "for-syntax parameter"
+ #:link-target? #f
+ ([(remember-output-file) (or/c string? false?)]
+ [(remember-output-file [name (or/c string? false?)]) void?])]
+ )]{
+ Indicates that new values added via
+ @racket[remember-write!] should be appended to the file
+ @racket[name]. More precisely, the expression
+ @racket[(remembered! _category _value)] is appended to the
+ file, followed by a newline.
+
+ Note that if the @racket[_value] given to
+ @racket[remember-write!] is already registered in an input
+ file with @racket[remembered!] for the same category, it
+ will not be appended to the output file.
+
+ For now there can only be one @racket[output] file at the
+ same time, any call to @racket[remember-output-file]
+ overrides the setting from previous calls. Future versions
+ of this library may offer the possibility to specify an
+ output file per @racket[_category].
+
+ The special value @racket[#f] indicates that there is no
+ output file, in which case @racket[remember-write!] simply
+ marks the @racket[value] as
+ @racket[remembered-or-written?] for that category, without
+ altering any file.
+
+ This identifier exists both as a macro and a for-syntax
+ parameter. When called without any argument, it expands to
+ (for the macro) or returns (for the for-syntax parameter)
+ the last value set using either the macro or by passing an
+ argument to the for-syntax parameter.}
+
+@defparam[remember-output-file-parameter output-file
+ (or/c path-string? false?)
+ #:value #f]{
+ This for-syntax parameter that new values added via @racket[remember-write!]
+ should be appended to the file whose name is stored within the parameter.
+
+ The @racket[remember-output-file] macro simply sets this parameter.}
+
+@defform[(remember-io-file name)
+ #:grammar ([name string?])]{
+ Indicates that calls to @racket[remembered!] in this file
+ should be taken into account, and that new values added
+ with @racket[remember-write!] should be appended to this
+ file.
+
+ It is equivalent to:
+ @racketblock[(remember-input-file name)
+ (remember-output-file name)]}
+
+@defform[(remembered! category value)
+ #:grammar ([category identifier?])]{
+ Marks the given @racket[value] as remembered in the given
+ @racket[category]. If the same value is remembered twice
+ for the same category, the second occurrence is ignored
+ (i.e. values are stored in a distinct @racket[set] for each
+ category).
+
+ Calls to this macro are usually present in an input file
+ loaded with @racket[remember-input-file] or
+ @racket[remember-io-file], but can also be inserted in the
+ main file or any other file loaded with @racket[require].}
+\ No newline at end of file
diff --git a/test/input-error.rkt b/test/input-error.rkt
@@ -0,0 +1,7 @@
+#lang racket
+(require remember)
+(remembered! foo-error (1 2 3))
+(remembered! foo-error (1 2 3 4))
+(remembered! foo-error (1 2 3 5))
+(define + 'wrong)
+(provide +)
+\ No newline at end of file
diff --git a/test/input1.rkt b/test/input1.rkt
@@ -0,0 +1,5 @@
+#lang racket
+(require remember)
+(remembered! foo (1 2 3))
+(remembered! foo (1 2 3 4))
+(remembered! foo (1 2 3 5))
+\ No newline at end of file
diff --git a/test/input3.rkt b/test/input3.rkt
@@ -0,0 +1,7 @@
+#lang racket
+(require remember)
+(remembered! foo3 (1 2 3))
+(remembered! foo3 (1 2 3 4))
+(remembered! foo3 (1 2 3 5))
+(define + 'wrong)
+(provide +)
+\ No newline at end of file
diff --git a/test/io2.rkt b/test/io2.rkt
@@ -0,0 +1,3 @@
+#lang racket
+(require remember)
+(remembered! bar (1 2 3 xyz))
diff --git a/test/test-error.rkt b/test/test-error.rkt
@@ -0,0 +1,18 @@
+#lang racket
+
+(require remember
+ rackunit)
+(remember-input-file "input-error.rkt")
+(define-syntax (test-rem stx)
+ (syntax-case stx ()
+ [(_ val)
+ (let ([v (syntax-e #'val)])
+ (unless (remembered? 'err-category v)
+ (remembered-error! 'err-category #'val)))
+ #'(void)]))
+
+(test-rem one)
+(test-rem two)
+(check-equal? (+ 1 2) 3)
+(test-rem three)
+(test-rem four)
+\ No newline at end of file
diff --git a/test/test1.rkt b/test/test1.rkt
@@ -0,0 +1,13 @@
+#lang racket
+(require remember
+ rackunit
+ (submod "../remember-implementation.hl.rkt" private))
+(remember-input-file "input1.rkt")
+(begin-for-syntax
+ (require rackunit)
+ (define secs (current-seconds))
+ (remembered-add-written! 'foo `(1 2 3 secs))
+ (check-false (remembered? 'foo `(1 2 3 secs)))
+ (check-true (remembered-or-written? 'foo `(1 2 3 secs))))
+;; check that no identifiers were imported from "input1.rkt".
+(check-not-equal? + 'wrong)
+\ No newline at end of file
diff --git a/test/test2.rkt b/test/test2.rkt
@@ -0,0 +1,9 @@
+#lang racket
+(require remember)
+(remember-io-file "io2.rkt")
+(begin-for-syntax
+ (require rackunit)
+ ;; Manually check for an error the first time this
+ ;; file is compiled after emptying io2.rkt
+ (check-true (remembered? 'bar '(1 2 3 xyz)))
+ (remember-write! 'bar '(1 2 3 xyz)))
+\ No newline at end of file
diff --git a/test/test3.rkt b/test/test3.rkt
@@ -0,0 +1,8 @@
+#lang racket
+(require remember)
+(remember-io-file "input3.rkt")
+(begin-for-syntax
+ (require rackunit
+ racket/set)
+ (check set=? (get-remembered 'foo3)
+ (set '(1 2 3) '(1 2 3 5) '(1 2 3 4))))
+\ No newline at end of file