duktape icon indicating copy to clipboard operation
duktape copied to clipboard

ES2015 Template Strings

Open kphillisjr opened this issue 8 years ago • 15 comments

This is yet another generalized break down of features new to ES6. ( You can find this, and others at this website: http://es6-features.org/ )

  • [ ] String Interpolation

    
    // New Method:
    var customer = { name: "Foo" };
    var card = { amount: 7, product: "Bar", unitprice: 42 }; message = `Hello ${customer.name},
    want to buy ${card.amount} ${card.product} for
    a total of ${card.amount * card.unitprice} bucks?`;
    
    // Old Method:
    var customer = { name: "Foo" };
    var card = { amount: 7, product: "Bar", unitprice: 42 };
    message = "Hello " + customer.name + ",\n" +
    "want to buy " + card.amount + " " + card.product + " for\n" +
    "a total of " + (card.amount * card.unitprice) + " bucks?";
    
  • [ ] Custom Interpolation

    // New Method
    get`http://example.com/foo?bar=${bar + baz}&quux=${quux}`;
    // Old Method
    get([ "http://example.com/foo?bar=", "&quux=", "" ],bar + baz, quux);
    
  • [ ] Raw String Access

    // This is New Feature only.
    function quux (strings, ...values) {
    strings[0] === "foo\n";
    strings[1] === "bar";
    strings.raw[0] === "foo\\n";
    strings.raw[1] === "bar";
    values[0] === 42;
    }
    
    // 
    quux `foo\n${ 42 }bar`;
    
    String.raw `foo\n${ 42 }bar` === "foo\\n42bar";
    

kphillisjr avatar Aug 10 '15 21:08 kphillisjr

This should be a relatively straight forward addition. The key Features for this is that it works a lot like the current strings do, but with a few clear differences. Most of the lexical information is found in Section "11.8.6 Template Literal Lexical Components".

  • All Template strings start and end with the Grave accent ( ``` ) character.

    var exampleTemplate1 = `My Example Template`;
    printf(exampleTemplate1);
    

    This code will output the following...

    My Example Template
    
  • Template strings can span lines. For example...

    var exampleTemplate2 = `Line 1.
    Line 2
    Line 3
    Line 4`;
    print(exampleTemplate2);
    

    This code will output the following...

    Line 1.
    Line 2
    Line 3
    Line 4
    
  • Template strings can span lines. For example...

    var exampleTemplate2 = `Line 1.
    Line 2
    Line 3
    Line 4`;
    print(exampleTemplate2);
    

    This code will output the following...

    Line 1.
    Line 2
    Line 3
    Line 4
    
  • Template strings variable access is done using ${ to begin the script code, and then it ends the script code with }. For example...

    var myLineNumber = [1, 2, "Green" , 4]
    var exampleTemplate3 = `Line ${myLineNumber[0]}
    Line ${myLineNumber[1]}
    Line ${myLineNumber[2]}
    Line ${myLineNumber[3]}`;
    print(exampleTemplate3);
    

    This code will output the following...

    Line 1
    Line 2
    Line Green
    Line 4
    

kphillisjr avatar Aug 19 '15 10:08 kphillisjr

I think this would be interesting feature, however after some consideration I'm not sure if this is most important? It is worth noting that there is also Tagged Template Literals, as explained here:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals

Currently you can get those features into Duktape using transpiler (Babel) and they are part of the workflow of many developers. Furthermore, If you have Template literals, you also expect to have Tagged template literals and to use them efficiently, one might also expect to have support for rest parameters in functions to work.

Having said that, even the basic support for multiline strings without the template syntax could be useful in cases where users Duktape is used as a standalone plugin allowing users to write JS plugins creating for example multiline XML strings, which are processed by libxml. Actually I just did that yesterday, so that's why I'm commenting here :) But I'm still unsure if that's really needed, because in many cases you can just run the code first through transpiler and get also other ES6/ES7 features, which the writers of the plugins might expect to have.

terotests avatar Sep 13 '16 06:09 terotests

Transpiling code through Babel means using node.js, which kind of defeats the purpose. My current use case is exactly what you mentioned, executing js plugins at runtime - no possibility of a compilation step there.

ricardobeat avatar Dec 20 '16 01:12 ricardobeat

Supporting multiline strings without interpolation would be very simple. Interpolation is a bit trickier because it creates a more complicated dependency between lexing and compilation (essentially, expressions need to be compiled within a literal). That may take bit more effort to work in.

svaarala avatar Dec 20 '16 01:12 svaarala

Hmm, the syntax is defined in parts (TemplateHead, TemplateMiddle, TemplateTail) so maybe if that approach was used it could be worked in a bit easier. The compiler would see a TemplateHead (or NoSubstitutionTemplate), parse 0-N TemplateMiddles, and finish with a TemplateTail to complete the expression.

svaarala avatar Dec 20 '16 01:12 svaarala

Semantically, `foo ${bar} quux` should be exactly equivalent to "foo " + bar + " quux". I don't think that would be too difficult to parse?

fatcerberus avatar Dec 20 '16 04:12 fatcerberus

I'm not sure about this, but to keep future implementation simpler, I think there should be a some kind of "template function" -pointer, which has the default implementation for a function which concatenates all the arguments to a simple string. Something like

function internalTemplateString(list_of_strings, listOfValues) {
  var list_or_values = Array.prototype.slice.call(arguments, 1)
  var s = ""
  for(var i=0; i<list_of_strings.length;i++) {
    s += list_of_strings[i]
    if( i < list_or_values.length) {
    		s+= list_or_values[i]
    }
  }
  return s
}

EDIT: Corrected the implementation, it was not that simple function, I created perhaps closer to working example in here. Template tag can return anything, also an object, so thinking about it as a function call could be close to correct https://jsfiddle.net/e671rrce/

Then if the lexer it encounters a string literal it could parse it as a function call with N arguments, where the arguments are then passed to either to internalTemplateString or to the function specified by the tag before the string template.

The idea would be to avoid duplicate work in future, when considering how to implement also the custom interpolation support with tags.

terotests avatar Dec 20 '16 06:12 terotests

Semantically, foo ${bar} quux should be exactly equivalent to "foo " + bar + " quux". I don't think that would be too difficult to parse?

There's nothing conceptually difficult about it for sure. But the current lexer generates a stream of tokens in a mostly stateless way. The lexer doesn't recurse back into the compiler at present to handle that ${bar} part, so this would need to be parsed as three tokens:

  • foo ${ (TemplateHead): generate a string load for "foo " to Rn
  • bar: parsed as expression, coerced to a string to Rn+1
  • } quux: (TemplateTail): generate a string load for " quux" to Rn+2, and since the template is done, concat Rn, Rn+1, Rn+2 to target register

The difficulty -- with current lexer/compiler structure, not conceptually -- is that the lexer needs to remember it's in the middle of parsing a template when continuing with the TemplateTail. It doesn't currently have that state; the state can be added but my point was simply that it's not a simple addition of a token to a list of existing ones.

svaarala avatar Dec 20 '16 11:12 svaarala

@kphillisjr Because string coercion and concatenation is a common feature it should probably be supported directly. There's an internal call to do that reasonably efficiently: duk_concat().

To be clear, there's nothing conceptually difficult with about any of the ES6 syntax constructs as such, they're pretty standard syntax constructs. The concrete issue for implementing them is that the lexer/compiler in master is designed for ES5.1. While it can be tweaked to parse many ES6 syntax changes, it needs structural changes to become fully ES6 capable.

So some changes are easier than others. For example, computer property names ({ [1+2]: 'three' }) were very easy to add. These template literals are harder but still probably doable with limited changes.

svaarala avatar Dec 20 '16 11:12 svaarala

To be clear, there's nothing conceptually difficult with about any of the ES6 syntax constructs as such, they're pretty standard syntax constructs.

I agree for the most part, with the exception of anything related to destructuring. That feels a bit "exotic" to me. The only case I can think of that's similar, interestingly enough, is Duktape's inspiration, Lua. :)

fatcerberus avatar Dec 20 '16 15:12 fatcerberus

Destructuring and pattern matching for LHS is quite common in e.g. ML-inspired languages. I have fond memories of using Standard ML of New Jersey for protocol writing for example, because you can pattern match entire messages (on the LHS side) very conveniently.

svaarala avatar Dec 20 '16 15:12 svaarala

Maybe my solution was not very good, but here is the problem I have as code:

ctx.test_js_fn = function(listOfRows) {    
    return `<View background-color="#333333" padding="5px" >${
                    listOfRows.map((row)=>{
                        return `<Label color="#ffffff" text="${row[4]}"></Label>`
                    })
                }   
            </View>`
}

String literals in neat in that way they allow almost React JSX -like syntax for representing XML templates. The C-code is quite capable of parsing or consuming the XML but creating the content dynamically is much harder there. It is very useful for creating user interface templates, images, layers of videos or screens etc.

terotests avatar Jan 09 '17 14:01 terotests

Hi! It's 2021 now. Hasn't this function been realized yet? It's really easy to use!

guoai2015 avatar Sep 30 '21 08:09 guoai2015

Any plan to add this?

panlina avatar Mar 11 '24 02:03 panlina

It is now 2024. In 2022, I used Webpack to bundle with Babel, which I thought was a perfect solution. Later on, I started developing embedded systems using vscode just like web frontend development. Below is my configuration file for reference:

the package.json

{
  "name": "your-app",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "build": "webpack --mode=production",
    "dev": "webpack --mode=development"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.8",
    "babel-loader": "^8.2.3",
    "webpack": "^4.46.0",
    "webpack-cli": "^4.9.1"
  },
  "dependencies": {
  }
}

the webpack.config.js

const path = require('path');

var config = {
    entry: './app/main.js',
    output: {
        path: path.resolve(__dirname, 'dist/app'),
        filename: '[name].js',
        // libraryTarget: 'umd',
        globalObject: 'this'
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: 'babel-loader',
            exclude: /node_modules/
        }]
    }
};

module.exports = (env, argv) => {
    if (argv.mode === 'development') {
        config.devtool = 'source-map';
    }

    if (argv.mode === 'production') {
        // ...
    }

    return config;
};

the .babelrc

{
    "presets": [
        ["@babel/preset-env", {
            "exclude": ["@babel/plugin-transform-new-target"]
        }]
    ],
    "plugins": []
}

Using the above files, you can create a project. After running 'npm run build', you can package the JavaScript files that can be used by Duktape. It's worth noting that webpack version 4.x must be used. I've tried using version 5.x, but the packaged JavaScript files still contain ES6 syntax, which Duktape cannot recognize.

guoai2015 avatar Mar 11 '24 07:03 guoai2015