8

I'd like to implement a string formatter. I've used formatters that take string like "the quick, brown {0} jumps over the lazy {1}" where you pass in parameters whose cardinal location is used to replace the braced integers. I'd love to be able to do something more like "the quick, brown {animal1} jumps over the lazy {animal2}" where animal1 and animal2 are variables and are simply evaluated. I got the following method implemented, but then realized that eval is not going to work because it doesn't use the same scope.

String.prototype.format = function() {
    reg = new RegExp("{([^{}]+)}", "g");
    var m;
    var s = this;
    while ((m = reg.exec(s)) !== null) {
        s = s.replace(m[0], eval(m[1]));
    }
    return s;
};
  1. Is there a way to do this without using eval (doesn't seem like it).
  2. Is there a way to give eval the closure so it gets scope? I tried with(window) and window.eval(), but that didn't work.
Taylan Aydinli
  • 4,333
  • 15
  • 39
  • 33
Jeremy Foster
  • 4,673
  • 3
  • 30
  • 49

3 Answers3

6

For a usage like var result = "This will get formatted with my {name} and {number}".format({name: "TetsujinOni", number: 1234});

Why not head off in this direction:

String.prototype.format = function(scope) {
    reg = new RegExp("{([^{}]+)}", "g");
    var m;
    var s = this;
    while ((m = reg.exec(s)) !== null) {
        s = s.replace(m[0], scope[m[1]]);
    }
    return s;
};
Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
Tetsujin no Oni
  • 7,300
  • 2
  • 29
  • 46
5

All global variables are defined in the window object, so you should be able to do this without eval:

String.prototype.format = function(scope) {
    scope = scope || window; //if no scope is defined, go with window
    reg = new RegExp("{([^{}]+)}", "g");
    var m;
    var s = this;
    while ((m = reg.exec(s)) !== null) {
        s = s.replace(m[0], scope[m[1]]);
        //                  ^^^^^^^^^^^
    }
    return s;
};

Here you should also simply be able to change window to what scope you feel like.

If variables are not in the global scope, but rather in your current scope, you might want to read this or go with Tetsujin's solution.

Community
  • 1
  • 1
h2ooooooo
  • 39,111
  • 8
  • 68
  • 102
  • Thanks. That's cool. What if my variables are not in global scope? What if they're in a function? `function() { var animal1 = 'fox'; "the {animal1} jumps...".format(); }` – Jeremy Foster Oct 08 '13 at 17:37
  • @JeremyFoster Check my edit - I've added a link to another SO thread refering to this exact problem. Short answer is that you can't *per se* unless you use an internal private object. That said, you could, as Tetsujin's solution refers to, simply parse `this` for his `scope` variable when formatting and you *should* refer to the current scope. – h2ooooooo Oct 08 '13 at 17:38
  • @JeremyFoster I've edited my post to include a scope object that you can pass as an argument if it's **not** in `window`. – h2ooooooo Oct 08 '13 at 17:41
  • I think I like it. Let me play. – Jeremy Foster Oct 08 '13 at 17:43
1

Oh yes... the holy grail of javascript variable interpolation... You actually can pass the local scope around by using dark magic like this:

String.prototype.format = function(_eval) {
    return this.replace(/{(.+?)}/g, function($0, $1) {
        return _eval($1);
    })
};

function foo() {
    var a = 123, b = 456;
    s = "a is {a} and a+b={a+b}".format(function(x) {return eval(x)})
    console.log(s) // a is 123 and a+b=579
}

I'm afraid there's no way to make the format call less verbose.

And here's a version that requires explicit scope passing, but still allows for arbitrary expressions in {...}'s:

String.prototype.format2 = function(scope) {
    eval(Object.keys(scope).map(
        function(x) { return "var " + x + "=scope." + x
    }).join(";"));
    return this.replace(/{(.+?)}/g, function($0, $1) {
        return eval($1);
    })
};

function foo() {
    var a = 123, b = 456;
    s = "a is {a} and a+b={a+b}".format2({a:a, b:b})
    console.log(s) // a is 123 and a+b=579
}

You are not expected to understand this.

georg
  • 211,518
  • 52
  • 313
  • 390