gilch / hissp

It's Python with a Lissp.
https://gitter.im/hissp-lang/community
Apache License 2.0
364 stars 9 forks source link

Custom `_macro_` types #254

Closed gilch closed 7 months ago

gilch commented 7 months ago

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 the foo. module's _macro_ rather than the local one.

It's a bit tricky because foo.bar.baz is different from (getattr foo 'bar.baz).

gilch commented 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.
gilch commented 7 months ago

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.

gilch commented 7 months ago

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.