Multiline Strings

# Florian Bösch (11 years ago)

I'm sorry if that's been asked before (I looked trough the archives trough to 2012 and couldn't find a thread on it).

Are there any plans on adding multiline strings?

Some examples of multiline strings below.

Lua:
foo = [[A
Multiline
String]]

Python:
foo = '''A
Multiline
String'''

Coffeescript:
foo = '''A
Multiline
String'''

Dart:
foo = ''''A
Multiline
String'''

C++11:
foo = R"bar(A
Multiline
String)bar";

Ruby:
foo = 'A
Multiline
String'
# Claude Pache (11 years ago)

We precisely discussed templates on es-discuss tonight. If I believe what I just read on 1, you will be able to write:

foo = `A
Multiline
String`
# Florian Bösch (11 years ago)

On Fri, Mar 7, 2014 at 8:36 AM, Claude Pache <claude.pache at gmail.com> wrote:

We precisely discussed templates on es-discuss tonight. If I believe what I just read on [1], you will be able to write:

Ah cool, sorry :)

A follow up question on that has to do with line number/column.

If you're doing things like this in say myfile.js (line numbers leading)

01: compileShader('''essl
02:  void main(){
03:    gl_FragColor = vec3(1.0); // would be an error
04:  }
05: ''');

Then the function that gets such a string would very much like to say something like:

Error in GLSL File: myfile.js, Line: 3
...

There's two complications with that. A string doesn't carry the line number it comes from. Also, myfile.js might get concated with other files. And lastly strings might get pasted together from smaller snippets.

The solution to this issue I'm using at the moment translates things like that to:

compileShader('''#line 1 file myfile.js
#line 2 myfile.js
  void main(){
#line 3 myfile.js
    gl_FragColor = vec3(1.0); // would be an error
#line 4 myfile.js
  }
#line 5 myfile.js
''');

I'm wondering if there's interest to address this issue within ES? I'm also wondering if Multiline strings are the right tool to address them?

# Peter van der Zee (11 years ago)

On Fri, Mar 7, 2014 at 8:56 AM, Florian Bösch <pyalot at gmail.com> wrote:

There's two complications with that. A string doesn't carry the line number it comes from. Also, myfile.js might get concated with other files. And lastly strings might get pasted together from smaller snippets.

I think you want to take a look at "source maps". They're specifically designed to deal with this problem.

# Florian Bösch (11 years ago)

On Fri, Mar 7, 2014 at 8:52 PM, Peter van der Zee <ecma at qfox.nl> wrote:

I think you want to take a look at "source maps". They're specifically designed to deal with this problem.

The problem is that a function like compileShader would look like this:

var compileShader(source){
   var shader = gl.createShader(gl.VERTEX_SHADER);
   gl.shaderSource(shader, source);
   gl.compileShader(shader);
   if(!gl.getShaderParameter(GL_COMPILE_STATUS)){
     var error = glGetShaderInfoLog(shader);
     // now what?

     // need file of source

     // need lines # of source

   }
}

And you'd do things like:

var source = """
   void main(){
      #{someSnippet}
      gl_FragColor = whatever;
   }
"""
compileShader(source);

I don't see how source maps help either.

# John Barton (11 years ago)

You may like to take a look at how the traceur compiler ( google/traceur-compiler) works. It allows one to write code like

var statement = parseStatement `${result}[${index}++] = ${expression};`;

where the ${} syntax surrounds variables from the caller that are substituted into the string. In our case the result 'statement' is an AST but it could source code just as well. And source maps work fine for our code. Well as fine a source maps ever work ;-)

# Florian Bösch (11 years ago)

That's a fine approach, and I'm not against preprocessing in any flavor if that's your cup-o-tea. The problem rather is that one explicit usecase of multiline strings (aka templates) is to make it easier to write DSLs. But if you write DSLs and embedd the strings in your JS source somewhere, you're gonna have to deal with debugging of any kind.

For example, WebGL implementations return errors strings like these:

ERROR: 0:1: 'foobar' : syntax error

You can imagine that being confronted with a string like that, out of thousands of shader code lines in your application, isn't very useful. You'll also realize that, not all errors can actually be detected with a validator.

In order to make this a useful piece of error message, you'll need to translate whatever WebGL throws back at you, to a sourceline and filename. And unless you instrumented this beforehand with a preprocessor, you're not gonna get it.

So my question is this, is everybody happy with the state of affairs that a preprocessor is the only viable way to use templates/multiline strings for DSLs, or am I the only one who thinks that could somehow be better?

# Mark S. Miller (11 years ago)

Thanks for raising this. I agree you've identified a real issue. Referring to slide #28 of Domenic's presentation, we'd need to add an array of sourcemaps to the frozen[1] record, to parallel the raw and cooked arrays, that say where each character of the raw and/or cooked arrays come from in the source. The func, which is the template-string-parser of the embedded DSL, has no other way to recover this information. The source-map-array should probably only describe the origins of the characters in the cooked array, since the origins of the characters in the raw array can be derived from this but not vice versa.

None of this would effect the use of template strings in Traceur that John describes, as he's interested in source positions in the file being translated, not source positions in the template strings within the Traceur compiler used to describe these translations. But for debugging Traceur itself, one may well be interested in the latter.

There are several issues with this, none of which are show stoppers:

  1. There is not yet a standard for sourcemaps. But see Source Map Revision 3 Proposal, Chrome Developer Tools Docs, and mozilla/source-map. Would someone care to champion this for inclusion in ES7?

  2. Since this can be addressed compatibly in ES7, we should note in ES6 that future editions may add more frozen data to this record, that can be feature tested for.

  3. Code doesn't normally know where it came from. Adding these to template-string records will give JavaScript the power of __FILE__ and __LINE__.

  4. Browsers are still all over the place in how they report Error stack trace information. Even after all the renormalizing done by SES, we still get the divergent stack traces shown at[2]. For all of these, the first stack trace comes from within a call to eval. The second from a dynamically generated script tag.

  5. At startSES.js I'm adding the source info that eval is supposed to use, as explained at [3], where the alleged source file is example.com/fake1.js and example.com/fake2.js. (See explicit.html.)

However, that alleged source file name is not showing up in any of the browser stack traces. Am I not using [3] correctly?

I would hope all of these could be addressed in ES7, including the addition of an array of source maps to the template string record.

[1] A more correct expansion is:

  var whatsThis = func(
    Object.freeze({
      raw:    Object.freeze(['', ' + ', '\\n = ', '']),
      cooked: Object.freeze(['', ' + ', '\n = ', ''])
    }),
    x,
    y,
    x + y
  );

except that the record is evaluated once per evaluation of the enclosing Program (loading of the compilation unit), rather than per evaluation of the expression. This allows func to memoize template-string-parsings on the identity of the record.

[2] chrome-canary-35: drive.google.com/file/d/0Bw0VXJKBgYPMMmVYVGtHb2tJaUk

FFNightly30-0a1: drive.google.com/file/d/0Bw0VXJKBgYPMZFlCaDByT2YzdnM

Safari702: drive.google.com/file/d/0Bw0VXJKBgYPMRVczXy1pOU9lcEk

Opera20: drive.google.com/file/d/0Bw0VXJKBgYPMUjVtdWxBR0hRTUE (Unsurprisingly, essentially the same as Chrome)

[3]: updates.html5rocks.com/2013/06/sourceMappingURL-and-sourceURL-syntax-changed, www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl, developers.google.com/chrome-developer-tools/docs/javascript-debugging#breakpoints

# Mark Miller (11 years ago)

On Sat, Mar 8, 2014 at 11:05 AM, Mark S. Miller <erights at google.com> wrote:

4. Browsers are still all over the place in how they report Error stack trace information. Even after all the renormalizing done by SES, we still get the divergent stack traces shown at[2]. For all of these, the first stack trace comes from within a call to eval. The second from a dynamically generated script tag.

To see these for yourself in your own browsers, visit google-caja.googlecode.com/svn/trunk/src/com/google/caja/ses/explicit.html

# Mark Miller (11 years ago)

... and click of the last two "[+]"s to expand these.

# Allen Wirfs-Brock (11 years ago)

On Mar 8, 2014, at 11:05 AM, Mark S. Miller wrote:

[1] A more correct expansion is: ...

Actually, more like:

  var whatsThis = func(
    Object.freeze(
      Object.defineOwnProperty(['', ' + ', '\n = ', ''],'raw', {value: Object.freeze(['', ' + ', '\\n = ', ''])}
    ),
    x,
    y,
    x + y
  );

As the "cooked" array itself is now specified as the call site object. See people.mozilla.org/~jorendorff/es6-draft.html#sec-runtime-semantics-runtime-semantics-argumentlistevaluation

I'll add a note about possible future properties. Also, current spec. says the raw property is enumberable. That sounds like a bug I'll fix. (don't want "raw" showing up if somebody for-in enumerates the call-site object.