1

I have a lot of condition statements stored in a table, each of which has some wildly different formats (if x > Y, if Z = true, if Z <= 12, etc). I've stored them in such a way that I could easily loop through the condition statements and execute a function to update the relevant attributes.

However, as I've found out, it won't let me store my conditional statement as a variable. Meaning, I have to explicitly declare each conditional statement, rather than declare it once and loop through all possible conditions.

Is there a way to do this I'm missing, or simply not understanding?

In the below example, I'd like to use eTable[i].condition to loop through all of the conditions stored at that location. Is this possible somehow?

function checkEvents () {
    eventConditionLength = Object.keys(eTable).length;

    for (let i = 0; i < eventConditionLength; i++) {
        if (eTable[i].condition) {
            alert(i);
        };
    };
};

eTable

var eTable = {
0: {
        sceneTitle: '', //Descriptive/organizational only
        sceneHeader: ['Event 1', 'Event 1', 'Event 1'],
        sceneImage: ['/images/1.jpg', '/images/2.jpg', '/images/3.jpg'],
        sceneText: ['Test Scene Text 1', 'Test Scene Text 1', 'Test Scene Text 1'],
        sceneControls: ['myFunction1', 'myFunction2', 'myFunction3'],
        visible: false,
        completed: false,
        condition: 'cEvent == "e0002"'
    },
1: {
        sceneTitle: '', //Descriptive/organizational only
        sceneHeader: ['Event 1', 'Event 1', 'Event 1'],
        sceneImage: ['/images/1.jpg', '/images/2.jpg', '/images/3.jpg'],
        sceneText: ['Test Scene Text 1', 'Test Scene Text 1', 'Test Scene Text 1'],
        sceneControls: ['myFunction1', 'myFunction2', 'myFunction3'],
        visible: false,
        completed: false,
        condition: 'stat1 > 15 && completed == false'
    }
};
freginold
  • 3,946
  • 3
  • 13
  • 28
hoosier03
  • 13
  • 3
  • 1
    Why not just use an array of objects instead of an object of objects? – Mikey Jan 23 '18 at 20:46
  • 1
    Where does `cEvent`, `stat1`, and `completed` come from? – Ori Drori Jan 23 '18 at 20:46
  • I don't see how your conditions relate to each object, except for `completed`. Where are the variables being compared? Are they external to each object? If so, it would seem like you could store all the data needed to invoke the `Function` constructor (parameter names and a function body), and then create functions and pass in the data. Hard to tell though without more information. –  Jan 23 '18 at 20:54
  • ...and if this isn't data that needs to be serialized, then you don't need strings at all, just create functions. But again, more info is needed before suggesting a reasonable solution. I wouldn't want to proceed without first trying as hard as possible to eliminate storing a string for the condition like you have. –  Jan 23 '18 at 20:57
  • I've got about a dozen JS files right now. Only included the bits relevant to the question here. That said, if your curious, its generally a simple RPG maker tool, with a simplified UI layout. This means, that the majority of my tables are containing UI information. This particular table is generating an event modal, that dynamically changes as you make decisions. The trigger I was working on, is triggering when/where the modal will display, and when it will not. At a general level, these tables organized for literal reading as much as possible, so that people with limited knowledge can read. – hoosier03 Jan 23 '18 at 21:49
  • ...All of that said, the table is structured as such, eTable[x] represents the different story blocks (displayed within a modal) that could come up. The arrays within, progress upwards to display each successive screen within a given block. In this way, I can simply increment x or y in the below, to get different story blocks, or scenes within those blocks. eTable[x].sceneText[y] – hoosier03 Jan 23 '18 at 21:52
  • You weren't asked what you're building. You were asked what the variables in the conditions represented. They don't seem related to the object properties, and you haven't shown any other source of data. –  Jan 23 '18 at 22:51

4 Answers4

2

As you probably know, as you've coded it, it'll always work (as long as the condition isn't an empty string), because any non-empty string is always truthy.

The best and most secure way would be to write some kind of parser which will analyze the condition by breaking it up cleanly and analyzing it. These typically look something like:

const condition = 'x < y';

/**
 * condition is the full string
 * a is the left-hand variable, if needed
 * b is the right-hand variable, if needed
 */
function resolve(condition, a, b) {
  // regex will get the non-alphanumeric operator and the left and right operands
  const [, left, operator, right] = condition.match(/([a-z0-9]+)\s*([^a-z0-9\s]+)\s*([a-z0-9]+)/);
  
  // you'd need a case for each vald operator
  switch (operator) {
    case '<':
      return a < b;
  }
}

console.log(resolve(condition, 3, 5));
console.log(resolve(condition, 5, 3));

Obviously, that's a simple example, but the basic process is the same. There are likely also libraries out there that will handle it for you.

Then you could just use it as needed:

if (resolve('a > b', a, b)) {
  // do something
}

You'd also want some logic that would see if the operands are numbers, and if so use those values instead of the passed in variables.

Another option that I hesitate to provide is to use the eval() function. This function is generally a really big, super bad no-no, as it executes arbitrary code and can open all kinds of security and logic holes. However, it is worth talking about for scenarios where it may be appropriate (like a really small, quick Node.js script for personal use).

const x = 3;
const y = 5;

if (eval('x < y')) {
  console.log('condition hit');
}

This will work, and in your example, just wrap your eTable[i].condition in the eval (and make sure variables it is looking for are declared in the current scope).

Again though, this is generally really bad to use, so if you use it, be sure to read up all about it before moving forward with it.

samanime
  • 25,408
  • 15
  • 90
  • 139
  • Thankyou, appreciate the help. For this project, its important to realize its primarily a learning tool for me, and maybe a toy to play with down the line. Its not storing sensitive data, nor exceptionally large. At this point, I think eval() will do the trick, but have marked this on my list to re-evaluate for a larger solution down the line if the need arises. Given that the thing is only half-done and missing a lot of connections still I think i'll move on for now. – hoosier03 Jan 23 '18 at 21:42
1

You can use eval().

function checkEvents () {
    eventConditionLength = Object.keys(eTable).length;


    for (let i = 0; i < eventConditionLength; i++) {
        if (eval(eTable[i].condition))
        {
            alert(i);
        };
    };
};

As Vasan pointed out in the comments, there are a few downsides to eval to be aware of.

Tyler
  • 1,029
  • 1
  • 8
  • 20
  • Yes, but see also: https://stackoverflow.com/questions/86513/why-is-using-the-javascript-eval-function-a-bad-idea – Vasan Jan 23 '18 at 20:49
  • @Vasan Good call, definitely a few caveats to be aware of. I will add to my answer, thank you. – Tyler Jan 23 '18 at 20:50
0

I believe you passing strings to if condition statement, because eTable[i].condition it is not logical condition, it is just string (I believe you have strings). You need to evaluate your strings as code using eval():

for (let i = 0; i < eventConditionLength; i++) {
    if (eval(eTable[i].condition)) {
        alert(i);
    };
};

P.S.: eval() is bad code style and dangerous. Be careful with that.

0

I can give you 2 solutions for you problem.

  1. The better way, store functions. Each function will hold your condition, for example, instead of store eTable['someCondition] = "x > y", store it like eTable['someCondition] = () => x > y, Then you can invoke it simple by if (eTable[i].condition()) { /* handle true */ } else { /* handle false */ } .
  2. the bad way, use eval() and store string that will represent js code to excute.

I think that first suggestion can achive your goal.

Yossi
  • 445
  • 2
  • 9