├── .gitignore ├── core └── src │ └── main │ └── scala │ ├── typeEnum.scala │ ├── enumOf.scala │ └── enum.scala ├── project └── Build.scala ├── README.md └── macros └── src └── main └── scala └── scalax.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | boot 3 | lib_managed 4 | _build 5 | -------------------------------------------------------------------------------- /core/src/main/scala/typeEnum.scala: -------------------------------------------------------------------------------- 1 | package scalax 2 | 3 | trait Serializable[T] { def serialize(x: T): String } 4 | 5 | object Serializable extends TypeEnum[Serializable]( 6 | Int { def serialize(x: Int) = x.toString }, 7 | Double { def serialize(x: Double) = x.toString } 8 | ) 9 | 10 | object SerializableDemo { 11 | def serialize[T : Serializable](x: T) = implicitly[Serializable[T]].serialize(x) 12 | 13 | serialize(42) 14 | serialize(math.Pi) 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/scala/enumOf.scala: -------------------------------------------------------------------------------- 1 | package scalax 2 | 3 | sealed abstract class Planet(val mass: Double, val radius: Double) extends Value 4 | 5 | object Planets extends EnumOf[Planet]( 6 | Mercury (3.303e+23, 2.4397e6), 7 | Venus (4.869e+24, 6.0518e6), 8 | Earth (5.976e+24, 6.37814e6), 9 | Mars (6.421e+23, 3.3972e6), 10 | Jupiter (1.9e+27, 7.1492e7), 11 | Saturn (5.688e+26, 6.0268e7), 12 | Uranus (8.686e+25, 2.5559e7), 13 | Neptune (1.024e+26, 2.4746e7) 14 | ) { 15 | def giants = List(Jupiter, Saturn) 16 | } 17 | 18 | object PlanetsDemo { 19 | import Planets._ 20 | 21 | implicitly[Value =:= Planet] 22 | 23 | def f(x: Planet) = x match { 24 | case Mercury => 25 | case Venus => 26 | } 27 | 28 | Planets.values.sortBy(_.mass) 29 | } 30 | -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | object BuildSettings { 5 | val buildSettings = Defaults.defaultSettings ++ Seq( 6 | organization := "com.github.aloiscochard.enum-paradise", 7 | version := "0.1-SNAPSHOT", 8 | scalacOptions ++= Seq(), 9 | scalaVersion := "2.11.0-SNAPSHOT", 10 | scalaOrganization := "org.scala-lang.macro-paradise", 11 | resolvers += Resolver.sonatypeRepo("snapshots") 12 | ) 13 | } 14 | 15 | object MyBuild extends Build { 16 | import BuildSettings._ 17 | 18 | lazy val root: Project = Project( 19 | "root", 20 | file("core"), 21 | settings = buildSettings 22 | ) aggregate(macros, core) 23 | 24 | lazy val macros: Project = Project( 25 | "macros", 26 | file("macros"), 27 | settings = buildSettings ++ Seq( 28 | libraryDependencies <+= (scalaVersion)("org.scala-lang.macro-paradise" % "scala-reflect" % _)) 29 | ) 30 | 31 | lazy val core: Project = Project( 32 | "core", 33 | file("core"), 34 | settings = buildSettings 35 | ) dependsOn(macros) 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/scala/enum.scala: -------------------------------------------------------------------------------- 1 | package scalax 2 | 3 | object Days extends Enum(Monday, Tuesday, Wednesday, Thursday, Friday) 4 | 5 | object DaysDemo { 6 | implicitly[Days.Monday.type <:< Days.Value] 7 | 8 | import Days._ 9 | def f(x: Value) = x match { 10 | case Monday => 11 | case Tuesday => 12 | case Wednesday => 13 | case Thursday => 14 | //case Friday => 15 | } 16 | //[warn] /core/src/main/scala/enum.scala:10: match may not be exhaustive. 17 | //[warn] It would fail on the following input: Friday 18 | } 19 | 20 | object Months extends Enum( 21 | January("Jan"), 22 | February("Feb"), 23 | Mars("Mar"), 24 | April("Apr"), 25 | May, 26 | June("Jun"), 27 | July("Jul"), 28 | August("Aug"), 29 | September("Sep"), 30 | October("Oct"), 31 | November("Nov"), 32 | December("Dec") 33 | ) 34 | 35 | object MonthsDemo { 36 | def code = Months.values.map(_.name.toUpperCase) 37 | 38 | //scala> scalax.MonthsDemo.code 39 | //res1: Seq[String] = List(JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC) 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enum Paradise (WORK IN PROGRESS) 2 | 3 | Scala enumeration implementation using type macros provided by [Macro Paradise](http://docs.scala-lang.org/overviews/macros/paradise.html) 4 | 5 | ## Usage 6 | 7 | Enumeration: 8 | 9 | object Days extends Enum(Monday, Tuesday, Wednesday, Thursday, Friday) 10 | 11 | implicitly[Days.Monday.type <:< Days.Value] 12 | 13 | import Days._ 14 | def f(x: Value) = x match { 15 | case Monday => 16 | case Tuesday => 17 | case Wednesday => 18 | case Thursday => 19 | //case Friday => 20 | } 21 | //[warn] /core/src/main/scala/enum.scala:10: match may not be exhaustive. 22 | //[warn] It would fail on the following input: Friday 23 | 24 | Enumeration with specific names: 25 | 26 | object Months extends Enum( 27 | January("Jan"), 28 | February("Feb"), 29 | Mars("Mar"), 30 | April("Apr"), 31 | May, 32 | June("Jun"), 33 | July("Jul"), 34 | August("Aug"), 35 | September("Sep"), 36 | October("Oct"), 37 | November("Nov"), 38 | December("Dec") 39 | ) 40 | 41 | def code = Months.values.map(_.name.toUpperCase) 42 | 43 | //scala> scalax.MonthsDemo.code 44 | //res1: Seq[String] = List(JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC) 45 | 46 | Enumeration with specific value type: 47 | 48 | sealed abstract class Planet(val mass: Double, val radius: Double) extends Value 49 | 50 | object Planets extends EnumOf[Planet]( 51 | Mercury (3.303e+23, 2.4397e6), 52 | Venus (4.869e+24, 6.0518e6), 53 | Earth (5.976e+24, 6.37814e6), 54 | Mars (6.421e+23, 3.3972e6), 55 | Jupiter (1.9e+27, 7.1492e7), 56 | Saturn (5.688e+26, 6.0268e7), 57 | Uranus (8.686e+25, 2.5559e7), 58 | Neptune (1.024e+26, 2.4746e7) 59 | ) { 60 | def giants = List(Jupiter, Saturn) 61 | } 62 | 63 | implicitly[Planets.Value =:= Planet] 64 | 65 | //scala> Planets.values.sortBy(_.mass) 66 | //res0: Seq[scalax.Planet] = List(Mercury, Mars, Venus, Earth, Uranus, Neptune, Saturn, Jupiter) 67 | 68 | Type Enumeration: 69 | 70 | trait Serializable[T] { def serialize(x: T): String } 71 | 72 | object Serializable extends TypeEnum[Serializable]( 73 | Int { def serialize(x: Int) = x.toString }, 74 | Double { def serialize(x: Double) = x.toString } 75 | ) 76 | 77 | def serialize[T : Serializable](x: T) = implicitly[Serializable[T]].serialize(x) 78 | 79 | //scala> serialize(42) 80 | //res0: String = 42 81 | 82 | //scala> serialize(math.Pi) 83 | //res1: String = 3.141592653589793 84 | 85 | ## License 86 | 87 | This software is licensed under the Apache 2 license, quoted below. 88 | 89 | Copyright 2009-2012 Alois Cochard 90 | 91 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 92 | use this file except in compliance with the License. You may obtain a copy of 93 | the License at http://www.apache.org/licenses/LICENSE-2.0 94 | 95 | Unless required by applicable law or agreed to in writing, software 96 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 97 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 98 | License for the specific language governing permissions and limitations under 99 | the License. 100 | -------------------------------------------------------------------------------- /macros/src/main/scala/scalax.scala: -------------------------------------------------------------------------------- 1 | import language.experimental.macros 2 | import scala.reflect.macros.Context 3 | 4 | // TODO Error handling (i.e. when parsing fail) 5 | 6 | package object scalax { 7 | trait Enumerable { 8 | type Value <: scalax.Value 9 | def values: Seq[Value] 10 | } 11 | 12 | trait Value { 13 | def name: String 14 | override def toString: String = name 15 | } 16 | 17 | type Enum(values: _*) = macro Macros.enum 18 | type EnumOf[T <: Value](values: _*) = macro Macros.enumOf[T] 19 | 20 | type TypeEnum[TC[_]](instances: _*) = macro Macros.typeEnum[TC] 21 | 22 | case class EnumDef(id: String, name: String) 23 | 24 | object Macros { 25 | 26 | def enum(c: Context)(values: c.Tree*): c.Tree = { 27 | import c.universe._ 28 | import Flag._ 29 | 30 | val enumDefs = parseValues(c)(values.toList) 31 | 32 | val Expr(Block(List(valueSealedTrait), _)) = reify { 33 | sealed trait Val extends scalax.Value 34 | } 35 | 36 | val valueType = TypeDef(Modifiers(OVERRIDE), TypeName("Value"), List(), Ident(TypeName("Val"))) 37 | val valueTypeTree = Select(This(TypeName(c.enclosingImpl.name.toString)), TypeName("Val")) 38 | 39 | template(c)(valueSealedTrait :: valueType :: valuesList(c)(valueTypeTree, enumDefs) :: valueObjects(c)(valueTypeTree, enumDefs)) 40 | } 41 | 42 | def enumOf[T : c.WeakTypeTag](c: Context)(values: c.Tree*): c.Tree = { 43 | import c.universe._ 44 | import Flag._ 45 | 46 | implicit val context = c 47 | 48 | val tpe = c.weakTypeOf[T] 49 | val enumDefs = parseValues(c)(values.toList) 50 | 51 | val valueTypeTree = Ident(tpe.typeSymbol) 52 | val valueType = TypeDef(Modifiers(OVERRIDE), TypeName("Value"), List(), Ident(tpe.typeSymbol)) 53 | 54 | val generatedCode = valueType :: 55 | valuesList(c)(valueTypeTree, enumDefs) :: 56 | valueObjects(c)(valueTypeTree, enumDefs) 57 | 58 | template(c)(valueType :: valuesList(c)(valueTypeTree, enumDefs) :: valueObjects(c)(valueTypeTree, enumDefs)) 59 | } 60 | 61 | def typeEnum[TC[_]](c: Context)(instances: c.Tree*)(implicit tag: c.WeakTypeTag[TC[_]]) = {//: c.Tree = { 62 | import c.universe._ 63 | import Flag._ 64 | 65 | val tpe = c.weakTypeOf[TC[_]] 66 | 67 | val generatedCode = instances.collect { 68 | // TODO Support package prefix for types 69 | case Apply(Ident(TermName(typeName)), List(Block(Tuple2(defs, _)))) => typeName -> defs 70 | } map { 71 | case (typeName, defs) => 72 | ModuleDef( 73 | Modifiers(IMPLICIT), 74 | TermName(tpe.typeSymbol.name + typeName), 75 | Template( 76 | List(AppliedTypeTree(Ident(tpe.typeSymbol), List(Ident(TypeName(typeName))))), 77 | emptyValDef, 78 | List( 79 | DefDef( 80 | Modifiers(), 81 | nme.CONSTRUCTOR, 82 | List(), 83 | List(List()), 84 | TypeTree(), 85 | Block( 86 | List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), 87 | Literal(Constant(())) 88 | ) 89 | ) 90 | ) ++ defs 91 | ) 92 | ) 93 | } 94 | 95 | val Expr(Block(List(ClassDef(_, _, _, Template(parents, _, body))), _)) = reify { 96 | class CONTAINER 97 | } 98 | 99 | val Template(_, _, _ :: existingCode) = c.enclosingTemplate 100 | Template(parents, emptyValDef, body ++ generatedCode ++ existingCode) 101 | } 102 | 103 | private def parseValues(c: Context)(xs: List[c.Tree]): List[(EnumDef, List[c.Tree])] = { 104 | import c.universe._ 105 | xs.collect { 106 | case Ident(TermName(id)) => EnumDef(id, id) -> Nil 107 | case Apply(Ident(TermName(id)), List(Literal(Constant(name)))) => EnumDef(id, name.toString) -> Nil 108 | case Apply(Ident(TermName(id)), args) => EnumDef(id, id) -> args 109 | } 110 | } 111 | 112 | private def template(c: Context)(generatedCode: List[c.Tree]) = { 113 | import c.universe._ 114 | 115 | val Expr(Block(List(ClassDef(_, _, _, Template(parents, _, body))), _)) = reify { 116 | class CONTAINER extends Enumerable 117 | } 118 | 119 | val Template(_, _, _ :: existingCode) = c.enclosingTemplate 120 | Template(parents, emptyValDef, body ++ generatedCode ++ existingCode) 121 | } 122 | 123 | private def valueObjects(c: Context)(typeTree: c.Tree, enumDefs: List[(EnumDef, List[c.Tree])]): List[c.Tree] = enumDefs.map { 124 | case (enumDef, args) => 125 | import c.universe._ 126 | ModuleDef( 127 | Modifiers(), 128 | TermName(enumDef.id), 129 | Template( 130 | List(typeTree), 131 | emptyValDef, 132 | List( 133 | DefDef( 134 | Modifiers(), 135 | nme.CONSTRUCTOR, 136 | List(), 137 | List(List()), 138 | TypeTree(), 139 | Block( 140 | List( 141 | Apply( 142 | Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), 143 | args 144 | ) 145 | ), 146 | Literal(Constant(())) 147 | ) 148 | ), 149 | DefDef(Modifiers(), TermName("name"), List(), List(), TypeTree(), Literal(Constant(enumDef.name))) 150 | ) 151 | ) 152 | ) 153 | } 154 | 155 | private def valuesList(c: Context)(typeTree: c.Tree, enumDefs: List[(EnumDef, List[c.Tree])]): c.Tree = { 156 | import c.universe._ 157 | ValDef( 158 | Modifiers(), 159 | TermName("values"), 160 | AppliedTypeTree(Ident(TypeName("Seq")), List(typeTree)), 161 | Apply( 162 | Select( 163 | Select(Select(Select(Ident(TermName("scala")), TermName("collection")), TermName("immutable")), TermName("List")), 164 | TermName("apply") 165 | ), 166 | enumDefs.map(_._1).map(enumDef => Ident(TermName(enumDef.id))).toList 167 | ) 168 | ) 169 | } 170 | } 171 | } 172 | --------------------------------------------------------------------------------