pauleveritt / fdom

Template engine based on tag strings and functional ideas.
0 stars 0 forks source link

Working with the proof of concept compiler #1

Closed jimbaker closed 1 year ago

jimbaker commented 1 year ago

First, the POC is not yet aligned with current PEP work re Chunk (as a subclass of str) and Thunk. So it basically is the older str and tuple model, which worked fine for my purposes for trying out the ideas of the compiler, at the cost of clarity! (But to be clear, str|tuple is still valid because Chunk is-a str, and Thunk is-a tuple.)

With that in mind, let's try some things out after doing the various installs:

>>> from fdom.compiler import html, make_key
>>> make_key'<{tag}><body baz="fum"><h1 {title_style!r:dict}>Blah {title:str}, foo</h1></body></{tag}>'
('<', (None, None, None, None), '><body baz="fum"><h1 ', (None, None, 'r', 'dict'), '>Blah ', (None, None, None, 'str'), ', foo</h1></body></', (None, None, None, None), '>')

Also when I write code here, I'm going to use Chunk, where I have done the following

Chunk = str

(not right, but raw strings vs strings is just a question of doing that decode, so good enough for now, and I will use interchangeably).

Note that anything that takes *args: str|tuple or *args: Chunk|Thunk is a valid tag function. This is super useful, as we can use it to figure out what is going on with make_key without having to call through a fairly complex chain, as in

def html(*args: Chunk | Thunk):
    return compile_it(*make_key(*args))(vdom, *args)

The key insight, so to speak, is that we only distinguish based on the string chunks and the conv/formatspec, if set at all. The argument here is that any interpolation for getvalue and its text is equivalent in how it can be used in the the template, so we always make those None, None in the key.

Lastly, here's a better version of make_key:

# NOTE treating Chunk and Thunk equivalent to str and tuple is WRONG - but but but it's fine for now
# given Chunk is-a str, and Thunk is-a tuple
# and a raw string is just a string that only requires decoding in *some* cases

Chunk = str  # WRONG!!! but works for our demo purposes

def make_key(*args: Chunk | Thunk) -> tuple[Chunk | tuple, ...]:
    key_args = []
    for arg in args:
        match arg:
            case Chunk():
                key_args.append(arg)
            case Thunk((_, _, conv, formatspec)) as t:
                key_args.append((None, None, conv, formatspec))
    return tuple(key_args)

Note that for case statements, there's no instantation of the class - this is equivalent to saying isinstance(arg, Chunk), etc.

The return value might be a bit unexpected, but hopefully this analysis makes it clearer. So it's

  1. A tuple, because we need something hashable (no list) for using as a key, but it's of a Chunk | tuple. So tuple[Chunk | tuple, ...]
  2. It's not a Chunk | Thunk, because getvalue and text (or whatever we call the expression text) of Thunk cannot be None.

Let's also look at html:

>>> tag = 'h1'
>>> title = 'Some Title'
>>> title_style = {'a': 47, 'b': 'bar'}
>>> html'<{tag}><body baz="fum"><h1 {title_style!r:dict}>Blah {title:str}, foo</h1></body></{tag}>'
def compiled(vdom, /, *args):
  return \
  vdom(
    args[1][0](),
    {},
    [
      vdom('body',
        {},
        [
          vdom('h1',
            args[3][0](),
            [
              'Blah ',
              args[5][0](),
              ', foo',
            ])
        ])
    ])
{'tagName': 'h1', 'children': [{'tagName': 'body', 'children': [{'tagName': 'h1', 'attributes': {'a': 47, 'b': 'bar'}, 'children': ['Blah ', 'Some Title', ', foo']}]}]}

The first time through, it compiles the function, and has the nice side effect that when it compiles, it prints the code. Also we can do this:

>>> from fdom.compiler import compile_it
>>> compile_it.cache_info()
CacheInfo(hits=0, misses=1, maxsize=128, currsize=1)

Second time through, no printing, because it has cached things:

>>> html'<{tag}><body baz="fum"><h1 {title_style!r:dict}>Blah {title:str}, foo</h1></body></{tag}>'
{'tagName': 'h1', 'children': [{'tagName': 'body', 'children': [{'tagName': 'h1', 'attributes': {'a': 47, 'b': 'bar'}, 'children': ['Blah ', 'Some Title', ', foo']}]}]}
>>> compile_it.cache_info()
CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
jimbaker commented 1 year ago

In the latest commit, I added support for PEP compliant implementations of Chunk and Thunk. I also added a KeyThunk named tuple implementation to make it more obvious what was done in this projection (in the SQL sense of that) with only conv and formatspec.

This feels much better than adding so many caveats about types being is-a, etc. Now they are.

jimbaker commented 1 year ago

With the changes above, this issue can be closed.