What Lisp does with source code
Common Lisp is an incredibly flexible language, in part because its source code can be easily represented using the same data structures that are used in the language. The most common form of macro expansion transforms the these structures into other structures. These are the kind of macros that you can define with define-symbol-macro
, define-compiler-macro
, defmacro
, and macrolet
. Before any of those kind of macroexpansions can be performed, however, the system first needs to read the source from an input stream (typically a file, or an interactive prompt). That's the reader's responsibility. The reader also is capable of executing some special actions when it encounters certain characters, such (
and '
. What you're trying to do probably needs to be happening down at the reader level, if you want to have, e.g., (read-from-string "a + 1 1 b")
return the list (+ 1 1)
, which is what you want if you want (eval (read-from-string "a + 1 1 b"))
to return 2
. That said, you could also define a special custom language (like loop
does) where a
and b
are treated specially.
Use set-macro-character
, not define-symbol-macro
This isn't something that you would do using symbol-macros, but rather with macro characters. You can set macro characters using the aptly named set-macro-character
. For instance, in the following, I set the macro character for %
to be a function that reads a list, using read-delimited-list
that should be terminated by ^
. (Using the characters a
and b
here will prove very difficult, because you won't be able to write things like (set-macro-character ...)
afterwards; it would be like writing (set-m(cro-ch(r(cter ...)
, which is not good.)
CL-USER> (set-macro-character #\% (lambda (stream ignore)
(declare (ignore ignore))
(read-delimited-list #^ stream)))
T
CL-USER> % + 1 1 ^
2
The related set-syntax-from-char
There's a related function that almost does what you want here, set-syntax-from-char
. You can use it to make one character behave like another. For instance, you can make %
behave like (
CL-USER> (set-syntax-from-char #\% #()
T
CL-USER> % + 1 1 )
2
However, since the macro character associated with (
isn't looking for a character that has the same syntax as )
, but an actual )
character, you can't simply replace )
with ^
in the same way:
CL-USER> (set-syntax-from-char #^ #))
T
CL-USER> % + 1 1 ^
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "unmatched close parenthesis" {1002C66031}>.
set-syntax-from-char
is more useful when there's an existing character that, by itself does something that you want to imitate. For instance, if you wanted to make !
an additional quotation character:
CL-USER> (set-syntax-from-char #! #')
T
CL-USER> (list !a !(1 2 3))
(A (1 2 3))
or make %
be a comment character, like it is in LaTeX:
CL-USER> (set-syntax-from-char #\% #;)
T
CL-USER> (list 1 2 % 3 4
5 6)
(1 2 5 6)
But consider why you're doing this at all…
Now, even though you can do all of this, it seems like something that would be utterly surprising to anyone who ran into it. (Perhaps you're entering an obfuscated coding competition? ;)) For the reasons shown above, doing this with commonly used characters such as a
and b
will also make it very difficult to write any more source code. It's probably a better bet to define an entirely new readtable that does what you want, or even write a new parser. even though (Common) Lisp lets you redefine the language, there are still things that it probably makes sense to leave?alone.