Skip to content

Commit d1f1b8f

Browse files
committed
Add process-declarations
Some small other fixes
1 parent 64bfcde commit d1f1b8f

7 files changed

+409
-17
lines changed

README.md

+134-14
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ This repo contains a collection of small Common Lisp hacks I've written over the
7373
- [Metatronic macros](#metatronic-macros)
7474
- [Notes](#notes-5)
7575
- [Writing more complicated macros](#writing-more-complicated-macros)
76-
- [Package, module](#package-module-13)
76+
- [Package, module, dependencies](#package-module-dependencies-4)
7777
- [Simple logging: slog](#simple-logging-slog)
7878
- [Almost a simple example](#almost-a-simple-example)
7979
- [Log entries](#log-entries)
@@ -86,11 +86,17 @@ This repo contains a collection of small Common Lisp hacks I've written over the
8686
- [Precision time](#precision-time)
8787
- [An example: rotating log files](#an-example-rotating-log-files)
8888
- [Notes](#notes-6)
89-
- [Package, module](#package-module-14)
89+
- [Package, module, dependencies](#package-module-dependencies-5)
9090
- [Binding multiple values: let-values](#binding-multiple-values-let-values)
91-
- [Package, module](#package-module-15)
92-
- [Utilities](#utilities)
93-
- [Package, module](#package-module-16)
91+
- [Package, module, dependencies](#package-module-dependencies-6)
92+
- [Processing declaration specifiers: process-declarations](#processing-declaration-specifiers-process-declarations)
93+
- [Terminology](#terminology)
94+
- [What you can do](#what-you-can-do)
95+
- [The interface](#the-interface-3)
96+
- [Examples](#examples-1)
97+
- [Package, module, dependencies](#package-module-dependencies-7)
98+
- [Small utilities: utilities](#small-utilities-utilities)
99+
- [Package, module](#package-module-13)
94100

95101
## General
96102
### Modules
@@ -2098,8 +2104,8 @@ for instance. Here `values` is just suppressing the other values from `metatron
20982104

20992105
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.
21002106

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.
21032109

21042110
## Simple logging: `slog`
21052111
`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.
25342540

25352541
Logging to pathnames rather than explicitly-managed streams may be a little slower, but seems now to be pretty close.
25362542

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.
25392545

25402546
## Binding multiple values: `let-values`
25412547
`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
25802586

25812587
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.
25822588

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.
2591+
2592+
## Processing declaration specifiers: `process-declarations`
2593+
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`.
25852630

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.
2659+
2660+
```lisp
2661+
(defun type-declarations-for-varmap (decls varmap environment)
2662+
;; DECLS is the declarations, VARMAP the alist from (var .
2663+
;; secret-var) and ENVIRONMENT the macro environment or NIL
2664+
`(declare
2665+
,@(collecting
2666+
(dolist (decl decls)
2667+
(dolist (specifier (rest decl))
2668+
(process-declaration-specifier
2669+
specifier
2670+
(lambda (identifier constructor &key variable-names &allow-other-keys)
2671+
(case identifier
2672+
(type ;only care about these
2673+
(let ((mapped-variables
2674+
(collecting
2675+
(dolist (v variable-names)
2676+
(let ((found (assoc v varmap)))
2677+
;; only care if there is a mapped variable
2678+
(when found (collect (cdr found))))))))
2679+
(collect (funcall constructor :variable-names mapped-variables))))))
2680+
:environment environment))))))
2681+
```
2682+
2683+
And now
2684+
2685+
```lisp
2686+
> (type-declarations-for-varmap
2687+
'((declare (optimize speed))
2688+
(declare (ignorable a b)
2689+
(fixnum a)
2690+
(type float b c)))
2691+
'((a . secret-a)
2692+
(b . secret-b))
2693+
nil)
2694+
(declare (type fixnum secret-a) (type float secret-b))
2695+
```
2696+
2697+
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`
25872703
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.
25882704

25892705
Here is what it currently provides.
@@ -2593,7 +2709,7 @@ Here is what it currently provides.
25932709
- `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"`.
25942710
- `thunk` makes anonymous functions with no arguments: `(thunk ...)` is `(lambda () ...)`.
25952711
- `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").
25972713
- `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.
25982714

25992715
### Package, module
@@ -2647,4 +2763,8 @@ The TFEB.ORG Lisp hax are copyright 1989-2024 Tim Bradshaw. See `LICENSE` for t
26472763

26482764
[^21]: Well: you could write your own `handler-bind` / `handler-case` forms, but don't do that.
26492765

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.

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
9.0.0
1+
10.0.0

org.tfeb.hax.asd

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@
7272
(:file "test-slog-run"
7373
:depends-on ("test-slog-setup"
7474
"test-slog-blackbox"
75-
"test-slog-whitebox"))))
75+
"test-slog-whitebox"))
76+
(:file "test-let-values")))

org.tfeb.hax.let-values.asd

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
("org.tfeb.hax.spam"
1919
"org.tfeb.hax.collecting"
2020
"org.tfeb.hax.iterate"
21-
"org.tfeb.hsx.utilities")
21+
"org.tfeb.hax.utilities")
2222
:components
2323
((:file "let-values")))

org.tfeb.hax.process-declarations.asd

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
;;;; Module org.tfeb.hax.process-declarations of org.tfeb.hax
2+
;;;
3+
4+
(in-package :asdf-user)
5+
6+
(defsystem "org.tfeb.hax.process-declarations"
7+
:description
8+
"A subsystem of the TFEB hax"
9+
:version
10+
(:read-file-line "VERSION")
11+
:author
12+
"Tim Bradshaw"
13+
:license
14+
"MIT"
15+
:homepage
16+
"https://github.com/tfeb/tfeb-lisp-hax"
17+
:depends-on
18+
("org.tfeb.hax.utilities")
19+
:components
20+
((:file "process-declarations")))

0 commit comments

Comments
 (0)