moved the continuations plugin into trunk. it is now part of the distributions under /plugins/continuations.jar which should make scalac load it by default. actual use however must be enabled by passing -P:continuations:enable as command line arg. supporting library code is in package scala.util.continuations and is compiled into scala-library.jar. review by rytz, cunei, odersky.

git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@21140 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
rompf 2010-03-11 16:55:38 +00:00
parent 4aa5526277
commit 8506a865b8
78 changed files with 2322 additions and 5 deletions

157
build.xml
View File

@ -370,7 +370,7 @@ LOCAL REFERENCE BUILD (LOCKER)
<touch file="${build-locker.dir}/compiler.complete" verbose="no"/>
<stopwatch name="locker.comp.timer" action="total"/>
</target>
<target name="locker.done" depends="locker.comp">
<touch file="${build-locker.dir}/all.complete" verbose="no"/>
<path id="locker.classpath">
@ -447,6 +447,7 @@ QUICK BUILD (QUICK)
<include name="library/**"/>
<include name="dbc/**"/>
<include name="actors/**"/>
<include name="continuations/**"/>
<include name="swing/**"/>
</srcfiles>
</uptodate>
@ -598,7 +599,62 @@ QUICK BUILD (QUICK)
<stopwatch name="quick.comp.timer" action="total"/>
</target>
<target name="quick.pre-scalap" depends="quick.comp">
<target name="quick.pre-plugins" depends="quick.comp" unless="quick.available">
<condition property="quick.plugins.needed">
<not><available file="${build-quick.dir}/plugins.complete"/></not>
</condition>
</target>
<target name="quick.plugins" depends="quick.pre-plugins" if="quick.plugins.needed">
<stopwatch name="quick.plugins.timer"/>
<mkdir dir="${build-quick.dir}/classes/continuations-plugin"/>
<scalacfork
destdir="${build-quick.dir}/classes/continuations-plugin"
compilerpathref="locker.classpath"
params="${scalac.args.quick}"
srcdir="${src.dir}/continuations/plugin"
jvmargs="${scalacfork.jvmargs}">
<include name="**/*.scala"/>
<compilationpath>
<pathelement location="${build-quick.dir}/classes/library"/>
<pathelement location="${build-quick.dir}/classes/compiler"/>
</compilationpath>
</scalacfork>
<copy todir="${build-quick.dir}/classes/continuations-plugin">
<fileset dir="${src.dir}/continuations/plugin">
<include name="**/*.tmpl"/>
<include name="**/*.xml"/>
<include name="**/*.js"/>
<include name="**/*.css"/>
<include name="**/*.properties"/>
<include name="**/*.swf"/>
<include name="**/*.png"/>
</fileset>
</copy>
<!-- not very nice to create jar here but needed to load plugin -->
<mkdir dir="${build-quick.dir}/plugins"/>
<jar destfile="${build-quick.dir}/plugins/continuations.jar">
<fileset dir="${build-quick.dir}/classes/continuations-plugin"/>
</jar>
<!-- might split off library part into its own ant target -->
<scalacfork
destdir="${build-quick.dir}/classes/library"
compilerpathref="locker.classpath"
params="${scalac.args.quick} -Xplugin:${build-quick.dir}/plugins/continuations.jar -Xplugin-require:continuations -P:continuations:enable"
srcdir="${src.dir}/continuations/library"
jvmargs="${scalacfork.jvmargs}">
<include name="**/*.scala"/>
<compilationpath>
<pathelement location="${build-quick.dir}/classes/library"/>
<pathelement location="${lib.dir}/forkjoin.jar"/>
</compilationpath>
</scalacfork>
<touch file="${build-quick.dir}/plugins.complete" verbose="no"/>
<stopwatch name="quick.plugins.timer" action="total"/>
</target>
<target name="quick.pre-scalap" depends="quick.plugins">
<uptodate property="quick.scalap.available" targetfile="${build-quick.dir}/scalap.complete">
<srcfiles dir="${src.dir}/scalap"/>
</uptodate>
@ -802,7 +858,21 @@ PACKED QUICK BUILD (PACK)
<copy file="${jline.jar}" toDir="${build-pack.dir}/lib"/>
</target>
<target name="pack.pre-partest" depends="pack.comp">
<target name="pack.pre-plugins" depends="pack.comp">
<uptodate
property="pack.plugins.available"
targetfile="${build-pack.dir}/plugins/continuations.jar"
srcfile="${build-quick.dir}/plugins.complete"/>
</target>
<target name="pack.plugins" depends="pack.pre-plugins" unless="pack.plugins.available">
<mkdir dir="${build-pack.dir}/plugins"/>
<jar destfile="${build-pack.dir}/plugins/continuations.jar">
<fileset dir="${build-quick.dir}/classes/continuations-plugin"/>
</jar>
</target>
<target name="pack.pre-partest" depends="pack.plugins">
<uptodate
property="pack.partest.available"
targetfile="${build-pack.dir}/lib/scala-partest.jar"
@ -1041,7 +1111,61 @@ BOOTSTRAPPING BUILD (STRAP)
<stopwatch name="strap.comp.timer" action="total"/>
</target>
<target name="strap.pre-scalap" depends="strap.comp">
<target name="strap.pre-plugins" depends="strap.comp" unless="strap.available">
<condition property="strap.plugins.needed">
<not><available file="${build-strap.dir}/plugins.complete"/></not>
</condition>
</target>
<target name="strap.plugins" depends="strap.pre-plugins" if="strap.plugins.needed">
<stopwatch name="strap.plugins.timer"/>
<mkdir dir="${build-strap.dir}/classes/continuations-plugin"/>
<scalacfork
destdir="${build-strap.dir}/classes/continuations-plugin"
compilerpathref="pack.classpath"
params="${scalac.args.quick}"
srcdir="${src.dir}/continuations/plugin"
jvmargs="${scalacfork.jvmargs}">
<include name="**/*.scala"/>
<compilationpath>
<pathelement location="${build-strap.dir}/classes/library"/>
<pathelement location="${build-strap.dir}/classes/compiler"/>
</compilationpath>
</scalacfork>
<copy todir="${build-strap.dir}/classes/continuations-plugin">
<fileset dir="${src.dir}/continuations/plugin">
<include name="**/*.tmpl"/>
<include name="**/*.xml"/>
<include name="**/*.js"/>
<include name="**/*.css"/>
<include name="**/*.properties"/>
<include name="**/*.swf"/>
<include name="**/*.png"/>
</fileset>
</copy>
<!-- not very nice to create jar here but needed to load plugin -->
<mkdir dir="${build-strap.dir}/plugins"/>
<jar destfile="${build-strap.dir}/plugins/continuations.jar">
<fileset dir="${build-strap.dir}/classes/continuations-plugin"/>
</jar>
<!-- might split off library part into its own ant target -->
<scalacfork
destdir="${build-strap.dir}/classes/library"
compilerpathref="pack.classpath"
params="${scalac.args.quick} -Xplugin:${build-strap.dir}/plugins/continuations.jar -Xplugin-require:continuations -P:continuations:enable"
srcdir="${src.dir}/continuations/library"
jvmargs="${scalacfork.jvmargs}">
<include name="**/*.scala"/>
<compilationpath>
<pathelement location="${build-strap.dir}/classes/library"/>
<pathelement location="${lib.dir}/forkjoin.jar"/>
</compilationpath>
</scalacfork>
<touch file="${build-strap.dir}/plugins.complete" verbose="no"/>
<stopwatch name="strap.plugins.timer" action="total"/>
</target>
<target name="strap.pre-scalap" depends="strap.plugins">
<uptodate property="strap.scalap.available" targetfile="${build-strap.dir}/scalap.complete">
<srcfiles dir="${src.dir}/scalap"/>
</uptodate>
@ -1283,6 +1407,7 @@ DOCUMENTATION
classpathref="pack.classpath">
<src>
<files includes="${src.dir}/actors"/>
<files includes="${src.dir}/actors"/> <!-- why twice ?? -->
<files includes="${src.dir}/library/scala"/>
<files includes="${src.dir}/swing"/>
</src>
@ -1390,6 +1515,7 @@ BOOTRAPING TEST AND TEST SUITE
<exclude name="**/*.properties"/>
<exclude name="bin/**"/>
<exclude name="*.complete"/>
<exclude name="plugins/*.jar"/>
</same>
</target>
@ -1440,7 +1566,24 @@ BOOTRAPING TEST AND TEST SUITE
</partest>
</target>
<target name="test.done" depends="test.suite, test.stability"/>
<target name="test.continuations.suite" depends="pack.done">
<property name="partest.srcdir" value="files" />
<partest showlog="yes" erroronfailed="yes" javacmd="${java.home}/bin/java"
timeout="2400000" javaccmd="${javac.cmd}"
srcdir="${partest.srcdir}"
scalacopts="${scalac.args.optimise} -Xplugin:${build-pack.dir}/plugins/continuations.jar -Xplugin-require:continuations -P:continuations:enable">
<compilationpath>
<path refid="pack.classpath"/>
<fileset dir="${partest.dir}/files/lib" includes="*.jar" />
</compilationpath>
<negtests dir="${partest.dir}/${partest.srcdir}/continuations-neg" includes="*.scala"/>
<runtests dir="${partest.dir}/${partest.srcdir}">
<include name="continuations-run/**/*.scala"/>
</runtests>
</partest>
</target>
<target name="test.done" depends="test.suite, test.continuations.suite, test.stability"/>
<!-- ===========================================================================
DISTRIBUTION
@ -1468,6 +1611,10 @@ DISTRIBUTION
<copy toDir="${dist.dir}/etc">
<fileset dir="${build-pack.dir}/etc"/>
</copy>
<mkdir dir="${dist.dir}/plugins"/>
<copy toDir="${dist.dir}/plugins">
<fileset dir="${build-pack.dir}/plugins"/>
</copy>
</target>
<target name="dist.doc" depends="dist.base">

View File

@ -0,0 +1,65 @@
// $Id$
// TODO: scaladoc
package scala.util
package object continuations {
type cps[A] = cpsParam[A,A]
type suspendable = cps[Unit]
def shift[A,B,C](fun: (A => B) => C): A @cpsParam[B,C] = {
throw new NoSuchMethodException("this code has to be compiled with the Scala continuations plugin enabled")
}
def reset[A,C](ctx: =>(A @cpsParam[A,C])): C = {
val ctxR = reify[A,A,C](ctx)
if (ctxR.isTrivial)
ctxR.getTrivialValue.asInstanceOf[C]
else
ctxR.foreach((x:A) => x)
}
def reset0[A](ctx: =>(A @cpsParam[A,A])): A = reset(ctx)
def run[A](ctx: =>(Any @cpsParam[Unit,A])): A = {
val ctxR = reify[Any,Unit,A](ctx)
if (ctxR.isTrivial)
ctxR.getTrivialValue.asInstanceOf[A]
else
ctxR.foreach((x:Any) => ())
}
// methods below are primarily implementation details and are not
// needed frequently in client code
def shiftUnit0[A,B](x: A): A @cpsParam[B,B] = {
shiftUnit[A,B,B](x)
}
def shiftUnit[A,B,C>:B](x: A): A @cpsParam[B,C] = {
throw new NoSuchMethodException("this code has to be compiled with the Scala continuations plugin enabled")
}
def reify[A,B,C](ctx: =>(A @cpsParam[B,C])): ControlContext[A,B,C] = {
throw new NoSuchMethodException("this code has to be compiled with the Scala continuations plugin enabled")
}
def shiftUnitR[A,B](x: A): ControlContext[A,B,B] = {
new ControlContext(null, x)
}
def shiftR[A,B,C](fun: (A => B) => C): ControlContext[A,B,C] = {
new ControlContext(fun, null.asInstanceOf[A])
}
def reifyR[A,B,C](ctx: => ControlContext[A,B,C]): ControlContext[A,B,C] = {
ctx
}
}

View File

@ -0,0 +1,448 @@
// $Id$
package scala.tools.selectivecps
import scala.tools.nsc.Global
import scala.collection.mutable.{Map, HashMap}
import java.io.{StringWriter, PrintWriter}
abstract class CPSAnnotationChecker extends CPSUtils {
val global: Global
import global._
import definitions._
//override val verbose = true
/**
* Checks whether @cps annotations conform
*/
object checker extends AnnotationChecker {
/** Check annotations to decide whether tpe1 <:< tpe2 */
def annotationsConform(tpe1: Type, tpe2: Type): Boolean = {
if (!cpsEnabled) return true
vprintln("check annotations: " + tpe1 + " <:< " + tpe2)
// Nothing is least element, but Any is not the greatest
if (tpe1.typeSymbol eq NothingClass)
return true
val annots1 = filterAttribs(tpe1,MarkerCPSTypes)
val annots2 = filterAttribs(tpe2,MarkerCPSTypes)
// @plus and @minus should only occur at the left, and never together
// TODO: insert check
val adaptPlusAnnots1 = filterAttribs(tpe1,MarkerCPSAdaptPlus)
val adaptMinusAnnots1 = filterAttribs(tpe1,MarkerCPSAdaptMinus)
// @minus @cps is the same as no annotations
if (!adaptMinusAnnots1.isEmpty)
return annots2.isEmpty
// to handle answer type modification, we must make @plus <:< @cps
if (!adaptPlusAnnots1.isEmpty && annots1.isEmpty)
return true
// @plus @cps will fall through and compare the @cps type args
// @cps parameters must match exactly
if ((annots1 corresponds annots2) { _.atp <:< _.atp })
return true
/*
hack no longer needed since introduction of adaptBoundsToAnnotations!
// special treatment of type parameter bounds
if ((tpe2.typeSymbol eq AnyClass)) {
// This is an ugly hack to allow instantiating Functions with an @cps
// return type. A better way would be to make sure everything goes through adapt,
// but that's a bit of work. Alternatively, an explicit hook could be added in
// Inferencer.checkBounds
val w = new StringWriter()
new Exception().printStackTrace(new PrintWriter(w, true))
if (w.toString.contains("scala.tools.nsc.typechecker.Infer$Inferencer.checkBounds")) {
vprintln("Testing whether " + tpe1 + " <:< " + tpe2 + ". We're inside Inferencer.checkBounds, so we just return true.")
return true
}
}
*/
false
}
/** Refine the computed least upper bound of a list of types.
* All this should do is add annotations. */
override def annotationsLub(tpe: Type, ts: List[Type]): Type = {
if (!cpsEnabled) return tpe
val annots1 = filterAttribs(tpe, MarkerCPSTypes)
val annots2 = ts flatMap (filterAttribs(_, MarkerCPSTypes))
if (annots2.nonEmpty) {
val cpsLub = AnnotationInfo(global.lub(annots1:::annots2 map (_.atp)), Nil, Nil)
val tpe1 = if (annots1.nonEmpty) removeAttribs(tpe, MarkerCPSTypes) else tpe
tpe1.withAnnotation(cpsLub)
} else tpe
}
/** Refine the bounds on type parameters to the given type arguments. */
override def adaptBoundsToAnnotations(bounds: List[TypeBounds], tparams: List[Symbol], targs: List[Type]): List[TypeBounds] = {
if (!cpsEnabled) return bounds
val anyAtCPS = AnnotationInfo(appliedType(MarkerCPSTypes.tpe, List(NothingClass.tpe, AnyClass.tpe)), Nil, Nil)
if (isFunctionType(tparams.head.owner.tpe) || tparams.head.owner == PartialFunctionClass) {
vprintln("function bound: " + tparams.head.owner.tpe + "/"+bounds+"/"+targs)
if (targs.last.hasAnnotation(MarkerCPSTypes))
bounds.reverse match {
case res::b if !res.hi.hasAnnotation(MarkerCPSTypes) =>
(TypeBounds(res.lo, res.hi.withAnnotation(anyAtCPS))::b).reverse
case _ => bounds
}
else
bounds
} else if (tparams.head.owner == ByNameParamClass) {
vprintln("byname bound: " + tparams.head.owner.tpe + "/"+bounds+"/"+targs)
if (targs.head.hasAnnotation(MarkerCPSTypes) && !bounds.head.hi.hasAnnotation(MarkerCPSTypes))
TypeBounds(bounds.head.lo, bounds.head.hi.withAnnotation(anyAtCPS))::Nil
else bounds
} else
bounds
}
override def canAdaptAnnotations(tree: Tree, mode: Int, pt: Type): Boolean = {
if (!cpsEnabled) return false
vprintln("can adapt annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
val annots1 = filterAttribs(tree.tpe,MarkerCPSTypes)
val annots2 = filterAttribs(pt,MarkerCPSTypes)
if ((mode & global.analyzer.PATTERNmode) != 0) {
//println("can adapt pattern annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
if (!annots1.isEmpty) {
return true
}
}
/*
//not precise enough
if ((mode & global.analyzer.TYPEmode) != 0 && (mode & global.analyzer.BYVALmode) != 0) {
//println("can adapt pattern annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
if (!annots1.isEmpty) {
return true
}
}
*/
if ((mode & global.analyzer.EXPRmode) == 0) {
vprintln("only handling EXPRmode")
return false
}
/*
this interferes with overloading resolution
if ((mode & global.analyzer.BYVALmode) != 0 && tree.tpe <:< pt) {
vprintln("already compatible, can't adapt further")
return false
}
*/
if ((mode & global.analyzer.EXPRmode) != 0) {
if ((annots1 corresponds annots2) { case (a1,a2) => a1.atp <:< a2.atp }) {
vprintln("already same, can't adapt further")
return false
}
if (annots1.isEmpty && !annots2.isEmpty && ((mode & global.analyzer.BYVALmode) == 0)) {
//println("can adapt annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
val adapt = AnnotationInfo(MarkerCPSAdaptPlus.tpe, Nil, Nil)
if (!tree.tpe.annotations.contains(adapt)) {
// val base = tree.tpe <:< removeAllCPSAnnotations(pt)
// val known = global.analyzer.isFullyDefined(pt)
// println(same + "/" + base + "/" + known)
val same = true//annots2 forall { case AnnotationInfo(atp: TypeRef, _, _) => atp.typeArgs(0) =:= atp.typeArgs(1) }
// TBD: use same or not?
if (same) {
vprintln("yes we can!! (unit)")
return true
}
}
} else if (!annots1.isEmpty && ((mode & global.analyzer.BYVALmode) != 0)) {
val adapt = AnnotationInfo(MarkerCPSAdaptMinus.tpe, Nil, Nil)
if (!tree.tpe.annotations.contains(adapt)) {
vprintln("yes we can!! (byval)")
return true
}
}
}
false
}
override def adaptAnnotations(tree: Tree, mode: Int, pt: Type): Tree = {
if (!cpsEnabled) return tree
// FIXME: there seems to be a problem when mode == 1 (expr, no poly) and
// there are wildcards inside an annotation (which we don't resolve yet)
// can we just instantiate things? <--- need to check this is still valid
vprintln("adapt annotations " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
val annots1 = filterAttribs(tree.tpe,MarkerCPSTypes)
val annots2 = filterAttribs(pt,MarkerCPSTypes)
if ((mode & global.analyzer.PATTERNmode) != 0) {
if (!annots1.isEmpty) {
return tree.setType(removeAllCPSAnnotations(tree.tpe))
}
}
/*
// doesn't work correctly -- still relying on addAnnotations to remove things from ValDef symbols
if ((mode & global.analyzer.TYPEmode) != 0 && (mode & global.analyzer.BYVALmode) != 0) {
if (!annots1.isEmpty) {
println("removing annotation from " + tree + "/" + tree.tpe)
val s = tree.setType(removeAllCPSAnnotations(tree.tpe))
println(s)
s
}
}
*/
if ((mode & global.analyzer.EXPRmode) != 0) {
if (annots1.isEmpty && !annots2.isEmpty && ((mode & global.analyzer.BYVALmode) == 0)) { // shiftUnit
// add a marker annotation that will make tree.tpe behave as pt, subtyping wise
// tree will look like having any possible annotation
//println("adapt annotations " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
val adapt = AnnotationInfo(MarkerCPSAdaptPlus.tpe, Nil, Nil)
val same = true//annots2 forall { case AnnotationInfo(atp: TypeRef, _, _) => atp.typeArgs(0) =:= atp.typeArgs(1) }
// TBD: use same or not? see infer0.scala/infer1.scala
// CAVEAT:
// for monomorphic answer types we want to have @plus @cps (for better checking)
// for answer type modification we want to have only @plus (because actual answer type may differ from pt)
// if (tree.tpe <:< removeAllCPSAnnotations(pt)) {
//val known = global.analyzer.isFullyDefined(pt)
if (same && !tree.tpe.annotations.contains(adapt)) {
if (true /*known*/)
return tree.setType(tree.tpe.withAnnotations(adapt::annots2)) // needed for #1807
else
return tree.setType(tree.tpe.withAnnotations(adapt::Nil))
}
tree
} else if (!annots1.isEmpty && ((mode & global.analyzer.BYVALmode) != 0)) { // dropping annotation
// add a marker annotation that will make tree.tpe behave as pt, subtyping wise
// tree will look like having no annotation
if (!tree.tpe.hasAnnotation(MarkerCPSAdaptMinus)) {
val adapt = AnnotationInfo(MarkerCPSAdaptMinus.tpe, Nil, Nil)
return tree.setType(tree.tpe.withAnnotations(adapt::Nil))
}
}
}
tree
}
def updateAttributesFromChildren(tpe: Type, childAnnots: List[AnnotationInfo], byName: List[Tree]): Type = {
tpe match {
// Need to push annots into each alternative of overloaded type
// But we can't, since alternatives aren't types but symbols, which we
// can't change (we'd be affecting symbols globally)
/*
case OverloadedType(pre, alts) =>
OverloadedType(pre, alts.map((sym: Symbol) => updateAttributes(pre.memberType(sym), annots)))
*/
case _ =>
assert(childAnnots forall (_.atp.typeSymbol == MarkerCPSTypes), childAnnots)
/*
[] + [] = []
plus + [] = plus
cps + [] = cps
plus cps + [] = plus cps
minus cps + [] = minus cp
synth cps + [] = synth cps // <- synth on left - does it happen?
[] + cps = cps
plus + cps = synth cps
cps + cps = cps! <- lin
plus cps + cps = synth cps! <- unify
minus cps + cps = minus cps! <- lin
synth cps + cps = synth cps! <- unify
*/
val plus = tpe.hasAnnotation(MarkerCPSAdaptPlus) || (tpe.hasAnnotation(MarkerCPSTypes) &&
byName.nonEmpty && byName.forall(_.tpe.hasAnnotation(MarkerCPSAdaptPlus)))
// move @plus annotations outward from by-name children
if (childAnnots.isEmpty) {
if (plus) { // @plus or @plus @cps
for (t <- byName) {
//println("removeAnnotation " + t + " / " + t.tpe)
t.setType(removeAttribs(t.tpe, MarkerCPSAdaptPlus, MarkerCPSTypes))
}
return tpe.withAnnotation(AnnotationInfo(MarkerCPSAdaptPlus.tpe, Nil, Nil))
} else
return tpe
}
val annots1 = filterAttribs(tpe, MarkerCPSTypes)
if (annots1.isEmpty) { // nothing or @plus
val synth = MarkerCPSSynth.tpe
val annots2 = List(linearize(childAnnots))
removeAttribs(tpe,MarkerCPSAdaptPlus).withAnnotations(AnnotationInfo(synth, Nil, Nil)::annots2)
} else {
val annot1 = single(annots1)
if (plus) { // @plus @cps
val synth = AnnotationInfo(MarkerCPSSynth.tpe, Nil, Nil)
val annot2 = linearize(childAnnots)
if (!(annot2.atp <:< annot1.atp))
throw new TypeError(annot2 + " is not a subtype of " + annot1)
val res = removeAttribs(tpe, MarkerCPSAdaptPlus, MarkerCPSTypes).withAnnotations(List(synth, annot2))
for (t <- byName) {
//println("removeAnnotation " + t + " / " + t.tpe)
t.setType(removeAttribs(t.tpe, MarkerCPSAdaptPlus, MarkerCPSTypes))
}
res
} else if (tpe.hasAnnotation(MarkerCPSSynth)) { // @synth @cps
val annot2 = linearize(childAnnots)
if (!(annot2.atp <:< annot1.atp))
throw new TypeError(annot2 + " is not a subtype of " + annot1)
removeAttribs(tpe, MarkerCPSTypes).withAnnotation(annot2)
} else { // @cps
removeAttribs(tpe, MarkerCPSTypes).withAnnotation(linearize(childAnnots:::annots1))
}
}
}
}
def transArgList(fun: Tree, args: List[Tree]): List[List[Tree]] = {
val formals = fun.tpe.paramTypes
val overshoot = args.length - formals.length
for ((a,tp) <- args.zip(formals ::: List.fill(overshoot)(NoType))) yield {
tp match {
case TypeRef(_, sym, List(elemtp)) if sym == ByNameParamClass =>
Nil // TODO: check conformance??
case _ =>
List(a)
}
}
}
def transStms(stms: List[Tree]): List[Tree] = stms match {
case ValDef(mods, name, tpt, rhs)::xs =>
rhs::transStms(xs)
case Assign(lhs, rhs)::xs =>
rhs::transStms(xs)
case x::xs =>
x::transStms(xs)
case Nil =>
Nil
}
def single(xs: List[AnnotationInfo]) = xs match {
case List(x) => x
case _ =>
global.error("not a single cps annotation: " + xs)// FIXME: error message
xs(0)
}
def transChildrenInOrder(tree: Tree, tpe: Type, childTrees: List[Tree], byName: List[Tree]) = {
val children = childTrees.flatMap { t =>
if (t.tpe eq null) Nil else {
val types = filterAttribs(t.tpe, MarkerCPSTypes)
// TODO: check that it has been adapted and if so correctly
if (types.isEmpty) Nil else List(single(types))
}
}
val newtpe = updateAttributesFromChildren(tpe, children, byName)
if (!newtpe.annotations.isEmpty)
vprintln("[checker] inferred " + tree + " / " + tpe + " ===> "+ newtpe)
newtpe
}
/** Modify the type that has thus far been inferred
* for a tree. All this should do is add annotations. */
override def addAnnotations(tree: Tree, tpe: Type): Type = {
if (!cpsEnabled) return tpe
// if (tree.tpe.hasAnnotation(MarkerCPSAdaptPlus))
// println("addAnnotation " + tree + "/" + tpe)
tree match {
case Apply(fun @ Select(qual, name), args) if (fun.tpe ne null) && !fun.tpe.isErroneous =>
// HACK: With overloaded methods, fun will never get annotated. This is because
// the 'overloaded' type gets annotated, but not the alternatives (among which
// fun's type is chosen)
vprintln("[checker] checking select apply " + tree + "/" + tpe)
transChildrenInOrder(tree, tpe, qual::(transArgList(fun, args).flatten), Nil)
case Apply(fun, args) if (fun.tpe ne null) && !fun.tpe.isErroneous =>
vprintln("[checker] checking unknown apply " + tree + "/" + tpe)
transChildrenInOrder(tree, tpe, fun::(transArgList(fun, args).flatten), Nil)
case TypeApply(fun, args) =>
vprintln("[checker] checking type apply " + tree + "/" + tpe)
transChildrenInOrder(tree, tpe, List(fun), Nil)
case Select(qual, name) =>
vprintln("[checker] checking select " + tree + "/" + tpe)
// FIXME: put it back in?? (problem with test cases select.scala and Test2.scala)
// transChildrenInOrder(tree, tpe, List(qual))
tpe
case If(cond, thenp, elsep) =>
transChildrenInOrder(tree, tpe, List(cond), List(thenp, elsep))
case Match(select, cases) =>
transChildrenInOrder(tree, tpe, List(select), cases:::cases map { case CaseDef(_, _, body) => body })
case Block(stms, expr) =>
// if any stm has annotation, so does block
transChildrenInOrder(tree, tpe, transStms(stms), List(expr))
case ValDef(mods, name, tpt, rhs) =>
vprintln("[checker] checking valdef " + name + "/"+tpe+"/"+tpt+"/"+tree.symbol.tpe)
// ValDef symbols must *not* have annotations!
if (hasAnswerTypeAnn(tree.symbol.info)) { // is it okay to modify sym here?
vprintln("removing annotation from sym " + tree.symbol + "/" + tree.symbol.tpe + "/" + tpt)
tpt.setType(removeAllCPSAnnotations(tpt.tpe))
tree.symbol.setInfo(removeAllCPSAnnotations(tree.symbol.info))
}
tpe
case _ =>
tpe
}
}
}
}

View File

@ -0,0 +1,131 @@
// $Id$
package scala.tools.selectivecps
import scala.tools.nsc.Global
trait CPSUtils {
val global: Global
import global._
import definitions._
var cpsEnabled = false
val verbose: Boolean = System.getProperty("cpsVerbose", "false") == "true"
def vprintln(x: =>Any): Unit = if (verbose) println(x)
lazy val MarkerCPSSym = definitions.getClass("scala.util.continuations.cpsSym")
lazy val MarkerCPSTypes = definitions.getClass("scala.util.continuations.cpsParam")
lazy val MarkerCPSSynth = definitions.getClass("scala.util.continuations.cpsSynth")
lazy val MarkerCPSAdaptPlus = definitions.getClass("scala.util.continuations.cpsPlus")
lazy val MarkerCPSAdaptMinus = definitions.getClass("scala.util.continuations.cpsMinus")
lazy val Context = definitions.getClass("scala.util.continuations.ControlContext")
lazy val ModCPS = definitions.getModule("scala.util.continuations")
lazy val MethShiftUnit = definitions.getMember(ModCPS, "shiftUnit")
lazy val MethShiftUnitR = definitions.getMember(ModCPS, "shiftUnitR")
lazy val MethShift = definitions.getMember(ModCPS, "shift")
lazy val MethShiftR = definitions.getMember(ModCPS, "shiftR")
lazy val MethReify = definitions.getMember(ModCPS, "reify")
lazy val MethReifyR = definitions.getMember(ModCPS, "reifyR")
lazy val allCPSAnnotations = List(MarkerCPSSym, MarkerCPSTypes, MarkerCPSSynth,
MarkerCPSAdaptPlus, MarkerCPSAdaptMinus)
// annotation checker
def filterAttribs(tpe:Type, cls:Symbol) =
tpe.annotations.filter(_.atp.typeSymbol == cls)
def removeAttribs(tpe:Type, cls:Symbol*) =
tpe.withoutAnnotations.withAnnotations(tpe.annotations.filterNot(cls contains _.atp.typeSymbol))
def removeAllCPSAnnotations(tpe: Type) = removeAttribs(tpe, allCPSAnnotations:_*)
def linearize(ann: List[AnnotationInfo]): AnnotationInfo = {
ann.reduceLeft { (a, b) =>
val atp0::atp1::Nil = a.atp.normalize.typeArgs
val btp0::btp1::Nil = b.atp.normalize.typeArgs
val (u0,v0) = (atp0, atp1)
val (u1,v1) = (btp0, btp1)
/*
val (u0,v0) = (a.atp.typeArgs(0), a.atp.typeArgs(1))
val (u1,v1) = (b.atp.typeArgs(0), b.atp.typeArgs(1))
vprintln("check lin " + a + " andThen " + b)
*/
vprintln("check lin " + a + " andThen " + b)
if (!(v1 <:< u0))
throw new TypeError("illegal answer type modification: " + a + " andThen " + b)
// TODO: improve error message (but it is not very common)
AnnotationInfo(appliedType(MarkerCPSTypes.tpe, List(u1,v0)),Nil,Nil)
}
}
// anf transform
def getExternalAnswerTypeAnn(tp: Type) = {
tp.annotations.find(a => a.atp.typeSymbol == MarkerCPSTypes) match {
case Some(AnnotationInfo(atp, _, _)) =>
val atp0::atp1::Nil = atp.normalize.typeArgs
Some((atp0, atp1))
case None =>
if (tp.hasAnnotation(MarkerCPSAdaptPlus))
global.warning("trying to instantiate type " + tp + " to unknown cps type")
None
}
}
def getAnswerTypeAnn(tp: Type) = {
tp.annotations.find(a => a.atp.typeSymbol == MarkerCPSTypes) match {
case Some(AnnotationInfo(atp, _, _)) =>
if (!tp.hasAnnotation(MarkerCPSAdaptPlus)) {//&& !tp.hasAnnotation(MarkerCPSAdaptMinus))
val atp0::atp1::Nil = atp.normalize.typeArgs
Some((atp0, atp1))
} else
None
case None => None
}
}
def hasAnswerTypeAnn(tp: Type) = {
tp.hasAnnotation(MarkerCPSTypes) && !tp.hasAnnotation(MarkerCPSAdaptPlus) /*&&
!tp.hasAnnotation(MarkerCPSAdaptMinus)*/
}
def hasSynthAnn(tp: Type) = {
tp.annotations.exists(a => a.atp.typeSymbol == MarkerCPSSynth)
}
def updateSynthFlag(tree: Tree) = { // remove annotations if *we* added them (@synth present)
if (hasSynthAnn(tree.tpe)) {
log("removing annotation from " + tree)
tree.setType(removeAllCPSAnnotations(tree.tpe))
} else
tree
}
type CPSInfo = Option[(Type,Type)]
def linearize(a: CPSInfo, b: CPSInfo)(implicit unit: CompilationUnit, pos: Position): CPSInfo = {
(a,b) match {
case (Some((u0,v0)), Some((u1,v1))) =>
vprintln("check lin " + a + " andThen " + b)
if (!(v1 <:< u0)) {
unit.error(pos,"cannot change answer type in composition of cps expressions " +
"from " + u1 + " to " + v0 + " because " + v1 + " is not a subtype of " + u0 + ".")
throw new Exception("check lin " + a + " andThen " + b)
}
Some((u1,v0))
case (Some(_), _) => a
case (_, Some(_)) => b
case _ => None
}
}
// cps transform
}

View File

@ -0,0 +1,389 @@
// $Id$
package scala.tools.selectivecps
import scala.tools.nsc._
import scala.tools.nsc.transform._
import scala.tools.nsc.symtab._
import scala.tools.nsc.plugins._
import scala.tools.nsc.ast._
/**
* In methods marked @cps, explicitly name results of calls to other @cps methods
*/
abstract class SelectiveANFTransform extends PluginComponent with Transform with
TypingTransformers with CPSUtils {
// inherits abstract value `global' and class `Phase' from Transform
import global._ // the global environment
import definitions._ // standard classes and methods
import typer.atOwner // methods to type trees
/** the following two members override abstract members in Transform */
val phaseName: String = "selectiveanf"
protected def newTransformer(unit: CompilationUnit): Transformer =
new ANFTransformer(unit)
class ANFTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
implicit val _unit = unit // allow code in CPSUtils.scala to report errors
var cpsAllowed: Boolean = false // detect cps code in places we do not handle (yet)
override def transform(tree: Tree): Tree = {
if (!cpsEnabled) return tree
tree match {
// TODO: Maybe we should further generalize the transform and move it over
// to the regular Transformer facility. But then, actual and required cps
// state would need more complicated (stateful!) tracking.
// Making the default case use transExpr(tree, None, None) instead of
// calling super.transform() would be a start, but at the moment,
// this would cause infinite recursion. But we could remove the
// ValDef case here.
case dd @ DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
log("transforming " + dd.symbol)
atOwner(dd.symbol) {
val rhs1 = transExpr(rhs, None, getExternalAnswerTypeAnn(tpt.tpe))
log("result "+rhs1)
log("result is of type "+rhs1.tpe)
treeCopy.DefDef(dd, mods, name, transformTypeDefs(tparams), transformValDefss(vparamss),
transform(tpt), rhs1)
}
case ff @ Function(vparams, body) =>
log("transforming anon function " + ff.symbol)
atOwner(ff.symbol) {
val body1 = transExpr(body, None, getExternalAnswerTypeAnn(body.tpe))
log("result "+body1)
log("result is of type "+body1.tpe)
treeCopy.Function(ff, transformValDefs(vparams), body1)
}
case vd @ ValDef(mods, name, tpt, rhs) => // object-level valdefs
log("transforming valdef " + vd.symbol)
atOwner(vd.symbol) {
assert(getExternalAnswerTypeAnn(tpt.tpe) == None)
val rhs1 = transExpr(rhs, None, None)
treeCopy.ValDef(vd, mods, name, transform(tpt), rhs1)
}
case TypeTree() =>
// circumvent cpsAllowed here
super.transform(tree)
case Apply(_,_) =>
// this allows reset { ... } in object constructors
// it's kind of a hack to put it here (see note above)
transExpr(tree, None, None)
case _ =>
if (hasAnswerTypeAnn(tree.tpe)) {
if (!cpsAllowed)
unit.error(tree.pos, "cps code not allowed here / " + tree.getClass + " / " + tree)
log(tree)
}
cpsAllowed = false
super.transform(tree)
}
}
def transExpr(tree: Tree, cpsA: CPSInfo, cpsR: CPSInfo): Tree = {
transTailValue(tree, cpsA, cpsR) match {
case (Nil, b) => b
case (a, b) =>
treeCopy.Block(tree, a,b)
}
}
def transArgList(fun: Tree, args: List[Tree], cpsA: CPSInfo): (List[List[Tree]], List[Tree], CPSInfo) = {
val formals = fun.tpe.paramTypes
val overshoot = args.length - formals.length
var spc: CPSInfo = cpsA
val (stm,expr) = (for ((a,tp) <- args.zip(formals ::: List.fill(overshoot)(NoType))) yield {
tp match {
case TypeRef(_, sym, List(elemtp)) if sym == ByNameParamClass =>
(Nil, transExpr(a, None, getAnswerTypeAnn(elemtp)))
case _ =>
val (valStm, valExpr, valSpc) = transInlineValue(a, spc)
spc = valSpc
(valStm, valExpr)
}
}).unzip
(stm,expr,spc)
}
def transValue(tree: Tree, cpsA: CPSInfo, cpsR: CPSInfo): (List[Tree], Tree, CPSInfo) = {
// return value: (stms, expr, spc), where spc is CPSInfo after stms but *before* expr
implicit val pos = tree.pos
tree match {
case Block(stms, expr) =>
val (cpsA2, cpsR2) = (cpsA, linearize(cpsA, getAnswerTypeAnn(tree.tpe))) // tbd
// val (cpsA2, cpsR2) = (None, getAnswerTypeAnn(tree.tpe))
val (a, b) = transBlock(stms, expr, cpsA2, cpsR2)
val tree1 = (treeCopy.Block(tree, a, b)) // no updateSynthFlag here!!!
(Nil, tree1, cpsA)
case If(cond, thenp, elsep) =>
val (condStats, condVal, spc) = transInlineValue(cond, cpsA)
val (cpsA2, cpsR2) = (spc, linearize(spc, getAnswerTypeAnn(tree.tpe)))
// val (cpsA2, cpsR2) = (None, getAnswerTypeAnn(tree.tpe))
val thenVal = transExpr(thenp, cpsA2, cpsR2)
val elseVal = transExpr(elsep, cpsA2, cpsR2)
// check that then and else parts agree (not necessary any more, but left as sanity check)
if (cpsR.isDefined) {
if (elsep == EmptyTree)
unit.error(tree.pos, "always need else part in cps code")
}
if (hasAnswerTypeAnn(thenVal.tpe) != hasAnswerTypeAnn(elseVal.tpe)) {
unit.error(tree.pos, "then and else parts must both be cps code or neither of them")
}
(condStats, updateSynthFlag(treeCopy.If(tree, condVal, thenVal, elseVal)), spc)
case Match(selector, cases) =>
val (selStats, selVal, spc) = transInlineValue(selector, cpsA)
val (cpsA2, cpsR2) = (spc, linearize(spc, getAnswerTypeAnn(tree.tpe)))
// val (cpsA2, cpsR2) = (None, getAnswerTypeAnn(tree.tpe))
val caseVals = for {
cd @ CaseDef(pat, guard, body) <- cases
val bodyVal = transExpr(body, cpsA2, cpsR2)
} yield {
treeCopy.CaseDef(cd, transform(pat), transform(guard), bodyVal)
}
(selStats, updateSynthFlag(treeCopy.Match(tree, selVal, caseVals)), spc)
case ldef @ LabelDef(name, params, rhs) =>
if (hasAnswerTypeAnn(tree.tpe)) {
val sym = currentOwner.newMethod(tree.pos, name)//unit.fresh.newName(tree.pos, "myloopvar"))
.setInfo(ldef.symbol.info)
.setFlag(Flags.SYNTHETIC)
val subst = new TreeSymSubstituter(List(ldef.symbol), List(sym))
val rhsVal = transExpr(subst(rhs), None, getAnswerTypeAnn(tree.tpe))
val stm1 = localTyper.typed(DefDef(sym, rhsVal))
val expr = localTyper.typed(Apply(Ident(sym), List()))
(List(stm1), expr, cpsA)
} else {
val rhsVal = transExpr(rhs, None, None)
(Nil, updateSynthFlag(treeCopy.LabelDef(tree, name, params, rhsVal)), cpsA)
}
case Try(block, catches, finalizer) =>
// no cps code allowed in try/catch/finally!
val blockVal = transExpr(block, None, None)
val catchVals = for {
cd @ CaseDef(pat, guard, body) <- catches
val bodyVal = transExpr(body, None, None)
} yield {
treeCopy.CaseDef(cd, transform(pat), transform(guard), bodyVal)
}
val finallyVal = transExpr(finalizer, None, None)
(Nil, updateSynthFlag(treeCopy.Try(tree, blockVal, catchVals, finallyVal)), cpsA)
case Assign(lhs, rhs) =>
// allow cps code in rhs only
val (stms, expr, spc) = transInlineValue(rhs, cpsA)
(stms, updateSynthFlag(treeCopy.Assign(tree, transform(lhs), expr)), spc)
case Return(expr0) =>
val (stms, expr, spc) = transInlineValue(expr0, cpsA)
(stms, updateSynthFlag(treeCopy.Return(tree, expr)), spc)
case Throw(expr0) =>
val (stms, expr, spc) = transInlineValue(expr0, cpsA)
(stms, updateSynthFlag(treeCopy.Throw(tree, expr)), spc)
case Typed(expr0, tpt) =>
// TODO: should x: A @cps[B,C] have a special meaning?
val (stms, expr, spc) = transInlineValue(expr0, cpsA)
val tpt1 = treeCopy.TypeTree(tpt).setType(removeAllCPSAnnotations(tpt.tpe))
(stms, treeCopy.Typed(tree, expr, tpt1).setType(removeAllCPSAnnotations(tree.tpe)), spc)
case TypeApply(fun, args) =>
val (stms, expr, spc) = transInlineValue(fun, cpsA)
(stms, updateSynthFlag(treeCopy.TypeApply(tree, expr, args)), spc)
case Select(qual, name) =>
val (stms, expr, spc) = transInlineValue(qual, cpsA)
(stms, updateSynthFlag(treeCopy.Select(tree, expr, name)), spc)
case Apply(fun, args) =>
val (funStm, funExpr, funSpc) = transInlineValue(fun, cpsA)
val (argStm, argExpr, argSpc) = transArgList(fun, args, funSpc)
(funStm ::: (argStm.flatten), updateSynthFlag(treeCopy.Apply(tree, funExpr, argExpr)),
argSpc)
case _ =>
cpsAllowed = true
(Nil, transform(tree), cpsA)
}
}
def transTailValue(tree: Tree, cpsA: CPSInfo, cpsR: CPSInfo): (List[Tree], Tree) = {
val (stms, expr, spc) = transValue(tree, cpsA, cpsR)
val bot = linearize(spc, getAnswerTypeAnn(expr.tpe))(unit, tree.pos)
val plainTpe = removeAllCPSAnnotations(expr.tpe)
if (cpsR.isDefined && !bot.isDefined) {
if (!expr.isEmpty && (expr.tpe.typeSymbol ne NothingClass)) {
// must convert!
log("cps type conversion (has: " + cpsA + "/" + spc + "/" + expr.tpe + ")")
log("cps type conversion (expected: " + cpsR.get + "): " + expr)
if (!expr.tpe.hasAnnotation(MarkerCPSAdaptPlus))
unit.warning(tree.pos, "expression " + tree + " is cps-transformed unexpectedly")
try {
val Some((a, b)) = cpsR
val res = localTyper.typed(atPos(tree.pos) {
Apply(TypeApply(gen.mkAttributedRef(MethShiftUnit),
List(TypeTree(plainTpe), TypeTree(a), TypeTree(b))),
List(expr))
})
return (stms, res)
} catch {
case ex:TypeError =>
unit.error(ex.pos, "cannot cps-transform expression " + tree + ": " + ex.msg)
}
}
} else if (!cpsR.isDefined && bot.isDefined) {
// error!
log("cps type error: " + expr)
println("cps type error: " + expr + "/" + expr.tpe + "/" + getAnswerTypeAnn(expr.tpe))
println(cpsR + "/" + spc + "/" + bot)
unit.error(tree.pos, "found cps expression in non-cps position")
} else {
// all is well
if (expr.tpe.hasAnnotation(MarkerCPSAdaptPlus)) {
unit.warning(tree.pos, "expression " + expr + "/" + expr.tpe + " should not have cps-plus annotation")
expr.setType(removeAllCPSAnnotations(expr.tpe))
}
// TODO: sanity check that types agree
}
(stms, expr)
}
def transInlineValue(tree: Tree, cpsA: CPSInfo): (List[Tree], Tree, CPSInfo) = {
val (stms, expr, spc) = transValue(tree, cpsA, None) // never required to be cps
getAnswerTypeAnn(expr.tpe) match {
case spcVal @ Some(_) =>
val valueTpe = removeAllCPSAnnotations(expr.tpe)
val sym = currentOwner.newValue(tree.pos, unit.fresh.newName(tree.pos, "tmp"))
.setInfo(valueTpe)
.setFlag(Flags.SYNTHETIC)
.setAnnotations(List(AnnotationInfo(MarkerCPSSym.tpe, Nil, Nil)))
(stms ::: List(ValDef(sym, expr) setType(NoType)),
Ident(sym) setType(valueTpe) setPos(tree.pos), linearize(spc, spcVal)(unit, tree.pos))
case _ =>
(stms, expr, spc)
}
}
def transInlineStm(stm: Tree, cpsA: CPSInfo): (List[Tree], CPSInfo) = {
stm match {
// TODO: what about DefDefs?
// TODO: relation to top-level val def?
// TODO: what about lazy vals?
case tree @ ValDef(mods, name, tpt, rhs) =>
val (stms, anfRhs, spc) = atOwner(tree.symbol) { transValue(rhs, cpsA, None) }
val tv = new ChangeOwnerTraverser(tree.symbol, currentOwner)
stms.foreach(tv.traverse(_))
// TODO: symbol might already have annotation. Should check conformance
// TODO: better yet: do without annotations on symbols
val spcVal = getAnswerTypeAnn(anfRhs.tpe)
if (spcVal.isDefined) {
tree.symbol.setAnnotations(List(AnnotationInfo(MarkerCPSSym.tpe, Nil, Nil)))
}
(stms:::List(treeCopy.ValDef(tree, mods, name, tpt, anfRhs)), linearize(spc, spcVal)(unit, tree.pos))
case _ =>
val (headStms, headExpr, headSpc) = transInlineValue(stm, cpsA)
val valSpc = getAnswerTypeAnn(headExpr.tpe)
(headStms:::List(headExpr), linearize(headSpc, valSpc)(unit, stm.pos))
}
}
def transBlock(stms: List[Tree], expr: Tree, cpsA: CPSInfo, cpsR: CPSInfo): (List[Tree], Tree) = {
stms match {
case Nil =>
transTailValue(expr, cpsA, cpsR)
case stm::rest =>
var (rest2, expr2) = (rest, expr)
val (headStms, headSpc) = transInlineStm(stm, cpsA)
val (restStms, restExpr) = transBlock(rest2, expr2, headSpc, cpsR)
(headStms:::restStms, restExpr)
}
}
}
}

View File

@ -0,0 +1,61 @@
// $Id$
package scala.tools.selectivecps
import scala.tools.nsc
import scala.tools.nsc.typechecker._
import nsc.Global
import nsc.Phase
import nsc.plugins.Plugin
import nsc.plugins.PluginComponent
class SelectiveCPSPlugin(val global: Global) extends Plugin {
import global._
val name = "continuations"
val description = "applies selective cps conversion"
val anfPhase = new SelectiveANFTransform() {
val global = SelectiveCPSPlugin.this.global
val runsAfter = List("pickler")
}
val cpsPhase = new SelectiveCPSTransform() {
val global = SelectiveCPSPlugin.this.global
val runsAfter = List("selectiveanf")
}
val components = List[PluginComponent](anfPhase, cpsPhase)
val checker = new CPSAnnotationChecker {
val global: SelectiveCPSPlugin.this.global.type = SelectiveCPSPlugin.this.global
}
global.addAnnotationChecker(checker.checker)
def setEnabled(flag: Boolean) = {
checker.cpsEnabled = flag
anfPhase.cpsEnabled = flag
cpsPhase.cpsEnabled = flag
}
// TODO: require -enabled command-line flag
override def processOptions(options: List[String], error: String => Unit) = {
var enabled = false
for (option <- options) {
if (option == "enable") {
enabled = true
} else {
error("Option not understood: "+option)
}
}
setEnabled(enabled)
}
override val optionsHelp: Option[String] =
Some(" -P:continuations:enable Enable continuations")
// " -sourcepath <path> Specify where to find input source files"
}

View File

@ -0,0 +1,250 @@
// $Id$
package scala.tools.selectivecps
import scala.collection._
import scala.tools.nsc._
import scala.tools.nsc.transform._
import scala.tools.nsc.plugins._
import scala.tools.nsc.ast.TreeBrowsers
import scala.tools.nsc.ast._
/**
* In methods marked @cps, CPS-transform assignments introduced by ANF-transform phase.
*/
abstract class SelectiveCPSTransform extends PluginComponent with
InfoTransform with TypingTransformers with CPSUtils {
// inherits abstract value `global' and class `Phase' from Transform
import global._ // the global environment
import definitions._ // standard classes and methods
import typer.atOwner // methods to type trees
/** the following two members override abstract members in Transform */
val phaseName: String = "selectivecps"
protected def newTransformer(unit: CompilationUnit): Transformer =
new CPSTransformer(unit)
/** - return symbol's transformed type,
*/
def transformInfo(sym: Symbol, tp: Type): Type = {
if (!cpsEnabled) return tp
val newtp = transformCPSType(tp)
if (newtp != tp)
log("transformInfo changed type for " + sym + " to " + newtp);
if (sym == MethReifyR)
log("transformInfo (not)changed type for " + sym + " to " + newtp);
newtp
}
def transformCPSType(tp: Type): Type = { // TODO: use a TypeMap? need to handle more cases?
tp match {
case PolyType(params,res) => PolyType(params, transformCPSType(res))
case MethodType(params,res) =>
MethodType(params, transformCPSType(res))
case TypeRef(pre, sym, args) => TypeRef(pre, sym, args.map(transformCPSType(_)))
case _ =>
getExternalAnswerTypeAnn(tp) match {
case Some((res, outer)) =>
appliedType(Context.tpe, List(removeAllCPSAnnotations(tp), res, outer))
case _ =>
removeAllCPSAnnotations(tp)
}
}
}
class CPSTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
override def transform(tree: Tree): Tree = {
if (!cpsEnabled) return tree
postTransform(mainTransform(tree))
}
def postTransform(tree: Tree): Tree = {
tree.setType(transformCPSType(tree.tpe))
}
def mainTransform(tree: Tree): Tree = {
tree match {
// TODO: can we generalize this?
case Apply(TypeApply(fun, targs), args)
if (fun.symbol == MethShift) =>
log("found shift: " + tree)
atPos(tree.pos) {
val funR = gen.mkAttributedRef(MethShiftR) // TODO: correct?
log(funR.tpe)
Apply(
TypeApply(funR, targs).setType(appliedType(funR.tpe, targs.map((t:Tree) => t.tpe))),
args.map(transform(_))
).setType(transformCPSType(tree.tpe))
}
case Apply(TypeApply(fun, targs), args)
if (fun.symbol == MethShiftUnit) =>
log("found shiftUnit: " + tree)
atPos(tree.pos) {
val funR = gen.mkAttributedRef(MethShiftUnitR) // TODO: correct?
log(funR.tpe)
Apply(
TypeApply(funR, List(targs(0), targs(1))).setType(appliedType(funR.tpe,
List(targs(0).tpe, targs(1).tpe))),
args.map(transform(_))
).setType(appliedType(Context.tpe, List(targs(0).tpe,targs(1).tpe,targs(1).tpe)))
}
case Apply(TypeApply(fun, targs), args)
if (fun.symbol == MethReify) =>
log("found reify: " + tree)
atPos(tree.pos) {
val funR = gen.mkAttributedRef(MethReifyR) // TODO: correct?
log(funR.tpe)
Apply(
TypeApply(funR, targs).setType(appliedType(funR.tpe, targs.map((t:Tree) => t.tpe))),
args.map(transform(_))
).setType(transformCPSType(tree.tpe))
}
case Block(stms, expr) =>
val (stms1, expr1) = transBlock(stms, expr)
treeCopy.Block(tree, stms1, expr1)
case _ =>
super.transform(tree)
}
}
def transBlock(stms: List[Tree], expr: Tree): (List[Tree], Tree) = {
stms match {
case Nil =>
(Nil, transform(expr))
case stm::rest =>
stm match {
case vd @ ValDef(mods, name, tpt, rhs)
if (vd.symbol.hasAnnotation(MarkerCPSSym)) =>
log("found marked ValDef "+name+" of type " + vd.symbol.tpe)
val tpe = vd.symbol.tpe
val rhs1 = transform(rhs)
log("valdef symbol " + vd.symbol + " has type " + tpe)
log("right hand side " + rhs1 + " has type " + rhs1.tpe)
log("currentOwner: " + currentOwner)
log("currentMethod: " + currentMethod)
val (bodyStms, bodyExpr) = transBlock(rest, expr)
val specialCaseTrivial = bodyExpr match {
case Apply(fun, args) =>
// for now, look for explicit tail calls only.
// are there other cases that could profit from specializing on
// trivial contexts as well?
(bodyExpr.tpe.typeSymbol == Context) && (currentMethod == fun.symbol)
case _ => false
}
def applyTrivial(ctxValSym: Symbol, body: Tree) = {
new TreeSymSubstituter(List(vd.symbol), List(ctxValSym)).traverse(body)
val body2 = localTyper.typed(atPos(vd.symbol.pos) { body })
if ((body2.tpe == null) || !(body2.tpe.typeSymbol.tpe <:< Context.tpe)) {
println(body2 + "/" + body2.tpe)
unit.error(rhs.pos, "cannot compute type for CPS-transformed function result")
}
body2
}
def applyCombinatorFun(ctxR: Tree, body: Tree) = {
val arg = currentOwner.newValueParameter(ctxR.pos, name).setInfo(tpe)
new TreeSymSubstituter(List(vd.symbol), List(arg)).traverse(body)
val fun = localTyper.typed(atPos(vd.symbol.pos) { Function(List(ValDef(arg)), body) }) // types body as well
arg.owner = fun.symbol
new ChangeOwnerTraverser(currentOwner, fun.symbol).traverse(body)
log("fun.symbol: "+fun.symbol)
log("fun.symbol.owner: "+fun.symbol.owner)
log("arg.owner: "+arg.owner)
log("fun.tpe:"+fun.tpe)
log("return type of fun:"+body.tpe)
var methodName = "map"
if (body.tpe != null) {
if (body.tpe.typeSymbol.tpe <:< Context.tpe)
methodName = "flatMap"
}
else
unit.error(rhs.pos, "cannot compute type for CPS-transformed function result")
log("will use method:"+methodName)
localTyper.typed(atPos(vd.symbol.pos) {
Apply(Select(ctxR, ctxR.tpe.member(methodName)), List(fun))
})
}
try {
if (specialCaseTrivial) {
log("will optimize possible tail call: " + bodyExpr)
// val ctx = <rhs>
// if (ctx.isTrivial)
// val <lhs> = ctx.getTrivialValue; ...
// else
// ctx.flatMap { <lhs> => ... }
val ctxSym = currentOwner.newValue(vd.symbol.name + "$shift").setInfo(rhs1.tpe)
val ctxDef = localTyper.typed(ValDef(ctxSym, rhs1))
def ctxRef = localTyper.typed(Ident(ctxSym))
val argSym = currentOwner.newValue(vd.symbol.name).setInfo(tpe)
val argDef = localTyper.typed(ValDef(argSym, Select(ctxRef, ctxRef.tpe.member("getTrivialValue"))))
val switchExpr = localTyper.typed(atPos(vd.symbol.pos) {
val body2 = duplicateTree(Block(bodyStms, bodyExpr)) // dup before typing!
If(Select(ctxRef, ctxSym.tpe.member("isTrivial")),
applyTrivial(argSym, Block(argDef::bodyStms, bodyExpr)),
applyCombinatorFun(ctxRef, body2))
})
(List(ctxDef), switchExpr)
} else {
// ctx.flatMap { <lhs> => ... }
// or
// ctx.map { <lhs> => ... }
(Nil, applyCombinatorFun(rhs1, Block(bodyStms, bodyExpr)))
}
} catch {
case ex:TypeError =>
unit.error(ex.pos, ex.msg)
(bodyStms, bodyExpr)
}
case _ =>
val (a, b) = transBlock(rest, expr)
(transform(stm)::a, b)
}
}
}
}
}

View File

@ -0,0 +1,5 @@
<!-- $Id$ -->
<plugin>
<name>continuations</name>
<classname>scala.tools.selectivecps.SelectiveCPSPlugin</classname>
</plugin>

View File

@ -0,0 +1,6 @@
function0.scala:11: error: type mismatch;
found : () => Int @scala.util.continuations.cpsParam[Int,Int]
required: () => Int
val g: () => Int = f
^
one error found

View File

@ -0,0 +1,16 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Any = {
val f = () => shift { k: (Int=>Int) => k(7) }
val g: () => Int = f
println(reset(g()))
}
}

View File

@ -0,0 +1,6 @@
function2.scala:11: error: type mismatch;
found : () => Int
required: () => Int @util.continuations.package.cps[Int]
val g: () => Int @cps[Int] = f
^
one error found

View File

@ -0,0 +1,16 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Any = {
val f = () => 7
val g: () => Int @cps[Int] = f
println(reset(g()))
}
}

View File

@ -0,0 +1,6 @@
function3.scala:10: error: type mismatch;
found : Int @scala.util.continuations.cpsParam[Int,Int]
required: Int
val g: () => Int = () => shift { k: (Int=>Int) => k(7) }
^
one error found

View File

@ -0,0 +1,15 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Any = {
val g: () => Int = () => shift { k: (Int=>Int) => k(7) }
println(reset(g()))
}
}

View File

@ -0,0 +1,4 @@
infer0.scala:11: error: cannot cps-transform expression 8: type arguments [Int(8),String,Int] do not conform to method shiftUnit's type parameter bounds [A,B,C >: B]
test(8)
^
one error found

View File

@ -0,0 +1,14 @@
// $Id$
import scala.util.continuations._
object Test {
def test(x: => Int @cpsParam[String,Int]) = 7
def main(args: Array[String]): Any = {
test(8)
}
}

View File

@ -0,0 +1,4 @@
infer2.scala:14: error: illegal answer type modification: scala.util.continuations.cpsParam[String,Int] andThen scala.util.continuations.cpsParam[String,Int]
test { sym(); sym() }
^
one error found

View File

@ -0,0 +1,19 @@
// $Id$
import scala.util.continuations._
object Test {
def test(x: => Int @cpsParam[String,Int]) = 7
def sym() = shift { k: (Int => String) => 9 }
def main(args: Array[String]): Any = {
test { sym(); sym() }
}
}

View File

@ -0,0 +1,6 @@
t1929.scala:8: error: type mismatch;
found : Int @scala.util.continuations.cpsParam[String,java.lang.String] @scala.util.continuations.cpsSynth
required: Int @scala.util.continuations.cpsParam[Int,java.lang.String]
reset {
^
one error found

View File

@ -0,0 +1,17 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args : Array[String]) {
reset {
println("up")
val x = shift((k:Int=>String) => k(8) + k(2))
println("down " + x)
val y = shift((k:Int=>String) => k(3))
println("down2 " + y)
y + x
}
}
}

View File

@ -0,0 +1,6 @@
t2285.scala:9: error: type mismatch;
found : Int @scala.util.continuations.cpsParam[String,String] @scala.util.continuations.cpsSynth
required: Int @scala.util.continuations.cpsParam[Int,String]
def foo() = reset { bar(); 7 }
^
one error found

View File

@ -0,0 +1,11 @@
// $Id$
import scala.util.continuations._
object Test {
def bar() = shift { k: (String => String) => k("1") }
def foo() = reset { bar(); 7 }
}

View File

@ -0,0 +1,6 @@
t2949.scala:13: error: type mismatch;
found : Int
required: ? @scala.util.continuations.cpsParam[List[?],Any]
x * y
^
one error found

View File

@ -0,0 +1,15 @@
// $Id$
import scala.util.continuations._
object Test {
def reflect[A,B](xs : List[A]) = shift{ xs.flatMap[B, List[B]] }
def reify[A, B](x : A @cpsParam[List[A], B]) = reset{ List(x) }
def main(args: Array[String]): Unit = println(reify {
val x = reflect[Int, Int](List(1,2,3))
val y = reflect[Int, Int](List(2,4,8))
x * y
})
}

View File

@ -0,0 +1,2 @@
28
28

View File

@ -0,0 +1,23 @@
// $Id$
import scala.util.continuations._
object Test {
def m0() = {
shift((k:Int => Int) => k(k(7))) * 2
}
def m1() = {
2 * shift((k:Int => Int) => k(k(7)))
}
def main(args: Array[String]) = {
println(reset(m0()))
println(reset(m1()))
}
}

View File

@ -0,0 +1 @@
7

View File

@ -0,0 +1,16 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Any = {
val f = () => shift { k: (Int=>Int) => k(7) }
val g: () => Int @cps[Int] = f
println(reset(g()))
}
}

View File

@ -0,0 +1 @@
7

View File

@ -0,0 +1,15 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Any = {
val g: () => Int @cps[Int] = () => shift { k: (Int=>Int) => k(7) }
println(reset(g()))
}
}

View File

@ -0,0 +1 @@
7

View File

@ -0,0 +1,15 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Any = {
val g: () => Int @cps[Int] = () => 7
println(reset(g()))
}
}

View File

@ -0,0 +1,2 @@
10
9

View File

@ -0,0 +1,18 @@
// $Id$
import scala.util.continuations._
object Test {
def test(x:Int) = if (x <= 7)
shift { k: (Int=>Int) => k(k(k(x))) }
else
shift { k: (Int=>Int) => k(x) }
def main(args: Array[String]): Any = {
println(reset(1 + test(7)))
println(reset(1 + test(8)))
}
}

View File

@ -0,0 +1,4 @@
10
9
8
11

View File

@ -0,0 +1,25 @@
// $Id$
import scala.util.continuations._
object Test {
def test1(x:Int) = if (x <= 7)
shift { k: (Int=>Int) => k(k(k(x))) }
else
x
def test2(x:Int) = if (x <= 7)
x
else
shift { k: (Int=>Int) => k(k(k(x))) }
def main(args: Array[String]): Any = {
println(reset(1 + test1(7)))
println(reset(1 + test1(8)))
println(reset(1 + test2(7)))
println(reset(1 + test2(8)))
}
}

View File

@ -0,0 +1,4 @@
abort
()
alive
()

View File

@ -0,0 +1,16 @@
// $Id$
import scala.util.continuations._
object Test {
def test(x:Int) = if (x <= 7)
shift { k: (Unit=>Unit) => println("abort") }
def main(args: Array[String]): Any = {
println(reset{ test(7); println("alive") })
println(reset{ test(8); println("alive") })
}
}

View File

@ -0,0 +1,2 @@
6
9

View File

@ -0,0 +1,21 @@
// $Id$
import scala.util.continuations._
object Test {
def util(x: Boolean) = shift { k: (Boolean=>Int) => k(x) }
def test(x:Int) = if (util(x <= 7))
x - 1
else
x + 1
def main(args: Array[String]): Any = {
println(reset(test(7)))
println(reset(test(8)))
}
}

View File

@ -0,0 +1,33 @@
// $Id$
import scala.util.continuations._
object Test {
def test(x: => Int @cpsParam[String,Int]) = 7
def test2() = {
val x = shift { k: (Int => String) => 9 }
x
}
def test3(x: => Int @cpsParam[Int,Int]) = 7
def util() = shift { k: (String => String) => "7" }
def main(args: Array[String]): Any = {
test { shift { k: (Int => String) => 9 } }
test { shift { k: (Int => String) => 9 }; 2 }
// test { shift { k: (Int => String) => 9 }; util() } <-- doesn't work
test { shift { k: (Int => String) => 9 }; util(); 2 }
test { shift { k: (Int => String) => 9 }; { test3(0); 2 } }
test3 { { test3(0); 2 } }
}
}

View File

@ -0,0 +1,2 @@
10
9

View File

@ -0,0 +1,18 @@
// $Id$
import scala.util.continuations._
object Test {
def test(x:Int) = x match {
case 7 => shift { k: (Int=>Int) => k(k(k(x))) }
case 8 => shift { k: (Int=>Int) => k(x) }
}
def main(args: Array[String]): Any = {
println(reset(1 + test(7)))
println(reset(1 + test(8)))
}
}

View File

@ -0,0 +1,2 @@
10
9

View File

@ -0,0 +1,18 @@
// $Id$
import scala.util.continuations._
object Test {
def test(x:Int) = x match {
case 7 => shift { k: (Int=>Int) => k(k(k(x))) }
case _ => x
}
def main(args: Array[String]): Any = {
println(reset(1 + test(7)))
println(reset(1 + test(8)))
}
}

View File

@ -0,0 +1,2 @@
B
B

View File

@ -0,0 +1,26 @@
// $Id$
import scala.util.continuations._
object Test {
def test1() = {
val (a, b) = shift { k: (((String,String)) => String) => k("A","B") }
b
}
case class Elem[T,U](a: T, b: U)
def test2() = {
val Elem(a,b) = shift { k: (Elem[String,String] => String) => k(Elem("A","B")) }
b
}
def main(args: Array[String]): Any = {
println(reset(test1()))
println(reset(test2()))
}
}

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,14 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Unit = {
val z = reset {
val f: (() => Int @cps[Int]) = () => 1
f()
}
println(z)
}
}

View File

@ -0,0 +1,10 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Unit = {
reset0 { 0 }
}
}

View File

@ -0,0 +1,14 @@
// $Id$
import scala.util.continuations._
object Test {
def shifted: Unit @suspendable = shift { (k: Unit => Unit) => () }
def test1(b: => Boolean) = {
reset {
if (b) shifted
}
}
def main(args: Array[String]) = test1(true)
}

View File

@ -0,0 +1,4 @@
()
()
()
()

View File

@ -0,0 +1,20 @@
// $Id$
import scala.util.continuations._
object Test {
def suspended[A](x: A): A @suspendable = x
def test1[A](x: A): A @suspendable = suspended(x) match { case x => x }
def test2[A](x: List[A]): A @suspendable = suspended(x) match { case List(x) => x }
def test3[A](x: A): A @suspendable = x match { case x => x }
def test4[A](x: List[A]): A @suspendable = x match { case List(x) => x }
def main(args: Array[String]) = {
println(reset(test1()))
println(reset(test2(List(()))))
println(reset(test3()))
println(reset(test4(List(()))))
}
}

View File

@ -0,0 +1 @@
9000

View File

@ -0,0 +1,22 @@
// $Id$
import scala.util.continuations._
object Test {
def foo(): Int @cps[Unit] = 2
def test(): Unit @cps[Unit] = {
var x = 0
while (x < 9000) { // pick number large enough to require tail-call opt
x += foo()
}
println(x)
}
def main(args: Array[String]): Any = {
reset(test())
}
}

View File

@ -0,0 +1,11 @@
up
up
up
up
up
10
down
down
down
down
down

View File

@ -0,0 +1,22 @@
// $Id$
import scala.util.continuations._
object Test {
def foo(): Int @cps[Unit] = shift { k => println("up"); k(2); println("down") }
def test(): Unit @cps[Unit] = {
var x = 0
while (x < 9) {
x += foo()
}
println(x)
}
def main(args: Array[String]): Any = {
reset(test())
}
}

View File

@ -0,0 +1,19 @@
up
up
up
up
up
up
up
up
up
9000
down
down
down
down
down
down
down
down
down

View File

@ -0,0 +1,23 @@
// $Id$
import scala.util.continuations._
object Test {
def foo1(): Int @cps[Unit] = 2
def foo2(): Int @cps[Unit] = shift { k => println("up"); k(2); println("down") }
def test(): Unit @cps[Unit] = {
var x = 0
while (x < 9000) { // pick number large enough to require tail-call opt
x += (if (x % 1000 != 0) foo1() else foo2())
}
println(x)
}
def main(args: Array[String]): Any = {
reset(test())
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test0.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test1.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test16Printf.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test2.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test3.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test4.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test5.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test6.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test7.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test8.main(args)
}
}

View File

@ -0,0 +1,9 @@
// $Id$
object Test {
def main(args: Array[String]): Any = {
examples.continuations.Test9Monads.main(args)
}
}

View File

@ -0,0 +1,4 @@
1
2
3
enough is enough

View File

@ -0,0 +1,33 @@
// $Id$
import scala.util.continuations._
import scala.util.continuations.Loops._
object Test {
def main(args: Array[String]): Any = {
reset {
val list = List(1,2,3,4,5)
for (x <- list.suspendable) {
shift { k: (Unit => Unit) =>
println(x)
if (x < 3)
k()
else
println("enough is enough")
}
}
}
}
}

View File

@ -0,0 +1,15 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args: Array[String]): Any = {
val g: PartialFunction[Int, Int @cps[Int,Int]] = { case x => 7 }
println(reset(g(2)))
}
}

View File

@ -0,0 +1,20 @@
8
java.lang.ClassCastException: scala.util.continuations.ControlContext cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(Unknown Source)
at Test$$anonfun$main$2.apply(select.scala:18)
at Test$$anonfun$main$2.apply(select.scala:18)
at scala.util.continuations.ControlContext$.reset(ControlContext.scala:65)
at Test$.main(select.scala:18)
at Test.main(select.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:55)
at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:22)
at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:61)
at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:55)
at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:61)
at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:33)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:153)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

View File

@ -0,0 +1,2 @@
8
8

View File

@ -0,0 +1,21 @@
// $Id$
import scala.util.continuations._
object Test {
class Bla {
val x = 8
}
def bla = shift { k:(Bla=>Bla) => k(new Bla) }
// TODO: check whether this also applies to a::shift { k => ... }
def main(args: Array[String]) = {
println(reset(bla).x)
println(reset(bla.x))
}
}

View File

@ -0,0 +1,18 @@
// $Id$
import scala.util.continuations._
object Test {
def double[B](n : Int)(k : Int => B) : B = k(n * 2)
def main(args : Array[String]) {
reset {
val result1 = shift(double[Unit](100))
val result2 = shift(double[Unit](result1))
println(result2)
}
}
}

View File

@ -0,0 +1,14 @@
// $Id$
import scala.util.continuations._
object Test {
def main(args : Array[String]) {
println(reset {
val x = shift(List(1,2,3).flatMap[Int, List[Int]])
List(x + 2)
})
}
}