Web Components in Scheme interpreter in JavaScript?

1 week ago 9
ARTICLE AD BOX

I have a Scheme interpreter written in JavaScript called LIPS, and I have a define-class macro which is using the ES5 prototype system. But I have a problem in creating super().

I'm trying to create a PoC of super inside a class. So I can create Web Components:

This is my code:

<script src="https://cdn.jsdelivr.net/npm/lips@beta/dist/lips.js"></script> <script type="text/x-scheme" bootstrap> ;; MODIFICATION OF define-class MACRO FROM STANDARD LIBRARY (define (%class-constructor expr parent) "(%class-constructor expr) Version of %class-lambda that is used as constructor." (let ((args (gensym 'args)) (ret (gensym 'ret))) `(lambda ,args (let ((,ret (apply ,(cadr expr) (unbind this) ,args))) (if (and (object? ,parent) (instanceof ,parent ,ret)) ,ret ;; we return this to solve issue when constructor ;; return a promise ;; ref: https://stackoverflow.com/a/50885340/387194 this))))) (define-macro (define-class name parent . body) "(define-class name parent . body) Defines a class - JavaScript function constructor with prototype. parent needs to be class, constructor function, or #null usage: (define-class Person Object (constructor (lambda (self name) (set-obj! self '_name name))) (hi (lambda (self) (display (string-append self._name \" says hi\")) (newline)))) (define jack (new Person \"Jack\")) (jack.hi) ; prints \"Jack says hi\"" (let ((g:parent (gensym))) (let iter ((functions '()) (constructor '()) (lst body)) (if (null? lst) `(begin (define ,name ,(if (null? constructor) `(lambda ()) (%class-constructor constructor parent))) (set-obj! ,name (Symbol.for "__class__") true) (let ((,g:parent ,parent )) (if (not (null? ,g:parent)) (begin (set-obj! ,name 'prototype (Object.create (. ,g:parent 'prototype))) (set-obj! (. ,name 'prototype) 'constructor ,name)))) (set-obj! ,name '__name__ ',name) ,@(map (lambda (fn) `(set-obj! (. ,name 'prototype) ,(%class-method-name (car fn)) ,(%class-lambda fn))) functions)) (let ((item (car lst))) (if (eq? (car item) 'constructor) (iter functions item (cdr lst)) (iter (cons item functions) constructor (cdr lst)))))))) ;; MAIN CODE (define-class CurrentDate HTMLElement (constructor (lambda (self) (Reflect.construct HTMLElement #() CurrentDate))) (connectedCallback (lambda (self) (let ((now (new Date))) (set! self.textContent (now.toLocaleDateString)))))) (customElements.define "current-date" CurrentDate) </script> <current-date></current-date>

The Scheme code just uses ES5 prototypes to build a class. It's just a 1-1 mapping of JavaScript code in Scheme.

I keep getting the error:

Uncaught TypeError: Failed to construct 'HTMLElement': Illegal constructor

(The stack snippet doesn't show the correct error; you need to open DevTools)

I was testing the code from: What is the ES5 way of writing web component classes?

The code throws at this expression:

(apply ,(cadr expr) this ,args)

And (cadr expr) is:

(lambda (self) (Reflect.construct HTMLElement #() CurrentDate))

The lambda macro executes a bit of code, but Reflect.construct doesn't need to be the first expression like with super. And the only thing that the custom component needs to do is to return a DOM node from calling the HTMLElement constructor.

This executes without any errors:

function HelloElement() { console.log('x'); const ret = make(); console.log(ret); console.log(ret instanceof HTMLElement); return ret; } function make() { return Reflect.construct(HTMLElement, [], HelloElement); } customElements.define('hello-element', HelloElement); <hello-element id="example"></hello-element>

I also tried to call JavaScript from Scheme but got the same error:

(define js-eval self.eval) (define-macro (super class) `((js-eval "(unbind, constructor) => Reflect.construct(HTMLElement, [], unbind(constructor));") ,unbind ,class)) (define-class CurrentDate HTMLElement (constructor (lambda (self) (super CurrentDate))))

In LIPS, functions have what I call soft binding (this context), which you can unbind (get the original function).

I was able to trigger the same error in JavaScript by adding:

return Promise.resolve(ret);

LIPS sometimes may return async code that I wanted to get rid of, but after doing a quick test, the define-class doesn't return a promise.

I can trigger the same error when I call (new CurrentDate) in Scheme and new HelloElement() in the JavaScript example. But I don't call new in my Scheme? The code is triggered by HTML code.

What may be other possible issues with my code? And the possible root cause of the error?

Read Entire Article