prevwong / reka.js

💎 State management system to build any no-code editor
https://reka.js.org/
MIT License
646 stars 48 forks source link

Resolving variable ambiguity #151

Open prevwong opened 8 months ago

prevwong commented 8 months ago

Overview

Currently, Reka resolve variables by their names and scope:


{
  type: "Program",
  globals: [ { type: "Val", name: "counter", ... }
  components: [
    {
      type: "RekaComponent", 
      name: "App", 
      state: [ { type: "Val", name: "counter", ... }
      template: {
    type: "TagTemplate",
        tag: "text",
        props: { 
      "value": { 
          type: "Identifier",
          name: "counter" // refers to the state variable
       } 
        },
      }
    }
  ]
}

In the above example, the counter variable used as prop refers the counter state variable in the App component, rather than the global variable. This design is simple and straightforward and it's based on what programmers typically expect when writing code.

However, this does make referencing the global counter variable impossible. More importantly, when building a page editor — the names of these variables are going to be defined by users and chances are we’re going to need to build some user friendly UI (ie: a dropdown to list variables). In which case, having variables to be resolved by names and scopes is going to cause a lot of confusion.


Removing ambiguity in the State

To solve this, we need a more explicit way to resolve variables. We can achieve this by requiring identifiers to specify the id of the variable explicitly:

{
  type: "Program",
  globals: [ { id: "hhghak1", type: "Val", name: "counter", ... }
  components: [
    {
      type: "RekaComponent", 
      name: "App", 
      state: [ { id: "kbamk10", type: "Val", name: "counter", ... }
      template: {
    type: "TagTemplate",
        tag: "text",
        props: { 
       "value": { 
        type: "Identifier",
        ref: "hhghak1" // refers to the global variable
        } 
        },
      }
    }
  ]
}

Parser

To support this change, we will need to make some changes to the Parser as well. An easy way to achieve a non-ambiguous to require identifiers to specify the “path” of the variable they are referencing:

val counter = 0;

component App() {
  val counter = 1;
} => (
  <text value={$state.counter} />
)

Here, $state.counter explicitly specifies that we’re referencing the state variable. Similarly we can allow the following:

One other place where we need to take care of this is in templates with the @each directive. Currently consumers can create a variable to reference each item in the iterator:

<div @each={item in [1,2,3]}>
   <div @each={item in [1,2,3]}>
    <text value={item} /> // item refers to the nested item 
   </div>
</div>

In this case, it is also impossible to reference the outer iterators item using our existing model. Using the same solution with the counter example, we can do the following:

<div @each={item in [1,2,3]}>
   <div @each={item in [1,2,3]}>
       <text value={$each.0.item} /> // item refers to the outer item 
   </div>
</div>