Home > other >  How to convert this tree structure into a JS MemberExpression tree structure?
How to convert this tree structure into a JS MemberExpression tree structure?

Time:11-24

I have figured out a way to represent the expression a.b[c.d][e].f[g[h[i.j]]] using my own tree format. That expression, represented as a tree, looks like this:

{
  "form": "nest",
  "link": [
    {
      "form": "site",
      "name": "a"
    },
    {
      "form": "site",
      "name": "b"
    },
    {
      "form": "nest",
      "link": [
        {
          "form": "site",
          "name": "c"
        },
        {
          "form": "site",
          "name": "d"
        }
      ]
    },
    {
      "form": "nest",
      "link": [
        {
          "form": "site",
          "name": "e"
        }
      ]
    },
    {
      "form": "site",
      "name": "f"
    },
    {
      "form": "nest",
      "link": [
        {
          "form": "site",
          "name": "g"
        },
        {
          "form": "nest",
          "link": [
            {
              "form": "site",
              "name": "h"
            },
            {
              "form": "nest",
              "link": [
                {
                  "form": "site",
                  "name": "i"
                },
                {
                  "form": "site",
                  "name": "j"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Now, that same string expression is also represented by this JS AST tree structure for the MemberExpression:

{
  "type": "MemberExpression",
  "object": {
    "type": "MemberExpression",
    "object": {
      "type": "MemberExpression",
      "object": {
        "type": "MemberExpression",
        "object": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "a"
          },
          "property": {
            "type": "Identifier",
            "name": "b"
          },
          "computed": false
        },
        "property": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "c"
          },
          "property": {
            "type": "Identifier",
            "name": "d"
          },
          "computed": false
        },
        "computed": true
      },
      "property": {
        "type": "Identifier",
        "name": "e"
      },
      "computed": true
    },
    "property": {
      "type": "Identifier",
      "name": "f"
    },
    "computed": false
  },
  "property": {
    "type": "MemberExpression",
    "object": {
      "type": "Identifier",
      "name": "g"
    },
    "property": {
      "type": "MemberExpression",
      "object": {
        "type": "Identifier",
        "name": "h"
      },
      "property": {
        "type": "MemberExpression",
        "object": {
          "type": "Identifier",
          "name": "i"
        },
        "property": {
          "type": "Identifier",
          "name": "j"
        },
        "computed": false
      },
      "computed": true
    },
    "computed": true
  },
  "computed": true
}

So those two tree structures represent the same string expression a.b[c.d][e].f[g[h[i.j]]]. You'll notice on the first "nest" structure, there are two types of objects, sites and nests. A site is just a name, while a nest means a "computed" property in JS AST terminology. So a nest is like parent[this_is_a_nest[and_another_nest]], while parent.site1.site2.

How do you transform the first tree structure into the second one?

What I have so far isn't really getting there, it is quite confusing.

console.log(JSON.stringify(transform(getNest()), null, 2))

function transform(nest) {
  let i = 0
  let stack = []
  while (i < nest.link.length) {
    let object = nest.link[i  ]
    let property = nest.link[i]
    let member = {
      type: 'MemberExpression'
    }
    stack.push(member)

    if (object.form === 'nest') {
      member.object = transform(object)
    } else {
      member.object = {
        type: 'Identifier',
        name: object.name
      }
    }

    if (property) {
      if (property.form === 'nest') {
        member.property = transform(property)
        member.computed = true
      } else {
        member.property = {
          type: 'Identifier',
          name: property.name
        }
      }
    }
  }

  let object = stack.pop()
  while (stack.length) {
    let nextObject = stack.pop()
    nextObject.object = object
    object = nextObject
  }

  return object
}


function getNest() {
  return {
    "form": "nest",
    "link": [
      {
        "form": "site",
        "name": "a"
      },
      {
        "form": "site",
        "name": "b"
      },
      {
        "form": "nest",
        "link": [
          {
            "form": "site",
            "name": "c"
          },
          {
            "form": "site",
            "name": "d"
          }
        ]
      },
      {
        "form": "nest",
        "link": [
          {
            "form": "site",
            "name": "e"
          }
        ]
      },
      {
        "form": "site",
        "name": "f"
      },
      {
        "form": "nest",
        "link": [
          {
            "form": "site",
            "name": "g"
          },
          {
            "form": "nest",
            "link": [
              {
                "form": "site",
                "name": "h"
              },
              {
                "form": "nest",
                "link": [
                  {
                    "form": "site",
                    "name": "i"
                  },
                  {
                    "form": "site",
                    "name": "j"
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Don't really know how to simplify the problem down in a way to solve it yet.

I don't know if this is of any help (acornjs parser for MemberExpression).

CodePudding user response:

This should do it:

function transform(treeNode) {
  if (treeNode.form == "site") {
    return {
      "type": "Identifier",
      "name": treeNode.name,
    };
  } else if (treeNode.form == "nest") {
    const [base, ...props] = treeNode.link;
    console.assert(base.form == "site");
    return props.reduce((lhs, rhs) => {
      if (rhs.form == "nest") {
        return {
          "type": "MemberExpression",
          "object": lhs,
          "property": transform(rhs), // returns MemberExpression or (if singleton) Identifier
          "computed": true,
        };
      } else if (rhs.form == "site") {
        return {
          "type": "MemberExpression",
          "object": lhs,
          "property": transform(rhs), // returns Identifier
          "computed": false,
        };
      }
    }, transform(base));
  }
}

You can of course simplify the reducer to just

props.reduce((lhs, rhs) => ({
  "type": "MemberExpression",
  "object": lhs,
  "property": transform(rhs),
  "computed": rhs.form == "nest",
}), transform(base));

CodePudding user response:

I just solved this slightly after @Bergi's answer before I saw it, so excited!

function transform(nest) {
  let i = 0
  let stack = [{
    type: 'Identifier',
    name: nest.link[i  ].name
  }]
  while (i < nest.link.length) {
    const object = stack.shift()
    const node = nest.link[i  ]
    if (node.form === 'nest') {
      const property = transform(node)
      stack.push({
        object: object,
        property,
        computed: true
      })
    } else {
      let property = {
        type: 'Identifier',
        name: node.name
      }
      stack.push({
        object: object,
        property: property,
        computed: false
      })
    }
  }

  return stack.shift()
}

Output is:

{
  "object": {
    "object": {
      "object": {
        "object": {
          "object": {
            "type": "Identifier",
            "name": "a"
          },
          "property": {
            "type": "Identifier",
            "name": "b"
          },
          "computed": false
        },
        "property": {
          "object": {
            "type": "Identifier",
            "name": "c"
          },
          "property": {
            "type": "Identifier",
            "name": "d"
          },
          "computed": false
        },
        "computed": true
      },
      "property": {
        "type": "Identifier",
        "name": "e"
      },
      "computed": true
    },
    "property": {
      "type": "Identifier",
      "name": "f"
    },
    "computed": false
  },
  "property": {
    "object": {
      "type": "Identifier",
      "name": "g"
    },
    "property": {
      "object": {
        "type": "Identifier",
        "name": "h"
      },
      "property": {
        "object": {
          "type": "Identifier",
          "name": "i"
        },
        "property": {
          "type": "Identifier",
          "name": "j"
        },
        "computed": false
      },
      "computed": true
    },
    "computed": true
  },
  "computed": true
}
  • Related