Closed gilch closed 7 months ago
Here's an example of turning the current module into a Lisp-2.
(hissp.._macro_.prelude)
(alias bn builtins.)
(define #' my#my) ; The function namespace.
(defmacro defun (name params : :* body)
"Defines a function in the function namespace"
`(setattr #' ',name (lambda ,params ,@body)))
(deftype _Macro ()
__init__ (lambda (self) (.update (vars self) (vars _macro_))) ; Keep prelude macros.
__getattr__ (lambda (self name)
(if-else (op#contains name ".")
(throw (bn#AttributeError name)) ; Macro not found, do normal behavior.
(lambda (: :* args)
`(,(.format "{}.{}" '#' name) ,@args)))))
(define _macro_ (_Macro))
(define __builtins__ (% '__import__ __import__)) ; Removes builtins, except __import__.
(bn#print "Hi!") ; builtins still available via alias.
(defun print x (bn#print "print:" x)) ; Lives in function namespace.
(define print 42) ; Just a global.
(print print) ; No conflict in a Lisp-2!
(print #'.print) ; You can still get the function object.
Note that this kind of override would only apply to invocations of symbols. Tuple expressions that resolve to callables wouldn't be affected. Fully qualified identifiers mostly wouldn't be affected either, but direct use of an affected _macro_
object would be.
Other uses might be to change how call syntax works. You could change the default active control words : :? :* :**
to something else, for example, add new ones, or change how those work. Macros mostly expand to fully-qualified invocations or special forms, so they'd mostly still be usable. Any expansion that invokes a gensym local might break though. Hmm.
This makes the compiler a little more consistent with the reader. Using the
_macro_
object's__getattr__
as a hook for overriding invocations (for example, making the module a Lisp-2) was a use case I intended at least since version 0.2.0 as mentioned in the old FAQ.When I actually tried doing it, I noticed that the compiler would require a small tweak. That's this change.
There are some subtleties around how dots should be handled. I might need more experience with this version before I can say if further changes are warranted. For use cases I'm imagining, I think local invocations with single dots like
(self.foo)
should be overridable, but external invocations with double dots like(builtins..print)
should not, although(foo.._macro_.bar)
should probably be overridable via thefoo.
module's_macro_
rather than the local one.It's a bit tricky because
foo.bar.baz
is different from(getattr foo 'bar.baz)
.