0

I'm working on my LIPS project (Scheme-based Lisp in JavaScript) and I want to add a way to add string representation to any object. The code look like this:

NOTE: you can ignore scheme code. At the end there is simplified use case, this is only for context and why I need this.

(add-repr! HTMLElement (lambda (x) 
                           (let ((tag (--> x.tagName (toLowerCase))))
                             (string-append "<" tag ">" x.innerHTML "</" tag ">"))))

it works fine when I evaluate:

(document.createElement "span")
;; or
(document.querySelector ".klas")

but it have problem while evaluating the code:

(let ((div (document.createElement "div")))
  (. div 'constructor 'prototype))

and my interpreter thinks that this is instance of HTMLElement, the JavaScript code look like this:

var repr = new Map();
// values in map are lisp lambdas that are JavaScript functions
// keys are constructor functions
...
            var fn;
            if (repr.has(constructor)) {
                fn = repr.get(constructor);
            } else {
                repr.forEach(function(value, key) {
                    if (obj instanceof key) {
                        fn = value;
                    }
                });
            }
            if (fn) {
                if (typeof fn === 'function') {
                    return fn(obj, quote);
                } else {
                    throw new Error('toString: Invalid repr value');
                }
            }

I check if obj is instanceof given type (HTMLElement from add-repr!) and it return true for prototype.

And throw exception that x.tagName is not defined because it's not instance but prototype.

To simplify the code (I've added scheme code for context) this is the code:

 document.createElement('div').constructor.prototype instanceof HTMLElement;

it return true because prototype was Object.create(HTMLElement). Is there a way to detect if the value is in fact a prototype of any value without have that original value.

var x = document.createElement('div').constructor.prototype;
// context is lost I can't access original value
// check if x is instanceof HTMLElement, but also it's constructed value and not prototype of any value.

and if you think that you can check if there is constructor value, this circular object:

document.createElement('div').constructor.prototype.constructor.prototype.constructor

to sum this question up I want to detect if value is any of but not both:

document.createElement('div')
document.createElement('div').constructor.prototype

My idea that just came to my mind while I was writing this was this:

var x = document.createElement('div').constructor.prototype;
if (x instanceof HTMLElement && x !== x.constructor.prototype) {
    // real instance
}

is this correct approach? I was also looking at Object.getPrototypeOf but it just return HTMLElement object (the one I'm testing). I need this to work for any nested prototype chain, because it's programming construct and user may use anything.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
jcubic
  • 61,973
  • 54
  • 229
  • 402
  • What did you try to achieve by accessing `.constructor.prototype` in the first place? – Bergi May 18 '20 at 16:55
  • @Bergi sorry I don't understand, I have a language that can interact with javascript and user may get constructor.prototype in REPL and get not related error, I need to guard my code to not throw not related error when user do that. – jcubic May 18 '20 at 16:57
  • @Bergi in REPL I'm printing representations of the objects, right now I'm printing `#"` if it's HTMLelement, I want to give user a way to change it. – jcubic May 18 '20 at 16:59
  • @Bergi also I may put that code in my unit tests I don't want them to fail. – jcubic May 18 '20 at 17:01
  • "*My idea that just came to my mind*" - yes, I'd say that's the correct approach – Bergi May 18 '20 at 19:07

1 Answers1

1

For detecting whether something is a prototype object, irrespective of HTMLElement, I would suggest doing

hasOwnProperty(x, "constructor") &&
  typeof x.constructor == "function" &&
  x.constructor.prototype == x

The context of the expression the user is trying to evaluate doesn't matter, they might as well try printing (. HTMLElement 'prototype) directly.

Separately, I would suggest not tying the "representation" function for instances to its constructor through a Map. Your add-repr! should just create a .repr() method on the prototype of the class, using Symbol("lips-repr()") as the property key.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I have another question for something different with Preact `Button.prototype = Object.create(preact.Component)` how to check if Button.prototype is prototype object? Is it possible? I'm trying to figure out why my demo is broken and is_prototype function is one the possible places. – jcubic Nov 14 '20 at 17:29
  • @jcubic I don't know Preact or its `Button`s, but it's possible they have forgotten [to set the `.constructor` property](https://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor) after inheriting from `preact.Component.prototype`. – Bergi Nov 14 '20 at 17:33
  • Ok, that was my that forget to set constructor. Thanks for the tip. – jcubic Nov 14 '20 at 17:44