qooxdoo-compiler icon indicating copy to clipboard operation
qooxdoo-compiler copied to clipboard

Add transpiler support for array index overloading

Open johnspackman opened this issue 7 years ago • 6 comments

It would be great if we could reduce long winded property access statements like myObj.getArrayObj().getItem(2).getMyPropValue() down to their native equivalent like myObj.arrayObj[2].myPropValue; to do this requires that the Qooxdoo property mechanism is updated to use Object.defineProperty so that getters and setters can be implicitly defined on class prototypes, but there is no mechanism in ES5 which will allow array indexes to be overridden.

It may be possible to override array accessors with the help of transpilation, eg if myObj[0] is transpiled to myObj.getItem(0) and there is a polyfill of Array.prototype.getItem=function(index){return this[index]; }. Replacing every array access with a method call would cause a slow down in performance, but by using the prototype chain the javascript interpretter would (hopefully) be able to efficiently determine the function to call and inline the array access where appropriate.

Note that implementing ES6 Proxy in ES5 is possible via a similar mechanism but has been shown to be very slow because Proxy allows you to intercept every property access, so the plugin has to change every get or set with a function call (eg myObj.x=1 becomes globalSetter(myObj, “x”, 1)) - see babel-plugin-proxy

This solution would mean that referencing anything with [...] would have to be intercepted, eg:

var myObj = { a: 1 };
myObj["a"] = 2;

would transpile as:

var myObj = { a: 1 };
myObj.getItem("a") = 2;

so our polyfill would have to be on Object:

Object.prototype.getItem = function(index){
  return this[index]; 
};

This is just an idea at present, it may not actually be possible, or possible but just too slow.

johnspackman avatar Jan 18 '18 09:01 johnspackman

This should be embedded in the general modernization of the property system and databinding, using Proxies. Here is an example how to implement negative array indexes with proxies. Fun stuff, but a complex project!

cboulanger avatar Apr 29 '19 21:04 cboulanger

@derrell said in gitter:

I started playing with creating ES6 class-compatible qx classes. I have a general framework. The biggest problem is that you can't use qx style constructor or member functions, and still use super, because at definition time, they are function objects, not class members.

let GLOBAL = global || window;
let qx = { Class : { define : define } };


qx.Class.define(
  "A",
  {
    construct : function(junk)
    {
      console.log(`A constructor: junk=${junk}`);
    },

    members :
    {
      foo : function()
      {
        console.log("foo in A");
      },

      bar : function()
      {
        console.log("bar in A");
      }
    }
  });
console.log(`A=` + A.toString());

qx.Class.define(
  "B",
  {
    extends : A,

    construct : function()
    {
      //super("Hi there");
      console.log("B constructor");
    },

    members :
    {
      foo : function()
      {
        //super();
        console.log("foo in B");
      }
    }
  });
console.log(`B=` + B.toString());


let a = new A("hello");
a.foo();
a.bar();
let b = new B();
b.foo();
b.bar();


//------------------------------------------------------------------------------


function define(name, config)
{
  let             s;
  let             clazz;
  let             separator;

  s =
    [
      `return class ${name}`,
      "{",
      config.construct.toString().replace("function", "constructor"),
    ].join("\n");
  s += "\n";

  separator = "";
  for (let member in config.members)
  {
    s +=
      [
        `${member}`,
        config.members[member].toString().replace("function",""),
        `${separator}`
      ].join("");
    s += "\n";
  }

  s += "}";

  clazz = GLOBAL[name] = Function(s)();
  Object.defineProperty(clazz, "name", { value : name });

  if (config.extends)
  {
    let             base = config.extends;

    Object.setPrototypeOf(clazz, base);
    Object.setPrototypeOf(clazz.prototype, base.prototype);
  }
}

johnspackman avatar Apr 01 '20 11:04 johnspackman

@johnspackman said:

@derrell one of my wish list items was to transpile so that super() works on Qooxdoo classes; and this.base is implemented by calculating the actual function to call (ie this.base() becomes myPackage.myClass.prototype.myFunction.call(...) and so we could consider a scheme where the compiler intercepts super()/this.base to work with your framework

johnspackman avatar Apr 01 '20 11:04 johnspackman

@cboulanger said in gitter:

@derrell Can you explain why you would want to convert qooxdoo "classes" into ES6 classes? I thought the idea was to use ES6 classes to behave like qooxdoo "classes"?

johnspackman avatar Apr 01 '20 11:04 johnspackman

@derrell said in gitter:

@cboulanger ES6 classes are almost syntactic sugar on top of prototype-based classes. The one real exception is super() which can't be fully emulated in ES5. (See, beginning at the paragraph above "Superclass property access" in https://medium.com/@robertgrosse/how-es6-classes-really-work-and-how-to-build-your-own-fd6085eb326a) Basing qooxdoo classes on real ES6 classes means that we would have the full ability of the language, including, likely, things that get added in the future. I see it as the forward-thinking way of implementing, rather than trying to emulate the future using features of the past (which may or may not be possible).

johnspackman avatar Apr 01 '20 11:04 johnspackman

the down side of that could be that ES6 additions may not sit easily along side Qooxdoo features, or may be incompatible with Qooxdoo classes; it would also mean that we could be restricted in future in the language features that we want to add.

IMHO it would be cool if the Qooxdoo implementation is the reference implementation, and also make it interoperate with ES6 classes. So you would be able to derive an ES6 class from a Qooxdoo class, and derive a Qooxdoo class from an ES6 class.

The super() implementation would be done by the compiler, in the same way that this.base is implemented now, and properties are added by using Object.defineProperty so that code like myObject.setMyProp(123) and myObject.myProp = 123 are synonymous.

Whether this means that we have to make the implementation of qx.Class.define be backed by actual ES6 classes or not (ie can we get away with just using Object.defineProperty) I don't know.

johnspackman avatar Apr 01 '20 11:04 johnspackman