gilch / hebigo

蛇語(HEH-bee-go): An indentation-based skin for Hissp.
https://github.com/gilch/hissp
Mozilla Public License 2.0
24 stars 3 forks source link

Consider allowing hotword expressions nested in bracketed expressions #59

Open gilch opened 2 years ago

gilch commented 2 years ago

While parsing a bracketed expression, we could recursively switch back to the base Hebigo parser when encountering a macro character that Python doesn't use, like !, until we finish the next Hebigo expression, then place the result of compiling that back into the Python string we're building. This would effectively be a builtin reader macro. For example,

def: fibonacci: n
  :@ functools..lru_cache: None  # Qualified identifier in decorator.
  if: (n <= 1)
    :then: n
    :else: (!fibonacci:(n - 1) + !fibonacci:(n - 2))

Maybe a bad example, since Python's expression syntax can handle this part fine.

    :else: (fibonacci(n - 1) + fibonacci(n - 2))

But suppose we needed a macro.

(!macro:spam + !macro:eggs)

We'd currently have to do something like

!let: :,: s e
  :be hebi.bootstrap..entuple: macro:spam macro:eggs
  (s + e)

or

operator..add: macro:spam macro:eggs

On the other hand, we might want to encourage using hotword expressions instead of Python expressions, because it's much easier to write macros to work with those.

gilch commented 2 years ago

This actually seems a bit more difficult than I first thought. We're relying on Python's parser in bracketed expressions, but the macro character ! probably shouldn't be valid inside of a string literal. We can probably recognize literal strings with the lexing regex though. Hmm. It does still seem doable.

gilch commented 2 years ago

F-strings though. We'd want to be able to turn the macro back on in those.

gilch commented 2 years ago

As convenient as this would be, it kind of doesn't seem worth it.

The thing I miss the most in bracketed expressions so far is the module literals. It would be nice if we could at least preprocess those, but it seems about as difficult to do as the full expression macros. One can certainly write __import__ calls by hand, but that's so much longer it doesn't seem worth it compared to using !let.

Python is capable of abbreviating this kind of thing.

class Importer:
    def __init__(self, module_path=()):
        self.mpath = module_path
    def __getattribute__(self, attr):
        return Importer(object.__getattribute__(self,'mpath') + (attr,))
    def __call__(self):
        return import_module('.'.join(object.__getattribute__(self,'mpath')))
>>> M = Importer()
>>> M.collections.abc().Iterable
<class 'collections.abc.Iterable'>

That's almost as good. You need a prefix like the M., and (). instead of ... There's also slightly more runtime overhead, which could add up.

gilch commented 2 years ago

Given f-strings, ! isn't a good macro character choice, because it does have meaning there. ; might be the best option. I'm pretty sure it's not in format specifiers. Normally, it would end a statement, so it can't be in an expression either, except in string literals, which would have to be dealt with somehow anyway.

gilch commented 2 years ago

A macro like this wouldn't be too hard to write:

!where: (spam+eggs) spam macro:spam  eggs macro:eggs

It feels more compact than the !let anyway.

It could also be done positionally with an anaphor.

!where: (X[0]+X[1]) macro:spam macro:eggs

Or both at once.

This seems like the 80% solution. Getting the parser to handle this properly seems a lot more difficult.