satyr / coco

Unfancy CoffeeScript
http://satyr.github.com/coco/
MIT License
497 stars 48 forks source link

[Idea] Inlines in the Code Generation #101

Open ceymard opened 12 years ago

ceymard commented 12 years ago

It would be great if there was a flag on the command line that would allow inlining helper functions.

Eg. In the CouchDB NodeJS driver, you can specify "views" directly in a Javascript object. Theses views are functions that are passed to .toString(), which gives their text body.

Another example ; in my template language (JinJS), I declare filters that are to be embedded into the resulting templates the same way.

The problem is that whenever I use, say, import, the __import function is defined at toplevel, therefore rendering the isolated .toString() functions useless since this part is not embedded.

So, it would be cool that a switch could allow us to generate a <<< b as

for (var key in src) {
    if ({}.hasOwnProperty.call(src, key)) {
        a[key] = b[key];
   }
}

This may be complicated, I don't know, but it would sure be useful, since I could still use coco instead of having to going back to javascript in those cases.

satyr commented 12 years ago

Eg. In the CouchDB NodeJS driver, you can specify "views" directly in a Javascript object. Theses views are functions that are passed to .toString(), which gives their text body.

I think relying on Function::toString isn't optimal in those cases. Can't you just:

"function(){#{ Coco.compile code, {+bare} }}"
ceymard commented 12 years ago

toString has its advantages, and no I can' t rely on compile at runtime since it is inefficient and has the drawback of needing a useless dependency (whereas coco is just a dev dependency right now).

Inlining is in fact the only viable approach, aside writing the code directly in javascript (yuck).

satyr commented 12 years ago

I can' t rely on compile at runtime since it is inefficient and has the drawback of needing a useless dependency

So precompiling ("function(){#{ fs.readFileSync 'lib/fun.js' }}") doesn't work?

ceymard commented 12 years ago

it is inefficient as well, since it requires execution at runtime.

I understand that they are ways to circumvent the issue, but I'm not interested in circumventing ; I really would like for it to work out-of-the-box.

If you're not against the principle of the idea in general, I could try to provide a patch if implementing it bothers you.

satyr commented 12 years ago

it is inefficient as well, since it requires execution at runtime.

Execution as in File IO, and caching isn't an option?:

js = "function(){#{ fs.readFileSync 'lib/fun.js' }}"'
...
views: js

I understand that they are ways to circumvent the issue, but I'm not interested in circumventing ; I really would like for it to work out-of-the-box.

Adding the proposed option is circumventing as well.

If you're not against the principle of the idea in general, I could try to provide a patch if implementing it bothers you.

I'm mildly against more options for producing different compilations. The patch would have to be shockingly elegant to be merged. ;)

ceymard commented 12 years ago

Adding the proposed option is circumventing as well.

I don't really get this feeling ; this option would not require me having to code differently than usual at all, just turning a switch on and off.

I'm mildly against more options for producing different compilations. The patch would have to be shockingly elegant to be merged. ;)

Understandable, I'll see what's doable if and when I have time :)

thejh commented 12 years ago

@satyr In https://github.com/satyr/coco/blob/master/src/ast.co#L2005, just inline the function if we're in the special mode.

function utility
  if <special mode>
    "(#{UTILITIES[it]})"
  else
    Scope.root.assign \__ + it, UTILITIES[it]

Apart from bloating the compiled code, any problems? :D

ceymard commented 12 years ago

Ehrm, how does it work exactly ?

thejh commented 12 years ago

@ceymard That function is used like this:

case \^ then return "#{ utility \clone }(#{ it.compile o, LEVEL_LIST })"

So, normally, utility(name) makes sure that the helper function is in the global scope and returns its name so that it can be called. My suggestion would instead just return the functions code so that it would be inlined. And yes, that's going to look really ugly in the output - but then again, that probably doesn't matter.

ceymard commented 12 years ago

Sorry, I still don't get it.

Say I have a function declared as such :

function a (arg)
    arg <<< arg

This gets compiled into

function a(arg){
  return __import(arg, arg);
}
function __import(obj, src){
  var own = {}.hasOwnProperty;
  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
  return obj;
}

What do I do so that the a() function either contains __import, or has a version of __import inlined directly into it with your proposal ?

thejh commented 12 years ago

The output would look like this:

function a(arg){
  return (function __import(obj, src){
    var own = {}.hasOwnProperty;
    for (var key in src) if (own.call(src, key)) obj[key] = src[key];
    return obj;
  })(arg, arg);
}
thejh commented 12 years ago

Uh, if that's your question, I didn't think about how to turn that on/off.

ceymard commented 12 years ago

Yes that was the question :)

@satyr : why not add some kind of keyword to the functions to tag them as inlined or selfcontained, or some other keyword ? Having it would then trigger thejh's compilation. Thoughts ?

satyr commented 12 years ago

why not add some kind of keyword to the functions to tag them as inlined or selfcontained, or some other keyword ?

You mean something like a inlined <<< b? Looks horrible.

thejh commented 12 years ago

@satyr He said "the functions", not "the operators". How about this?

(foo, bar) inlined ->
  a <<< b
satyr commented 12 years ago

(foo, bar) inlined ->

Horrible too.

Note that I'm not yet convinced about the use cases for this proposal.

thejh commented 12 years ago

I really understand the couch usecase. When I have a bunch of small map functions, I don't want one file for each. So, I have to manually read in the file, chop it in seperate methods and run them through coco, right?

ceymard commented 12 years ago

@thejh that's exactly the use case I wanted to solve.

And yes, I meant something linke function(whatever) inlined or inline function(whatever), except inline doesn't seem to be accurate.

More like standalone function thefunc(args) ... or function thefunc(args) standalone, which makes their body not call anything from outer scope that isn't explicitely called inside.

As you said, it is not a very common use case, but a very annoying issue to deal with, which is why I think the ugliness of this construct is largely outweighted by its usefulness and rareness (ie you won't find it too often in your code, but when you do, you'll be happy it's there, however ugly you think it is :) ).

satyr commented 12 years ago

So, I have to manually read in the file, chop it in seperate methods and run them through coco, right?

You should be doing that as that's what the Couch API expects--a JS code that's executed in a separate environment. Like I said above, Function::toString is unreliable.

thejh commented 12 years ago

@ceymard Time to build an inlining postprocessor for JS! :D

If you're interested, you might want to use https://github.com/thejh/node-astjourney for that - it can read the JS AST into a nice structure, you can walk and modify it, and finally, you can write it out. Actually, I'd appreciate having someone use my libs. :D

ceymard commented 12 years ago

Function::toString is unreliable in javascript preprocessors, like coco, because of the afore mentionned problem (creating objects in outer scope).

It is, however very used in many node.js projects, and is reliable enough to actually be used. It would be nice we could use it too in coco.

thejh commented 12 years ago

@ceymard Actually, wouldn't a preprocessor that can also inline your own helpers be better?

ceymard commented 12 years ago

@thejh coco is already a preprocessor, I don't see the point of multiplying the number of external tools I need to build my code.

About using your project, I try to stay clear from ASTs when I don't need them, and here I don't think I do :)

thejh commented 12 years ago

@ceymard But you could argue that although coco is a preprocesor, inlining is a completely different job.

thejh commented 12 years ago

@ceymard Quickly coded this, would it work for you? https://github.com/thejh/node-astjourney/tree/master/example