Reader Macros in Common Lisp (2014)
lisper.in
lisper.in
My goal is to create the barest-metal Lisp OS (basically Lisp REPL with full ring-0 access, the OS) with an asm reader macro to write low level assembly opcodes you can jmp into.
(defn add1 (x)
(declare x int64)
#asm(
mov rax, %x
add rax, 1
))
(Just thought of this syntax on the spot, I'm a few dozen hours away from that still.)Does it desugar to something like this?
(assemble ‘((mov rax %x) (add rax 1)))
Anyhow I think this is a great project. I hope you share it! I’ve often thought bootstrapping an assembler with lisp macros would be an interesting way to build a compiler from the very ground up. Assembler macros are nothing new, but I’ve never heard of an homoiconic implementation.Got more details/links to study?
Keyword is the line
(dos-eval '(debug < TO.DEB > NUL))
("mapc" and "map" had their parameters in wrong order, which annoys me even today)The goal is being able to replace every single part of the runtime/interpreter if you wish, but the OS itself starts with the fewest functions/features possible. cons, lambda, and the other usual suspects, a way of running actual native machine code, the homoiconicity of LISP, a runtime-editable image, packed into a UEFI binary.
You could build yourself a Scheme or a Common Lisp on top of it if you wish.
No offense, but if it were Common Lisp I would take a look, but I don't have much interest in learning a one off language.
Sounds like you're having so fun, though, so good luck!
We are also working out interfaces to make it easier to programatically work on our typed IR through a technique and set of interfaces known as "abstract interpretation".
Are you saying that I don't need to use parenthesis as much in lisp? Instead I could define a few "reader macros" and then immediately have a different syntax?
Say if I wanted to change
(if (test-clause) (action1)
(action2))
To if (test-clause) do (action1)
else (action2)
Is this actually possible inline in lisp, meaning I just need to import a library that implements the if-reader-macro?https://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/...
It has test cases at the bottom. like checking that f(a)*=g(b) expands to (setf (f a) (* (f a) (g b))))
The module installs the syntax under the #i dispatch characters; you wrap the infix expression with #I( ... )
Or is Gödel rolling in his grave for such insolent heresy?
(get-macro-character #\() => SB-IMPL::READ-LIST
(get-macro-character #\)) => SB-IMPL::READ-RIGHT-PAREN (that one just signals an error, the read-list one picks up the closing #\))
If you redefine the #\) reader macro, you could use it in a top-level context. That’s probably not a good idea since it’s easy to accidentally have too many #\) when closing a deeply nested expression and thus accidentally invoke the macro.
Aside from signaling an error, the #\) reader macro is also useful because it changes the rules when reading symbols. Basically, if you write (+ foo bar), the existence of the #\) macro helps the reader know that you’re referencing the “bar” symbol rather than the “bar)” symbol.
Generally, when people define new balanced-pair syntax for Common Lisp, (such as a #\{ macro for hash tables) they will follow the same pattern and define a corresponding reader macro for the closing side that always signals an error for all the same reasons.
Edit: also, as others have pointed out, you seem to be mistakenly assuming that the redefinition takes affect mid-way through reading the expression. That’s not how CL works. CL cleanly separates the process of executing code into a few distinct phases. First, the reader reads an entire expression (“form” in lisp terms). Then, that form is macroexpanded (traditional macros, not reader macros!) as needed before (optionally) being compiled and then executed.
The change to the read table would happen during the execution phase — well ordered after the original characters for that form are out of the picture.
You COULD force a change to the readtable mid-way through reading a form using the #. reader macro, but that definitely gets into chainsaw-juggling territory.
Anyways already in 1982 I was diabolically opposed to this kind of shit. Read and Print should be as simple as possible and always one-to-one. When you print something to disk, that is what you get when reading, no additional adjustment needed.
If you want reading macros, for example, you make your own Read. Better Read could even be in standard package "Additional-macros-for-common-lisp".
It makes little sense to reimplement (and maintain over time) the whole Read if you only care about changing some small aspect of it. Instead, you can consider standard Read mechanism from CL to be extensible - reader macros are plugins/hooks/customization points/whatever you want to call it. You can maintain 1:1 Read/Print compatibility easily by defining/overriding a matching printer method, which is the Print side plugin/hook/customization point/whatever.
The only thing that could be simpler than this would be some magic that lets you automatically derive a read macro and a printing method from a simple declaration, but for that you'd have to sacrifice Turing completeness.
In my experience, reader macros are just freaking people out for unconscious and irrational reasons. I can tell because they still freak me out a little, even though I'm conceptually fine with them.
This is wrong. See http://www.lispworks.com/documentation/lw50/CLHS/Body/02_ad....
http://www.lispworks.com/documentation/lw50/CLHS/Body/02_add...
That is the system I was alluding to. It’s been a few years since I’ve done anything with CL and so it seems my memory was slightly off.
CL-USER> (set-macro-character #\) (get-macro-character #\())
T
CL-USER> )format t "Hello")
Hello
NIL