You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -2098,8 +2104,8 @@ for instance. Here `values` is just suppressing the other values from `metatron
2098
2104
2099
2105
However in general metatronic macros are far more useful for simple macros where there is no complicated expander function like this: that's what it was intended for.
2100
2106
2101
-
### Package, module
2102
-
`metatronic` lives in and provides `:org.tfeb.hax.metatronic`.
2107
+
### Package, module, dependencies
2108
+
`metatronic` lives in and provides `:org.tfeb.hax.metatronic`. It requires `utilities` and will attempt to load it if `require-module` is present.
2103
2109
2104
2110
## Simple logging: `slog`
2105
2111
`slog` is based on an two observations about the Common Lisp condition system:
@@ -2534,8 +2540,8 @@ I'm not completely convinced by the precision time code.
2534
2540
2535
2541
Logging to pathnames rather than explicitly-managed streams may be a little slower, but seems now to be pretty close.
2536
2542
2537
-
### Package, module
2538
-
`slog` lives in and provides `:org.tfeb.hax.slog`.
2543
+
### Package, module, dependencies
2544
+
`slog` lives in and provides `:org.tfeb.hax.slog`. It requires `utilities`, `collecting`, `spam` and `metatronic`and will attempt to load them if `require-module` is present.
2539
2545
2540
2546
## Binding multiple values: `let-values`
2541
2547
`let-values` provides four forms which let you bind multiple values in a way similar to `let`: you can bind several sets of them in one form. The main benefit of this is saving indentation.
@@ -2580,10 +2586,120 @@ In the `let*`-style cases the declarations will apply to all duplicate variables
2580
2586
2581
2587
Now, without knowing what `f` does, it could refer to the dynamic binding of `a`. So the special declaration for `a` needs to be made for the temporary binding as well, unless it is in the final group of bindings. The starred forms now do this.
2582
2588
2583
-
### Package, module
2584
-
`let-values` lives in and provides `:org.tfeb.hax.let-values`.
2589
+
### Package, module, dependencies
2590
+
`let-values` lives in and provides `:org.tfeb.hax.let-values`. It requires `spam`, `collecting`, `iterate` and `utilities`, and will attempt to load them if `require-module` is present.
When writing macros it's useful to be able to process declaration specifiers in a standardised way. In particular it's common to want to select all specifiers which mention a variable and perhaps create equivalent ones which refer to some new variable introduced by the macro.
2594
+
2595
+
Things in this system are *probably not stable*. I want to publish it so other things can rely on it, but its details may change.
2596
+
2597
+
### Terminology
2598
+
A `declare` expression[^22] is of the form `(declare <declaration-specifier> ...)`, where a `<declaration-specifier>` is of the form `(<declaration-identifier> ...)`. A `<declaration-identifier>` is a symbol, which may be the name of a type[^23]: `(<type> ...)` is a shorthand for the canonical `(type <type> ...)`. There are a number of standard identifiers, and nonstandard ones can be portably declared as valid identifiers. This terminology is the same as that used in the standard.
2599
+
2600
+
### What you can do
2601
+
`process-declaration-specifier` lets you call a function on a processed declaration specifier, with the function being handed information which tells it the identifier (canonicalised in the case of type declarations), which variable and function names it applies to if any, and a function which will construct a similar specifier for possibly-different sets of variable or function names or other information. Other information can be handed to the function.
2602
+
2603
+
Methods on `process-declaration-identifier` allow you to define handlers for declaration identifiers which describe how the function should be called. Suitable methods exist for all standard declaration identifiers, so for normal usage you will never need to write methods on this generic function.
2604
+
2605
+
### The interface
2606
+
**`process-declaration-specifier`** calls a function on a processed declaration specifier. It takes two positional arguments and any number of keyword arguments. Positional arguments are:
2607
+
2608
+
- the declaration specifier
2609
+
- the function to call on the processed specifier
2610
+
2611
+
The only keyword argument used by the function itself is `environment`, which specifies an optional environment object. This can be used to infer things about types at compile time. All keyword arguments, including `environment` are passed to the function being called.
2612
+
2613
+
The given function is called with two positional arguments and a number of keyword arguments, including all those given to `process-declaration-specifier` itself. Positional arguments are
2614
+
2615
+
- the canonical declaration identifier, which for a declaration specifier like `(fixnum ...)` will be `type`.
2616
+
- a function which will construct a suitable declaration specifier of the same kind as the one being processed.
2617
+
2618
+
All other arguments to the given function are keyword arguments and they will always include
2619
+
2620
+
-`variable-names`, a list of variable names for this specifier, if any;
2621
+
-`function-names`, a list of function names for this specifier, if any;
2622
+
-`specifier`, the canonical version of the whole specifier;
2623
+
-`original-specifier`, the original specifier, which may differ for shorthand type specifiers;
2624
+
-`specifier-body`, the cdr of `specifier`;
2625
+
-`environment`, the environment, or `nil`
2626
+
2627
+
In addition any keyword arguments passed to `process-declaration-specifier` are passed to the function.
2628
+
2629
+
The above keyword arguments are all the arguments passed to functions for standard declaration identifiers. Non-standard ones may pass additional arguments, based on methods on `process-declaration-identifier`.
2585
2630
2586
-
## Utilities
2631
+
The second argument to the function is itself a function, whose job is to construct a declaration specifier of the same kind as the one being processed, but usually for other arguments. This function again takes zero or more keyword arguments: which arguments it accepts depend on the declaration identifier. For the standard identifiers here are the arguments.
2632
+
2633
+
-`type`: `variable-names`, a list of variable names.
2634
+
-`ftype`: `function-names`, a list of function names.
2635
+
-`special` and `dynamic-extent`: `variable-names`, a list of variable names.
2636
+
-`inline` and `notinline`: `function-names`, a list of function names.
2637
+
-`ignore` and `ignorable`: both `variable-names` and `function-names` are allowed.
2638
+
- All other standard declaration identifiers have constructor functions which take no arguments and simply return the canonical specifier.
2639
+
2640
+
Constructor functions are fussy about their arguments: they *don't* allow unknown keyword arguments. This means you get an error rather than silently get the wrong anser.
2641
+
2642
+
`process-declaration-specifier` returns what the function called returns.
2643
+
2644
+
**`process-declaration-identifier`** is a generic function used to implement `process-declaration-specifier` for specific declaration identifiers. *Unless you want to support non-standard identifiers, you do not need to implement methods on this function*. You should not implement methods for any standard identifiers, or a fallback method, as these already exist.
2645
+
2646
+
The generic function is called with two positional arguments and any number of keyword arguments (the GF itself has `&allow-other-keys` so methods don't need to say this themselves). The positional arguments are:
2647
+
2648
+
- the declaration identifier, which shoul be used for dispatch;
2649
+
- the function handed to `process-declaration-specifier`.
2650
+
2651
+
Keyword arguments include all those specified above, as well as any user-provided arguments to `process-declaration-specifier`. Note that the values of all the keyword arguments are the default ones, so for instance `variable-names` will be `()`: methods are responsible for providing non-default values where needed. Note that CL's keyword-argument parsing, which allows repeated keywords and takes the value of the leftmost one, makes this easy.
2652
+
2653
+
Methods on the generic function are responsible for arranging for the user function to be called in the right way, and in particular with an appropriate constructor function.
2654
+
2655
+
Methods should return the values of the user function.
2656
+
2657
+
### Examples
2658
+
All the above sounds pretty obscure, but it's much easier to use than it seems. Here is a typical case: I want to create equivalent `type` declarations for 'hidden' variables corresponding to a number of variables in a macro. Here is a function which will do this, using `collecting` for result accumulation.
It is generally never necessary to write methods on `process-declaration-identifier`: if you need to you should look at the source to see what they look like.
2698
+
2699
+
### Package, module, dependencies
2700
+
`process-declarations` lives in and provides `:org.tfeb.hax.process-declarations`. It needs `utilities` and will attempt to load it if `require-module` is present.
2701
+
2702
+
## Small utilities: `utilities`
2587
2703
This is used both by other hax and by other code I've written. Things in this system *may not be stable*: it should be considered mostly-internal. However, changes to it *are* reflected in the version number of things, since other systems can depend on things in it.
2588
2704
2589
2705
Here is what it currently provides.
@@ -2593,7 +2709,7 @@ Here is what it currently provides.
2593
2709
-`with-names` binds variables to uninterned symbols with the same name by default: `(with-names (<foo>) ...)`will bind `<foo>` to a fresh uninterned symbol with name `"<FOO>"`. `(with-names ((<foo> foo)) ...)` will bind `<foo>` to a fresh uninterned symbol with name `"FOO"`.
2594
2710
-`thunk` makes anonymous functions with no arguments: `(thunk ...)` is `(lambda () ...)`.
2595
2711
-`thunk*` makes anonymous functions which take an arbitrary number of arguments and ignore them all.
2596
-
-`valid-type-specifier-p` attempts to answer the question 'is something a valid type specifier?'. It does this, normally[^22], by checking that `(typep nil <thing>)` does not signal an error, which I think is a loophole-free way of answering this question (SBCL, again, says incorrectly that`(subtypep <thing> t)`is true for any `<thing>` which looks even slightly like a type specifier). There is an optional second argument which is an environment object handed to `typep`: using this lets it answer the question for the compilation environment: see [this CLHS issue](https://www.lispworks.com/documentation/HyperSpec/Issues/iss334.htm"Issue `SUBTYPEP-ENVIRONMENT:ADD-ARG` Summary").
2712
+
-`valid-type-specifier-p` attempts to answer the question 'is something a valid type specifier?'. It does this, normally[^24], by checking that `(typep nil <thing>)` does not signal an error, which I think is a loophole-free way of answering this question (SBCL, again, says incorrectly that`(subtypep <thing> t)`is true for any `<thing>` which looks even slightly like a type specifier). There is an optional second argument which is an environment object handed to `typep`: using this lets it answer the question for the compilation environment: see [this CLHS issue](https://www.lispworks.com/documentation/HyperSpec/Issues/iss334.htm"Issue `SUBTYPEP-ENVIRONMENT:ADD-ARG` Summary").
2597
2713
-`canonicalize-declaration-specifier` attempts to turn the shorthand `(<type> ...)` declaration specifier into a canonical `(type <type> ...)`. It does this using `valid-type-specifier-p`. Its optional second argument is an environent object passed to `valid-type-specifier-p`. The spec says that a declaration identifier is 'one of the symbols [...]; *or a symbol which is the name of a type*' [my emphasis. This means that a declaration specifier like `((integer 0) ...)` is not legal. Several implementations accept these however, so this function blindly turns such things into type declaration specifiers. It returns a second value which will be false for one of these, true otherwise.
2598
2714
2599
2715
### Package, module
@@ -2647,4 +2763,8 @@ The TFEB.ORG Lisp hax are copyright 1989-2024 Tim Bradshaw. See `LICENSE` for t
2647
2763
2648
2764
[^21]: Well: you could write your own `handler-bind` / `handler-case` forms, but don't do that.
2649
2765
2650
-
[^22]: SBCL has its own version of this function, so that's used for SBCL.
2766
+
[^22]: All of this applies to `proclaim` and `declaim` as well.
2767
+
2768
+
[^23]: Some implementations allow non-atomic type specifiers: this is not strictly conforming but supported by this code.
2769
+
2770
+
[^24]: SBCL has its own version of this function, so that's used for SBCL.
0 commit comments