Skip to content

Commit

Permalink
Feature/core constructor (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
b-studios authored Dec 11, 2023
2 parents 94b8b65 + 122dec9 commit 8778ec0
Show file tree
Hide file tree
Showing 32 changed files with 341 additions and 158 deletions.
1 change: 1 addition & 0 deletions effekt/jvm/src/test/scala/effekt/MLTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class MLTests extends EffektTests {
examplesDir / "casestudies" / "lexer.effekt.md",
examplesDir / "casestudies" / "parser.effekt.md",
examplesDir / "casestudies" / "prettyprinter.effekt.md",
examplesDir / "benchmarks" / "pretty.effekt",
examplesDir / "pos" / "simpleparser.effekt",

// cont
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ abstract class AbstractPolymorphismBoxingTests extends CorePhaseTests(Polymorphi
)
}
class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {

test("simple non-polymorphic code should stay the same"){
val code =
"""module main
Expand Down Expand Up @@ -87,7 +86,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
"""module main
|
|def id = { ['A](a: 'A) => return a: 'A }
|def idInt = { (x: Int) => return (id: ['A]('A) => 'A @ {})[BoxedInt]((MkBoxedInt: (Int) => BoxedInt @ {})(x: Int)).unboxInt: Int }
|def idInt = { (x: Int) => return (id: ['A]('A) => 'A @ {})[BoxedInt](make BoxedInt MkBoxedInt(x: Int)).unboxInt: Int }
|""".stripMargin
assertTransformsTo(from, to)
}
Expand All @@ -103,7 +102,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
"""module main
|
|def id = { ['A](a: 'A) => return a: 'A }
|def idInt = { (x: Int) => val tmp = (id: ['A]('A) => 'A @ {})[BoxedInt]((MkBoxedInt: (Int) => BoxedInt @ {})(x: Int)) ; return tmp:BoxedInt.unboxInt: Int }
|def idInt = { (x: Int) => val tmp = (id: ['A]('A) => 'A @ {})[BoxedInt](make BoxedInt MkBoxedInt(x: Int)) ; return tmp:BoxedInt.unboxInt: Int }
|""".stripMargin
assertTransformsTo(from, to)
}
Expand All @@ -127,7 +126,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
|def idInt = { (x: Int) =>
| {
| let res = run {
| let boxedRes = !(id: ['A]('A) => 'A @ {})[BoxedInt]((MkBoxedInt: (Int) => BoxedInt @ {})(x: Int))
| let boxedRes = !(id: ['A]('A) => 'A @ {})[BoxedInt](make BoxedInt MkBoxedInt(x: Int))
| return boxedRes:BoxedInt.unboxInt: Int
| }
| return res: Int
Expand All @@ -151,7 +150,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
| {
| def originalFn = { (x: Int) => return x: Int }
| val result = (originalFn: (Int) => Int @ {})(boxedX: BoxedInt.unboxInt: Int);
| return (MkBoxedInt: (Int) => BoxedInt @ {})(result: Int)
| return make BoxedInt MkBoxedInt(result: Int)
| }
| };
| return r:BoxedInt.unboxInt: Int
Expand Down Expand Up @@ -182,11 +181,11 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
| (hhofargarg: Int) =>
| {
| def tmp = hhofargB: ('A) => 'A @ {}
| val rres = (tmp: ('A) => 'A @ {})((MkBoxedInt: (Int) => BoxedInt @ {})(hhofargarg: Int));
| val rres = (tmp: ('A) => 'A @ {})(make BoxedInt MkBoxedInt(hhofargarg: Int));
| return rres:BoxedInt.unboxInt: Int
| }
| };
| return (MkBoxedInt: (Int) => BoxedInt @ {})(res:Int)
| return make BoxedInt MkBoxedInt(res:Int)
| }
| };
| return result:BoxedInt.unboxInt: Int
Expand Down
26 changes: 0 additions & 26 deletions effekt/shared/src/main/scala/effekt/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1237,32 +1237,6 @@ object Typer extends Phase[NameResolved, Typechecked] {
Context.annotateInferredEffects(t, effs.toEffects)
Result(got, effs)
}

/**
* Helper methods on function symbols to retreive its type
* either from being annotated or by looking it up (if already typechecked...)
*/
extension (fun: Callable)(using Context) {
// invariant: only works if ret is defined!
def toType: FunctionType =
annotatedType.get
def toType(ret: ValueType, effects: Effects, capabilityParams: List[Capture]): FunctionType =
val bcapt = fun.bparams.map { p => p.capture }
val tps = fun.tparams
val vps = fun.vparams.map { p => p.tpe.get }
val bps = fun.bparams.map { p => p.tpe }
FunctionType(tps, bcapt ++ capabilityParams, vps, bps, ret, effects)

def annotatedType: Option[FunctionType] =
for {
ret <- fun.annotatedResult;
effs <- fun.annotatedEffects
effects = effs.distinct
// TODO currently the return type cannot refer to the annotated effects, so we can make up capabilities
// in the future namer needs to annotate the function with the capture parameters it introduced.
capt = effects.canonical.map { tpe => CaptureParam(tpe.name) }
} yield toType(ret, effects, capt)
}
//</editor-fold>

}
Expand Down
1 change: 1 addition & 0 deletions effekt/shared/src/main/scala/effekt/core/Deadcode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class Reachable(
case Pure.ValueVar(id, annotatedType) => process(id)
case Pure.Literal(value, annotatedType) => ()
case Pure.PureApp(b, targs, vargs) => process(b); vargs.foreach(process)
case Pure.Make(data, tag, vargs) => process(tag); vargs.foreach(process)
case Pure.Select(target, field, annotatedType) => process(target)
case Pure.Box(b, annotatedCapture) => process(b)
}
Expand Down
1 change: 1 addition & 0 deletions effekt/shared/src/main/scala/effekt/core/Inline.scala
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ object Inline {

def rewrite(p: Pure)(using InlineContext): Pure = p match {
case Pure.PureApp(b, targs, vargs) => pureApp(rewrite(b), targs, vargs.map(rewrite))
case Pure.Make(data, tag, vargs) => make(data, tag, vargs.map(rewrite))
// currently, we don't inline values, but we can dealias them
case x @ Pure.ValueVar(id, annotatedType) => dealias(x)

Expand Down
11 changes: 8 additions & 3 deletions effekt/shared/src/main/scala/effekt/core/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ class CoreParsers(positions: Positions, names: Names) extends EffektLexers(posit
None
}

lazy val `run` = keyword("run")
lazy val `;` = super.literal(";")
lazy val `run` = keyword("run")
lazy val `;` = super.literal(";")
lazy val `make` = keyword("make")

/**
* Literals
Expand Down Expand Up @@ -154,6 +155,7 @@ class CoreParsers(positions: Positions, names: Names) extends EffektLexers(posit
( literal
| id ~ (`:` ~> valueType) ^^ Pure.ValueVar.apply
| `box` ~> captures ~ block ^^ { case capt ~ block => Pure.Box(block, capt) }
| `make` ~> dataType ~ id ~ valueArgs ^^ Pure.Make.apply
| block ~ maybeTypeArgs ~ valueArgs ^^ Pure.PureApp.apply
| failure("Expected a pure expression.")
)
Expand Down Expand Up @@ -261,11 +263,14 @@ class CoreParsers(positions: Positions, names: Names) extends EffektLexers(posit

lazy val primValueType: P[ValueType] =
( typeParam ^^ ValueType.Var.apply
| id ~ maybeTypeArgs ^^ ValueType.Data.apply
| dataType
| `(` ~> valueType <~ `)`
| failure("Expected a value type")
)

lazy val dataType: P[ValueType.Data] =
id ~ maybeTypeArgs ^^ { case id ~ targs => ValueType.Data(id, targs) : ValueType.Data }

lazy val blockType: P[BlockType] =
( maybeTypeParams ~ maybeValueTypes ~ many(blockTypeParam) ~ (`=>` ~/> primValueType) ^^ {
case tparams ~ vparams ~ bcparams ~ result =>
Expand Down
33 changes: 25 additions & 8 deletions effekt/shared/src/main/scala/effekt/core/PolymorphismBoxing.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package effekt.core
package effekt
package core

import effekt.PhaseResult.CoreTransformed
import effekt.context.Context
Expand Down Expand Up @@ -28,7 +29,7 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
* @param constructor The constructor to box the values with
* @param field The field to access for unboxing
*/
case class Boxer(tpe: ValueType, constructor: Id, field: Id)
case class Boxer(tpe: ValueType.Data, constructor: Id, field: Id)

/**
* Partial function to describe which values to box and how.
Expand Down Expand Up @@ -204,8 +205,21 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
def transform(pure: Pure)(using PContext): Pure = pure match {
case Pure.ValueVar(id, annotatedType) => Pure.ValueVar(id, transform(annotatedType))
case Pure.Literal(value, annotatedType) => Pure.Literal(value, transform(annotatedType))
case Pure.PureApp(b, targs, vargs) =>
instantiate(b, targs).callPure(b, vargs map transform)
case Pure.PureApp(b, targs, vargs) => instantiate(b, targs).callPure(b, vargs map transform)
case Pure.Make(data, tag, vargs) =>
val dataDecl = PContext.getDataLikeDeclaration(data.name)
val ctorDecl = dataDecl.constructors.find(_.id == tag).getOrElse {
Context.panic(s"No constructor found for tag ${tag} in data type: ${data}")
}

val argTypes = vargs.map(_.tpe)
val paramTypes = ctorDecl.fields.map(_.tpe)

val coercedArgs = (paramTypes zip (argTypes zip vargs)).map { case (param, (targ, arg)) =>
coercer(targ, param)(transform(arg))
}
Pure.Make(transform(data), tag, coercedArgs)

case Pure.Select(target, field, annotatedType) => {
val (symbol, targs) = target.tpe match {
case ValueType.Data(symbol, targs) => (symbol, targs)
Expand All @@ -230,6 +244,10 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
case ValueType.Boxed(tpe, capt) => ValueType.Boxed(transform(tpe), capt)
}

def transform(valueType: ValueType.Data)(using PContext): ValueType.Data = valueType match {
case ValueType.Data(symbol, targs) => ValueType.Data(symbol, targs map transformArg)
}

def transform(blockType: BlockType)(using PContext): BlockType = blockType match {
case BlockType.Function(tparams, cparams, vparams, bparams, result) =>
BlockType.Function(tparams, cparams, vparams map transform, bparams map transform, transform(result))
Expand Down Expand Up @@ -274,8 +292,7 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {

override def apply(t: Pure): Pure = {
val boxer = box(valueType)
val blockTpe = BlockType.Function(List(), List(), List(from), List(), to)
Pure.PureApp(Block.BlockVar(boxer.constructor, blockTpe, Set()), List(), List(t))
Pure.Make(boxer.tpe, boxer.constructor, List(t))
}
}
case class UnboxCoercer(valueType: ValueType)(using PContext) extends Coercer[ValueType, Pure] {
Expand Down Expand Up @@ -308,7 +325,7 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
class FunctionIdentityCoercer[Ty <: BlockType, Te <: Block](
from: Ty, to: Ty, targs: List[ValueType]) extends IdentityCoercer[Ty, Te](from, to) with FunctionCoercer[Ty, Te] {
override def call(block: Te, vargs: List[Pure], bargs: List[Block])(using PContext): Stmt =
Stmt.App(block, targs map transformArg, vargs.map(transform(_)), bargs map transform)
Stmt.App(block, targs map transformArg, vargs.map(transform), bargs map transform)
override def callPure(block: Te, vargs: List[Pure])(using PContext): Pure =
Pure.PureApp(block, targs map transformArg, vargs map transform)
override def callDirect(block: Te, vargs: List[Pure], bargs: List[Block])(using PContext): Expr =
Expand Down Expand Up @@ -352,7 +369,7 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
}

override def callPure(block: B, vargs: List[Pure])(using PContext): Pure = {
rcoercer(Pure.PureApp(block, targs map transformArg, (vcoercers zip vargs).map {case (c,v)=> c(v)}))
rcoercer(Pure.PureApp(block, targs map transformArg, (vcoercers zip vargs).map { case (c,v) => c(v) }))
}

override def callDirect(block: B, vargs: List[Pure], bargs: List[Block])(using PContext): Expr = {
Expand Down
3 changes: 2 additions & 1 deletion effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ object PrettyPrinter extends ParenPrettyPrinter {
case Literal(value, _) => value.toString
case ValueVar(id, _) => toDoc(id)

case PureApp(b, targs, vargs) => toDoc(b) <> argsToDoc(targs, vargs, Nil)
case PureApp(b, targs, vargs) => toDoc(b) <> argsToDoc(targs, vargs, Nil)
case Make(data, tag, vargs) => "make" <+> toDoc(data) <+> toDoc(tag) <> argsToDoc(Nil, vargs, Nil)
case DirectApp(b, targs, vargs, bargs) => toDoc(b) <> argsToDoc(targs, vargs, bargs)

case Select(b, field, tpe) => toDoc(b) <> "." <> toDoc(field)
Expand Down
17 changes: 13 additions & 4 deletions effekt/shared/src/main/scala/effekt/core/Transformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,14 +231,23 @@ object Transformer extends Phase[Typechecked, CoreTransformed] {
val vargs = vparams.map { case Param.ValueParam(id, tpe) => Pure.ValueVar(id, tpe) }

// [[ f ]] = { (x) => f(x) }
def etaExpandPure(b: Constructor | ExternFunction): BlockLit = {
def etaExpandPure(b: ExternFunction): BlockLit = {
assert(bparamtps.isEmpty)
assert(effects.isEmpty)
assert(cparams.isEmpty)
BlockLit(tparams, Nil, vparams, Nil,
Stmt.Return(PureApp(BlockVar(b), targs, vargs)))
}

// [[ f ]] = { (x) => make f(x) }
def etaExpandConstructor(b: Constructor): BlockLit = {
assert(bparamtps.isEmpty)
assert(effects.isEmpty)
assert(cparams.isEmpty)
BlockLit(tparams, Nil, vparams, Nil,
Stmt.Return(Make(core.ValueType.Data(b.tpe, targs), b, vargs)))
}

// [[ f ]] = { (x){g} => let r = f(x){g}; return r }
def etaExpandDirect(f: ExternFunction): BlockLit = {
assert(effects.isEmpty)
Expand All @@ -254,7 +263,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] {

sym match {
case _: ValueSymbol => transformUnbox(tree)
case cns: Constructor => etaExpandPure(cns)
case cns: Constructor => etaExpandConstructor(cns)
case f: ExternFunction if isPure(f.capture) => etaExpandPure(f)
case f: ExternFunction if pureOrIO(f.capture) => etaExpandDirect(f)
// does not require change of calling convention, so no eta expansion
Expand Down Expand Up @@ -477,7 +486,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] {
val substitution = Substitutions((tparams zip targs).toMap, Map.empty)
val remainingTypeParams = tparams.drop(targs.size)
val bparams = Nil
// TODO this is exactly like in [[Typer.toType]] -- TODO repeated here:
// TODO this is exactly like in [[Callable.toType]] -- TODO repeated here:
// TODO currently the return type cannot refer to the annotated effects, so we can make up capabilities
// in the future namer needs to annotate the function with the capture parameters it introduced.
val cparams = effects.canonical.map { tpe => symbols.CaptureParam(tpe.name) }
Expand All @@ -504,7 +513,7 @@ object Transformer extends Phase[Typechecked, CoreTransformed] {
DirectApp(BlockVar(f), targs, vargsT, bargsT)
case r: Constructor =>
if (bargs.nonEmpty) Context.abort("Constructors cannot take block arguments.")
PureApp(BlockVar(r), targs, vargsT)
Make(core.ValueType.Data(r.tpe, targs), r, vargsT)
case f: Operation =>
Context.panic("Should have been translated to a method call!")
case f: Field =>
Expand Down
41 changes: 38 additions & 3 deletions effekt/shared/src/main/scala/effekt/core/Tree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import effekt.util.messages.INTERNAL_ERROR
* │ │─ [[ Def ]]
* │ │─ [[ Include ]]
* │
* │─ [[ Definition ]]
* │ │─ [[ Def ]]
* │ │─ [[ Let ]]
* │
* │─ [[ Expr ]]
* │ │─ [[ DirectApp ]]
* │ │─ [[ Run ]]
Expand Down Expand Up @@ -163,18 +167,33 @@ case class Run(s: Stmt) extends Expr
* │─ [[ ValueVar ]]
* │─ [[ Literal ]]
* │─ [[ PureApp ]]
* │─ [[ Make ]]
* │─ [[ Select ]]
* │─ [[ Box ]]
*
* -------------------------------------------
*/
enum Pure extends Expr {

case ValueVar(id: Id, annotatedType: ValueType)

case Literal(value: Any, annotatedType: ValueType)

// invariant, block b is pure.
/**
* Pure FFI calls. Invariant, block b is pure.
*/
case PureApp(b: Block, targs: List[ValueType], vargs: List[Pure])

/**
* Constructor calls
*
* Note: the structure mirrors interface implementation
*/
case Make(data: ValueType.Data, tag: Id, vargs: List[Pure])

/**
* Record Selection
*/
case Select(target: Pure, field: Id, annotatedType: ValueType)

case Box(b: Block, annotatedCapture: Captures)
Expand Down Expand Up @@ -333,6 +352,9 @@ object normal {
case other => Stmt.App(callee, targs, vargs, bargs)
}

def make(tpe: ValueType.Data, tag: Id, vargs: List[Pure]): Pure =
Pure.Make(tpe, tag, vargs)

def pureApp(callee: Block, targs: List[ValueType], vargs: List[Pure]): Pure =
callee match {
case b : Block.BlockLit =>
Expand All @@ -347,9 +369,16 @@ object normal {
}

// "match" is a keyword in Scala
// TODO perform matching here, if scrutinee statically known
def patternMatch(scrutinee: Pure, clauses: List[(Id, BlockLit)], default: Option[Stmt]): Stmt =
Match(scrutinee, clauses, default)
scrutinee match {
case Pure.Make(dataType, ctorTag, vargs) =>
clauses.collectFirst { case (tag, lit) if tag == ctorTag => lit }
.map(body => app(body, Nil, vargs, Nil))
.orElse { default }.getOrElse { sys error "Should not happen" }
case other =>
Match(scrutinee, clauses, default)
}


def directApp(callee: Block, targs: List[ValueType], vargs: List[Pure], bargs: List[Block]): Expr =
callee match {
Expand Down Expand Up @@ -498,6 +527,8 @@ object Tree {
def rewrite(p: Param.BlockParam): Param.BlockParam = rewrite(p: Param).asInstanceOf[Param.BlockParam]

def rewrite(t: ValueType): ValueType = rewriteStructurally(t)
def rewrite(t: ValueType.Data): ValueType.Data = rewriteStructurally(t)

def rewrite(t: BlockType): BlockType = rewriteStructurally(t)
def rewrite(t: BlockType.Interface): BlockType.Interface = rewriteStructurally(t)
def rewrite(capt: Captures): Captures = capt.map(rewrite)
Expand Down Expand Up @@ -544,6 +575,7 @@ object Variables {
case Pure.ValueVar(id, annotatedType) => Variables.value(id, annotatedType)
case Pure.Literal(value, annotatedType) => Variables.empty
case Pure.PureApp(b, targs, vargs) => free(b) ++ all(vargs, free)
case Pure.Make(data, tag, vargs) => all(vargs, free)
case Pure.Select(target, field, annotatedType) => free(target)
case Pure.Box(b, annotatedCapture) => free(b)
}
Expand Down Expand Up @@ -748,6 +780,9 @@ object substitutions {
case Literal(value, annotatedType) =>
Literal(value, substitute(annotatedType))

case Make(tpe, tag, vargs) =>
Make(substitute(tpe).asInstanceOf, tag, vargs.map(substitute))

case PureApp(b, targs, vargs) =>
PureApp(substitute(b), targs.map(substitute), vargs.map(substitute))

Expand Down
Loading

0 comments on commit 8778ec0

Please sign in to comment.