TiarkRompf / virtualization-lms-core

A Framework for Runtime Code Generation and Compiled DSLs
http://scala-lms.github.com
BSD 3-Clause "New" or "Revised" License
324 stars 91 forks source link

String interpolators for codegens #69

Closed Lewix closed 10 years ago

Lewix commented 10 years ago

The changes in this pull request add two string interpolators, gen and src, which simplify writing code generation cases. Both of the interpolators automate:

On top of this, gen calls stream.println on the strings and emitBlock on any blocks. The result is that code such as:

stream.println("val " + quote(sym) + " = { ")
emitBlock(b)
stream.println(quote(getBlockResult(b)))
stream.println("}")

Now looks like this:

gen"""val $sym = {
     |$b
     |${getBlockResult(b)}
     |}"""
vjovanov commented 10 years ago

Why do we mix src and raw? I guess src should be used in every place.

vjovanov commented 10 years ago

LGTM.

vjovanov commented 10 years ago

@tiarkrompf Can we merge this?

TiarkRompf commented 10 years ago

One thing I want to make sure is that we are not going to quadratic complexity by repeatedly copying generated strings from nested blocks. How is this handled?

It also seems emitBlock is called implicitly, but getBlockResult is still explicit. I would suggest to swap it. emitBlock is a recursive call, and I think we should keep those visible.

Lewix commented 10 years ago

No, the strings are not copied when generating nested blocks. A string such as:

// context
$block
// more context

Gets broken into a list of context parts (["// context\n", "\n// more context"]) and a list of arguments ([block]). The gen string interpolator will interleave printing the contexts and the arguments to stream, as you would expect. However if it encounters an Block[_] argument, instead of calling stream.println, it simply calls emitBlock. This way, the emitBlock method does the printing rather than gen having to copy the string produced by emitBlock into the stream.

The code where this is handled is in GenericNestedCodeGen and GenericCodeGen.

I agree it would be better to have emitBlock explicit and getBlockResult implicit, but I'm not sure if that's possible. I would need emitBlock(block) to be passed to the string evaluator by name, but I get a "no by-name parameter type allowed here" error if I try to.

TiarkRompf commented 10 years ago

Thanks for clarifying -- sounds good.

For emitBlock, maybe we can do something like this:

${nestedBlock(block}}

And have nestedBlock create a thunk, i.e. a dummy object that wraps the block, and then call emitBlock as before when we process that object later.

Lewix commented 10 years ago

Ok, how about using a case class called NestedBlock. Then the code becomes:

gen"""val $sym = {
     |${NestedBlock(b)}
     |$b
     |}"""
TiarkRompf commented 10 years ago

I have a slight preference for a method as the public API for reasons of modularity: you can override a method in a subtrait, but you cannot override a case class.

Lewix commented 10 years ago

I just noticed a missing $ sign in ArrayOps.scala due to my last commit. It should say out(i) = $blk. The timing is unfortunate, so would you like me to submit another PR? It might be simpler for you to just add the $ yourself. Sorry about this.

vjovanov commented 10 years ago

Just submit another pull request as that is the usual policy.