svaarala / duktape

Duktape - embeddable Javascript engine with a focus on portability and compact footprint
MIT License
5.96k stars 516 forks source link

ES2015 Template Strings #273

Open kphillisjr opened 9 years ago

kphillisjr commented 9 years ago

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/ )

kphillisjr commented 9 years ago

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".

terotests commented 8 years ago

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.

ricardobeat commented 7 years ago

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.

svaarala commented 7 years ago

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 commented 7 years ago

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.

fatcerberus commented 7 years ago

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

terotests commented 7 years ago

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.

svaarala commented 7 years ago

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:

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 commented 7 years ago

@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.

fatcerberus commented 7 years ago

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. :)

svaarala commented 7 years ago

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.

terotests commented 7 years ago

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.

guoai2015 commented 3 years ago

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

panlina commented 8 months ago

Any plan to add this?

guoai2015 commented 8 months ago

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.