robotpy/cxxheaderparser

Add github pages site with in-browser renderer

virtuald opened this issue · 5 comments

Would be a neat stunt, maybe using https://brython.info?

I took a crack at this tonight, it works fine except that the dataclasses formatting is basically unreadable without using black.

<html>
<head>
    <meta charset="utf-8">
    <script type="text/javascript"
        src="https://cdn.jsdelivr.net/npm/brython@3.10/brython.min.js">
    </script>
    <script type="text/javascript"
        src="https://cdn.jsdelivr.net/npm/brython@3.10/brython_stdlib.js">
    </script>
    <style>
        .row {
          display: flex;
        }

        .column {
          flex: 50%;
        }
    </style>
</head>

<body onload="brython()">

<div class="row">
    <div class="column">
        C++ code<br/>
        <textarea id="cxxinput" rows="40" cols="80">
// comments are ignored
std::vector&lt;int&gt; x;
        </textarea>
    </div>
    <div class="column">
        cxxheaderparser output<br/>
        <textarea id="cxxoutput" rows="40" cols="80" readonly="true"></textarea>
    </div>
</div>

<script type="text/python">
from cxxheaderparser.simple import parse_string
from browser import bind, document, console

cxxinput = document["cxxinput"]
cxxoutput = document["cxxoutput"]

@bind("#cxxinput", "input")
def on_input(evt):
    # TODO: probably should only run this every 200ms or so
    try:
        data = parse_string(cxxinput.value)
    except Exception as e:
        cxxoutput.value = "Exception:\n" + str(e)
    else:
        # TODO: the formatting is terrible
        cxxoutput.value = repr(data)

on_input(None)

</script>
</body>
</html>

Ok, I played with this a bit more tonight and wrote a simple 'webrepr' dataclasses formatter that has output very similar to black. WIth this formatter, the web interface is a bit more usable:

def webrepr(data: ParsedData, defaults: bool = False, mxlen: int = 88) -> str:
    """
    A dumb black-like formatter for use on the cxxheaderparser webpage, which cannot
    use black.

    No guarantees are provided for this dumper. It probably generates valid python
    most of the time.
    """

    fp = io.StringIO()

    def _format(item, curlen: int, indent: str):
        # see if the default representation fits
        r = repr(item)
        if len(r) + curlen <= mxlen:
            fp.write(r)
            return

        # got to expand the item. Depends on what it is
        newindent = indent + "  "
        if isinstance(item, list):
            fp.write("[\n")
            curlen = len(newindent)
            for li in item:
                fp.write(newindent)
                _format(li, curlen, newindent)
                fp.write(",\n")
            fp.write(f"{indent}]")
        elif isinstance(item, dict):
            fp.write("{\n")
            curlen = len(newindent)
            for k, v in item.items():
                curlen = fp.write(f"{newindent}{k!r}:")
                _format(v, curlen, newindent)
                fp.write(",\n")
            fp.write(f"{indent}}}")
        elif dataclasses.is_dataclass(item):
            # always write the name, then process like a dict
            fp.write(f"{item.__class__.__qualname__}(\n")
            fields = dataclasses.fields(item)
            written = False
            for field in fields:
                # check to see if this is a default value, exclude those
                v = getattr(item, field.name)
                if not defaults and field.repr and field.compare:
                    if field.default_factory is not dataclasses.MISSING:
                        default = field.default_factory()
                    else:
                        default = field.default
                    if v == default:
                        continue
                curlen = fp.write(f"{newindent}{field.name}=")
                _format(v, curlen, newindent)
                fp.write(",\n")
                written = True
            if written:
                fp.write(indent)
            fp.write(")")
        else:
            # I give up, just write it. It's probably fine.
            fp.write(r)

    _format(data, 0, "")
    return fp.getvalue()

There is a bug with functions that have a 'constructor' argument, so this is blocked on brython-dev/brython#1830

Additionally, it looks like there's something else wonky going on too (maybe a PLY issue?). When parsing this simple code in the web editor:

class Fx {
public:

int z(int x);
int x;
};

I get a parsing error, but I don't get one when running the cpython version.

Edit: turns out, the parsing error is for the exact same reason -- because the 'Method' object takes a keyword argument named 'constructor'. So hopefully once they fix that bug then this might be doable?

The brython bug was fixed, but since I'd like to use a CDN for this, we'll have to wait for a new release. Their release schedule appears to be fairly irregular.

Brython has a 3.10.4 release, but it now has a dataclasses-only bug with arguments that have the name "constructor" in them (brython-dev/brython#1859)

Brython is just too buggy, went with pyodide instead.