Remember:   storage for macros which is persistant across compilations
get-remembered
remembered-add!
remembered?
remembered-or-written?
remember-write!
remembered-error!
disable-remember-immediate-error
lift-maybe-delayed-errors
remember-input-file
remember-output-file
remember-output-file-parameter
remember-io-file
remembered!
7.0

Remember: storage for macros which is persistant across compilations

Georges Dupéron

 (require remember) package: remember

This library is implemented using literate programming. The implementation details are presented in Implementation of Remember.

This module allows macros to remember some values across compilations. Values are grouped by category, so that multiple macros can use this facility without interfering with each other. The category is simply a symbol given when remembering the value.

The list of all remembered values for a given category is returned by get-remembered, and it is possible to check if a single value has been remembered using remembered?.

Values are loaded from files using remember-input-file and remember-io-file. An output file can be set with remember-output-file and remember-io-file.

When an output file has been declared, new values passed to remember-write! are marked as remembered-or-written? and appended to that file (more precisely, the expression (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 (remembered! category value) statements:

#lang racket
(require remember)

The remembered! macro indicates an already-remembered value, and is typically used inside input files. The for-syntax function remembered-add! can also be used instead, to mark a value as remembered? without adding it to any file (this can be useful for values which should implicitly be remembered).

for-syntax procedure

(get-remembered category)  list?

  category : symbol?
Returns a list of all values that have been remembered for the given category (i.e. all values passed as the second argument to remembered-add!, remember-write! or remembered!, with the given category as the first argument).

for-syntax procedure

(remembered-add! category value)  void?

  category : symbol?
  value : any/c
Marks the given value as remembered in the given 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 set for each category).

This for-syntax procedure is called by the remembered! macro, but can also be executed on its own.

for-syntax procedure

(remembered? category value)  boolean?

  category : symbol?
  value : any/c
Checks whether the given value has already been added to the set of remembered values for the given category.

for-syntax procedure

(remembered-or-written? category value)  boolean?

  category : symbol?
  value : any/c
Checks whether the given value has already been added to the set of remembered values for the given category, or if it was freshly written to a file during the current expansion.

for-syntax procedure

(remember-write! category value)  void?

  category : symbol?
  value : any/c
Adds the given value to the current remember-output-file for the given category. More precisely, the expression (remembered! category value) is appended to the file, followed by a newline.

If the value is already remembered-or-written?, then the file is left unchanged, i.e. two or more calls to remember-write! with the same category and value will only append an expression to the file the first time.

The value is also added to the set of remembered-or-written? values, so that subsequent calls to remembered-or-written? return #t for that category and value. Calls to remembered? will be unaffected, and will still return #f. If some declarations are created by a library based on the get-remembered set, it is therefore possible to check whether a value was already present, or if it was added by a subsequent remember-write!.

for-syntax procedure

(remembered-error! category stx-value)  void?

  category : symbol
  stx-value : syntax?
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 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 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.

parameter

(disable-remember-immediate-error)  boolean?

(disable-remember-immediate-error disable?)  void?
  disable? : boolean?
 = #f
The disable-remember-immediate-error parameter allows code to temporarily prevent remembered-error! from lifting a delayed error. This can be useful for example when calling remembered-error! from a context where (syntax-local-lift-context) is #false, e.g. outside of the expansion of a macro, but within a begin-for-syntax block.

The error is still put aside, so that if a delayed error was triggered by another call to 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 lift-maybe-delayed-errors within a context where lifts are possible.

Uses syntax-local-lift-module-end-declaration or 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 remembered-error! when the lifted form is executed, then nothing will happen and expansion will proceed.

Note that when (syntax-transforming-module-expression?) returns #false, syntax-local-lift-expression is used. The lifted form is then run as part of the current expansion pass, before the contents of any let forms are expanded. This means that calls to remembered-error! must not happen within the expansion of nested let forms (with respect to the let form being expanded (if any) when lift-maybe-delayed-errors is called), as they would add delayed errors too late, i.e. after the lifted form got executed.

syntax

(remember-input-file name)

 
name = string?
The file is loaded with require, but no identifier is imported from that module. Instead, remembered? relies on its internal mutable for-syntax hash table which stores remembered values associated to their category.

remembered-values. Values are added to the hash via the remembered! macro. The name file should therefore require the remember library, and contain a number of calls to remembered!, each adding a new value to the mutable hash.

syntax

(remember-output-file)

(remember-output-file name)
 
name = (or/c string? false?)

for-syntax parameter

(remember-output-file)  (or/c string? false?)

(remember-output-file name)  void?
  name : (or/c string? false?)
Indicates that new values added via remember-write! should be appended to the file name. More precisely, the expression (remembered! category value) is appended to the file, followed by a newline.

Note that if the value given to remember-write! is already registered in an input file with remembered! for the same category, it will not be appended to the output file.

For now there can only be one output file at the same time, any call to remember-output-file overrides the setting from previous calls. Future versions of this library may offer the possibility to specify an output file per category.

The special value #f indicates that there is no output file, in which case remember-write! simply marks the value as 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.

parameter

(remember-output-file-parameter)  (or/c path-string? false?)

(remember-output-file-parameter output-file)  void?
  output-file : (or/c path-string? false?)
 = #f
This for-syntax parameter that new values added via remember-write! should be appended to the file whose name is stored within the parameter.

The remember-output-file macro simply sets this parameter.

syntax

(remember-io-file name)

 
name = string?
Indicates that calls to remembered! in this file should be taken into account, and that new values added with remember-write! should be appended to this file.

It is equivalent to:

syntax

(remembered! category value)

 
category = identifier?
Marks the given value as remembered in the given 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 set for each category).

Calls to this macro are usually present in an input file loaded with remember-input-file or remember-io-file, but can also be inserted in the main file or any other file loaded with require.