Skip to content

Commit dcc70cb

Browse files
author
Olivier Chafik
committed
Merge branch 'gh-pages' of github.com:ochafik/Scalaxy into gh-pages
2 parents df4cd2a + 6aafd4f commit dcc70cb

File tree

198 files changed

+15691
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

198 files changed

+15691
-0
lines changed

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
old
2+
target
3+
project/boot
4+
project/target
5+
*~
6+
.DS_Store
7+
.settings
8+
*#Untitled-*#
9+
.idea
10+
.idea_modules
11+
.settings
12+
.project
13+
.classpath
14+
*.class
15+
*.marks
16+
17+
Macros/.cache
18+
19+
Compiler/.cache
20+
21+
Core/.cache
22+
23+
.history

Beans/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Scalaxy/Beans
2+
3+
Syntactic sugar to set Java beans properties with a very Scala-friendly syntax ([BSD-licensed](https://github.com/ochafik/Scalaxy/blob/master/LICENSE), does not depend on the rest of Scalaxy).
4+
5+
The following expression:
6+
```scala
7+
import scalaxy.beans._
8+
9+
new MyBean().set(foo = 10, bar = 12)
10+
```
11+
Gets replaced (and fully type-checked) at compile time by:
12+
```scala
13+
{
14+
val bean = new MyBean()
15+
bean.setFoo(10)
16+
bean.setBar(12)
17+
bean
18+
}
19+
```
20+
21+
Works with all Java beans and doesn't bring any runtime dependency.
22+
23+
Only downside: code completion won't work in IDE (unless someone adds a special case for `Scalaxy/Beans` :-)).
24+
25+
# Usage
26+
27+
If you're using `sbt` 0.12.2+, just put the following lines in `build.sbt`:
28+
```scala
29+
// Only works with 2.10.0+
30+
scalaVersion := "2.10.0"
31+
32+
// Dependency at compilation-time only (not at runtime).
33+
libraryDependencies += "com.nativelibs4java" %% "scalaxy-beans" % "0.3-SNAPSHOT" % "provided"
34+
35+
// Scalaxy/Beans snapshots are published on the Sonatype repository.
36+
resolvers += Resolver.sonatypeRepo("snapshots")
37+
```
38+
39+
# Hacking
40+
41+
If you want to build / test / hack on this project:
42+
- Make sure to use [paulp's sbt script](https://github.com/paulp/sbt-extras) with `sbt` 0.12.2+
43+
- Use the following commands to checkout the sources and build the tests continuously:
44+
45+
```
46+
git clone git://github.com/ochafik/Scalaxy.git
47+
cd Scalaxy
48+
sbt "project scalaxy-beans" "; clean ; ~test"
49+
```
50+
51+
# References
52+
53+
See [my original post](http://ochafik.com/blog/?p=786).
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package scalaxy
2+
3+
import scala.language.dynamics
4+
import scala.language.experimental.macros
5+
6+
import scala.reflect.ClassTag
7+
import scala.reflect.NameTransformer
8+
import scala.reflect.macros.Context
9+
10+
/**
11+
Syntactic sugar to instantiate Java beans with a very Scala-friendly syntax.
12+
13+
The following expression:
14+
15+
import scalaxy.beans
16+
17+
new MyBean().set(
18+
foo = 10,
19+
bar = 12
20+
)
21+
22+
Gets replaced (and type-checked) at compile time by:
23+
24+
{
25+
val bean = new MyBean
26+
bean.setFoo(10)
27+
bean.setBar(12)
28+
bean
29+
}
30+
31+
Doesn't bring any runtime dependency (macro is self-erasing).
32+
Don't expect code completion from your IDE as of yet.
33+
*/
34+
package object beans
35+
{
36+
implicit def beansExtensions[T <: AnyRef](bean: T) = new {
37+
def set = new Dynamic {
38+
def applyDynamicNamed(name: String)(args: (String, Any)*): T =
39+
macro impl.applyDynamicNamedImpl[T]
40+
}
41+
}
42+
}
43+
44+
package beans
45+
{
46+
package object impl
47+
{
48+
// This needs to be public and statically accessible.
49+
def applyDynamicNamedImpl[T : c.WeakTypeTag]
50+
(c: Context)
51+
(name: c.Expr[String])
52+
(args: c.Expr[(String, Any)]*) : c.Expr[T] =
53+
{
54+
import c.universe._
55+
56+
// Check that the method name is "create".
57+
name.tree match {
58+
case Literal(Constant(n)) =>
59+
if (n != "apply")
60+
c.error(name.tree.pos, s"Expected 'apply', got '$n'")
61+
}
62+
63+
// Get the bean.
64+
val Select(Apply(_, List(bean)), _) = c.typeCheck(c.prefix.tree)
65+
66+
// Choose a non-existing name for our bean's val.
67+
val beanName = newTermName(c.fresh("bean"))
68+
69+
// Create a declaration for our bean's val.
70+
val beanDef = ValDef(NoMods, beanName, TypeTree(bean.tpe), bean)
71+
72+
// Try to find a setter in the bean type that can take values of the type we've got.
73+
def getSetter(name: String) = {
74+
bean.tpe.member(newTermName(name))
75+
.filter(s => s.isMethod && s.asMethod.paramss.flatten.size == 1)
76+
}
77+
78+
// Try to find a setter in the bean type that can take values of the type we've got.
79+
def getVarTypeFromSetter(s: Symbol) = {
80+
val Seq(param) = s.asMethod.paramss.flatten
81+
param.typeSignature
82+
}
83+
84+
val values = args.map(_.tree).map {
85+
// Match Tuple2.apply[String, Any](fieldName, value).
86+
case Apply(_, List(Literal(Constant(fieldName: String)), value)) =>
87+
(fieldName, value)
88+
}
89+
90+
// Forbid duplicates.
91+
for ((fieldName, dupes) <- values.groupBy(_._1); if dupes.size > 1) {
92+
for ((_, value) <- dupes.drop(1))
93+
c.error(value.pos, s"Duplicate value for property '$fieldName'")
94+
}
95+
96+
// Generate one setter call per argument.
97+
val setterCalls = values map
98+
{
99+
case (fieldName, value) =>
100+
101+
// Check that all parameters are named.
102+
if (fieldName == null || fieldName == "")
103+
c.error(value.pos, "Please use named parameters.")
104+
105+
// Get beans-style setter or Scala-style var setter.
106+
val setterSymbol =
107+
getSetter("set" + fieldName.capitalize)
108+
.orElse(getSetter(NameTransformer.encode(fieldName + "_=")))
109+
110+
if (setterSymbol == NoSymbol)
111+
c.error(value.pos, s"Couldn't find a setter for property '$fieldName' in type ${bean.tpe}")
112+
113+
val varTpe = getVarTypeFromSetter(setterSymbol)
114+
if (!(value.tpe weak_<:< varTpe))
115+
c.error(value.pos, s"Setter ${bean.tpe}.${setterSymbol.name}($varTpe) does not accept values of type ${value.tpe}")
116+
117+
Apply(Select(Ident(beanName), setterSymbol), List(value))
118+
}
119+
// Build a block with the bean declaration, the setter calls and return the bean.
120+
c.Expr[T](Block(Seq(beanDef) ++ setterCalls :+ Ident(beanName): _*))
121+
}
122+
}
123+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package scalaxy
2+
3+
import org.junit._
4+
import org.junit.Assert._
5+
6+
import scalaxy.beans._
7+
8+
class BeansTest
9+
{
10+
class A
11+
class B extends A
12+
class Bean {
13+
private var _foo = 0
14+
def getFoo = _foo
15+
def setFoo(foo: Int) { _foo = foo }
16+
17+
private var _bar = 0.0
18+
def getBar = _bar
19+
def setBar(bar: Double) { _bar = bar }
20+
21+
private var _a: A = _
22+
def getA = _a
23+
def setA(a: A) { _a = a }
24+
25+
private var _b: B = _
26+
def getB() = _b
27+
def setB(b: B) { _b = b }
28+
29+
private var _child: Bean = _
30+
def getChild = _child
31+
def setChild(child: Bean) { _child = child }
32+
}
33+
class Mutable {
34+
var x: Int = _
35+
var y: Double = _
36+
var a: A = _
37+
var b: B = _
38+
}
39+
40+
@Test
41+
def simple {
42+
val bean = new Bean().set(
43+
bar = 12,
44+
foo = 10
45+
)
46+
assertEquals(10, bean.getFoo)
47+
assertEquals(12, bean.getBar, 0)
48+
}
49+
50+
@Test
51+
def inheritance {
52+
val a = new A
53+
val b = new B
54+
assertEquals(a, new Bean().set(a = a).getA)
55+
assertEquals(b, new Bean().set(a = b).getA)
56+
assertEquals(b, new Bean().set(b = b).getB)
57+
}
58+
59+
@Test
60+
def child {
61+
val child = new Bean().set(bar = 12)
62+
assertEquals(child, new Bean().set(child = child).getChild)
63+
assertEquals(123, new Bean().set(child = new Bean().set(foo = 123)).getChild.getFoo)
64+
}
65+
66+
@Test
67+
def mutableScala {
68+
assertEquals(10, new Mutable().set(x = 10).x)
69+
}
70+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package scalaxy.compilets
2+
3+
trait Compilet {
4+
def runsAfter: Seq[Compilet] = Seq()
5+
6+
def name = getClass.getName.replaceAll("\\$", "")
7+
}
8+
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package scalaxy.compilets
2+
3+
import scala.language.experimental.macros
4+
import scala.reflect.macros.Context
5+
6+
import scala.reflect.runtime.{ universe => ru }
7+
8+
object impl
9+
{
10+
def newListTree[T: ru.TypeTag](c: Context)(
11+
values: List[c.universe.Tree]): c.Expr[List[T]] =
12+
{
13+
import c.universe._
14+
c.Expr[List[T]](
15+
Apply(
16+
TypeApply(
17+
Select(
18+
Select(
19+
Ident(newTermName("scala")).setSymbol(definitions.ScalaPackage),
20+
newTermName("List")
21+
),
22+
newTermName("apply")
23+
),
24+
List(TypeTree(typeOf[T]))
25+
),
26+
values
27+
)
28+
)
29+
}
30+
31+
def traverse(u: scala.reflect.api.Universe)(tree: u.Tree)(f: PartialFunction[u.Tree, Unit]) {
32+
(new u.Traverser { override def traverse(tree: u.Tree) {
33+
//println("traversing " + tree)
34+
super.traverse(tree)
35+
if (f.isDefinedAt(tree))
36+
f.apply(tree)
37+
}}).traverse(tree)
38+
}
39+
40+
def assertNoUnsupportedConstructs(c: Context)(tree: c.universe.Tree) {
41+
import c.universe._
42+
def notSupported(t: Tree, what: String) =
43+
c.error(t.pos, what + " definitions are not supported by Scalaxy yet")
44+
45+
// Coarse validation of supported ASTs:
46+
traverse(c.universe)(tree) {
47+
case t @ DefDef(_, _, _, _, _, _) => notSupported(t, "Function / method")
48+
case t @ ClassDef(_, _, _, _) => notSupported(t, "Class")
49+
case t @ ModuleDef(_, _, _) => notSupported(t, "Module")
50+
case t @ TypeDef(_, _, _, _) => notSupported(t, "Type")
51+
case t @ PackageDef(_, _) => notSupported(t, "Package")
52+
}
53+
}
54+
private def expr[T](c: Context)(x: c.Expr[T]): c.Expr[ru.Expr[T]] = {
55+
c.Expr[ru.Expr[T]](
56+
c.reifyTree(
57+
c.universe.treeBuild.mkRuntimeUniverseRef,
58+
c.universe.EmptyTree,
59+
c.typeCheck(x.tree)
60+
)
61+
)
62+
}
63+
private def tree(c: Context)(x: c.Expr[Any]): c.universe.Tree =
64+
expr[Any](c)(x).tree
65+
66+
def fail(c: Context)(message: c.Expr[String])(pattern: c.Expr[Any]): c.Expr[MatchError] = {
67+
assertNoUnsupportedConstructs(c)(pattern.tree)
68+
c.universe.reify(new MatchError(expr(c)(pattern).splice, message.splice))
69+
}
70+
71+
def warn(c: Context)(message: c.Expr[String])(pattern: c.Expr[Any]): c.Expr[MatchWarning] = {
72+
assertNoUnsupportedConstructs(c)(pattern.tree)
73+
c.universe.reify(new MatchWarning(expr(c)(pattern).splice, message.splice))
74+
}
75+
76+
def replace[T](c: Context)(pattern: c.Expr[T], replacement: c.Expr[T]): c.Expr[Replacement] = {
77+
import c.universe._
78+
assertNoUnsupportedConstructs(c)(pattern.tree)
79+
assertNoUnsupportedConstructs(c)(replacement.tree)
80+
c.universe.reify(new Replacement(expr(c)(pattern).splice, expr(c)(replacement).splice))
81+
}
82+
83+
def when[T](c: Context)(pattern: c.Expr[T])(idents: c.Expr[Any]*)(thenMatch: c.Expr[PartialFunction[List[ru.Tree], Action[T]]])
84+
: c.Expr[ConditionalAction[T]] =
85+
{
86+
import c.universe._
87+
assertNoUnsupportedConstructs(c)(pattern.tree)
88+
89+
val scalaCollection =
90+
Select(Ident(newTermName("scala")), newTermName("collection"))
91+
92+
c.Expr[ConditionalAction[T]](
93+
New(
94+
Select(Ident(rootMirror.staticPackage("scalaxy.compilets")), newTypeName("ConditionalAction")),
95+
List(List(
96+
tree(c)(pattern),
97+
Apply(
98+
Select(Select(scalaCollection, newTermName("Seq")), newTermName("apply")),
99+
idents.map(_.tree).toList.map { case Ident(n) => Literal(Constant(n.toString)) }
100+
),
101+
thenMatch.tree
102+
))
103+
)
104+
)
105+
}
106+
107+
def replacement[T: c.WeakTypeTag](c: Context)(replacement: c.Expr[T]): c.Expr[ReplaceBy[T]] = {
108+
assertNoUnsupportedConstructs(c)(replacement.tree)
109+
c.universe.reify(new ReplaceBy[T](expr(c)(replacement).splice))
110+
}
111+
}
112+

0 commit comments

Comments
 (0)