Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
梦境迷离
Scala Macro Tools
提交
f7b44644
S
Scala Macro Tools
项目概览
梦境迷离
/
Scala Macro Tools
上一次同步 大约 1 年
通知
8
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
Scala Macro Tools
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
f7b44644
编写于
6月 20, 2021
作者:
梦境迷离
提交者:
GitHub
6月 20, 2021
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
add builder macro (#6)
* add builder macro * add builder macro * add builder macro
上级
a042cc9c
变更
6
隐藏空白更改
内联
并排
Showing
6 changed file
with
278 addition
and
5 deletion
+278
-5
README.md
README.md
+59
-0
src/main/scala/io/github/dreamylost/builder.scala
src/main/scala/io/github/dreamylost/builder.scala
+140
-0
src/main/scala/io/github/dreamylost/json.scala
src/main/scala/io/github/dreamylost/json.scala
+2
-0
src/main/scala/io/github/dreamylost/toString.scala
src/main/scala/io/github/dreamylost/toString.scala
+6
-4
src/test/scala/io/github/dreamylost/BuilderTest.scala
src/test/scala/io/github/dreamylost/BuilderTest.scala
+70
-0
src/test/scala/io/github/dreamylost/JsonTest.scala
src/test/scala/io/github/dreamylost/JsonTest.scala
+1
-1
未找到文件。
README.md
浏览文件 @
f7b44644
...
...
@@ -9,6 +9,8 @@ scala macro and abstract syntax tree learning code.
## @toString
The
`@toString`
used to generate
`toString`
for Scala classes or a
`toString`
with parameter names for the case classes.
-
Note
-
`verbose`
Whether to enable detailed log.
-
`withFieldName`
Whether to include the name of the field in the toString.
...
...
@@ -58,6 +60,63 @@ val json = Json.toJson(person)
Json
.
fromJson
[
Person
](
json
)
```
## @builder
The
`@builder`
used to generate builder pattern for Scala classes.
-
Note
-
Support
`case class`
/
`class`
.
-
It can be used with
`@toString`
. But it needs to be put in the back.
-
If there is no accompanying object, one will be generated to store the builder method.
-
IDE support is not very good, a red prompt will appear, but the compilation is OK.
-
Example
```
scala
@builder
case
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
val
ret
=
TestClass1
.
builder
().
i
(
1
).
j
(
0
).
x
(
"x"
).
build
()
assert
(
ret
.
toString
==
"TestClass1(1,0,x,Some())"
)
```
Compiler intermediate code:
```
scala
object
TestClass1
extends
scala
.
AnyRef
{
def
<
init
>()
=
{
super
.<
init
>();
()
};
def
builder
()
:
Builder
=
new
Builder
();
class
Builder
extends
scala
.
AnyRef
{
def
<
init
>()
=
{
super
.<
init
>();
()
};
private
var
i
:
Int
=
0
;
private
var
j
:
Int
=
_
;
private
var
x
:
String
=
_
;
private
var
o
:
Option
[
String
]
=
Some
(
""
);
def
i
(
i
:
Int
)
:
Builder
=
{
this
.
i
=
i
;
this
};
def
j
(
j
:
Int
)
:
Builder
=
{
this
.
j
=
j
;
this
};
def
x
(
x
:
String
)
:
Builder
=
{
this
.
x
=
x
;
this
};
def
o
(
o
:
Option
[
String
])
:
Builder
=
{
this
.
o
=
o
;
this
};
def
build
()
:
TestClass1
=
TestClass1
(
i
,
j
,
x
,
o
)
}
}
```
# How to use
Add library dependency
...
...
src/main/scala/io/github/dreamylost/builder.scala
0 → 100644
浏览文件 @
f7b44644
package
io.github.dreamylost
import
scala.annotation.
{
StaticAnnotation
,
compileTimeOnly
}
import
scala.language.experimental.macros
import
scala.reflect.macros.whitebox
/**
*
* @author 梦境迷离
* @since 2021/6/19
* @version 1.0
*/
@compileTimeOnly
(
"enable macro to expand macro annotations"
)
final
class
builder
extends
StaticAnnotation
{
def
macroTransform
(
annottees
:
Any*
)
:
Any
=
macro
builderMacro
.
impl
}
object
builderMacro
{
def
impl
(
c
:
whitebox.Context
)(
annottees
:
c.Expr
[
Any
]*)
:
c.Expr
[
Any
]
=
{
import
c.universe._
// @see https://scala-lang.org/files/archive/spec/2.13/05-classes-and-objects.html
def
fieldSetMethod
(
c
:
whitebox.Context
)(
field
:
c.universe.Tree
)
:
c.universe.Tree
=
{
import
c.universe._
field
match
{
case
tree
@
q
"$mods var $tname: $tpt = $expr"
=>
q
"""
def $tname($tname: $tpt): Builder = {
this.$tname = $tname
this
}
"""
case
tree
@
q
"$mods val $tname: $tpt = $expr"
=>
q
"""
def $tname($tname: $tpt): Builder = {
this.$tname = $tname
this
}
"""
}
}
def
fieldDefinitionMethod
(
c
:
whitebox.Context
)(
field
:
c.universe.Tree
)
:
c.universe.Tree
=
{
import
c.universe._
field
match
{
case
tree
@
q
"$mods val $tname: $tpt = $expr"
=>
q
"""private var $tname: $tpt = $expr"""
case
tree
@
q
"$mods var $tname: $tpt = $expr"
=>
q
"""private var $tname: $tpt = $expr"""
}
}
def
fieldTermNameMethod
(
c
:
whitebox.Context
)(
field
:
c.universe.Tree
)
:
c.universe.Tree
=
{
import
c.universe._
field
match
{
case
tree
@
q
"$mods val $tname: $tpt = $expr"
=>
q
"""$tname"""
case
tree
@
q
"$mods var $tname: $tpt = $expr"
=>
q
"""$tname"""
}
}
def
builderTemplate
(
typeName
:
TypeName
,
fields
:
List
[
Tree
],
isCase
:
Boolean
)
:
c.universe.Tree
=
{
val
termName
=
typeName
.
toTermName
.
toTermName
val
builderFieldMethods
=
fields
.
map
(
f
=>
fieldSetMethod
(
c
)(
f
))
val
builderFieldDefinitions
=
fields
.
map
(
f
=>
fieldDefinitionMethod
(
c
)(
f
))
val
allFieldsTermName
=
fields
.
map
(
f
=>
fieldTermNameMethod
(
c
)(
f
))
q
"""
def builder(): Builder = new Builder()
class Builder {
..$builderFieldDefinitions
..$builderFieldMethods
def build(): $typeName = ${if (isCase) q"$termName(..$allFieldsTermName)" else q"new $typeName(..$allFieldsTermName)"}
}
"""
}
def
modifiedCompanion
(
compDeclOpt
:
Option
[
ModuleDef
],
builder
:
Tree
,
className
:
TypeName
)
:
c.universe.Tree
=
{
compDeclOpt
map
{
compDecl
=>
// Add the builder to the existing companion object
val
q
"object $obj extends ..$bases { ..$body }"
=
compDecl
val
o
=
q
"""
object $obj extends ..$bases {
..$body
..$builder
}
"""
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedCompanion className: $className, exists obj: $o"
,
force
=
true
)
o
}
getOrElse
{
// Create a companion object with the builder
val
o
=
q
"object ${className.toTermName} { ..$builder }"
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedCompanion className: $className, new obj: $o"
,
force
=
true
)
o
}
}
def
modifiedDeclaration
(
classDecl
:
ClassDef
,
compDeclOpt
:
Option
[
ModuleDef
]
=
None
)
:
c.Expr
[
Nothing
]
=
{
val
(
mods
,
className
,
fields
)
=
classDecl
match
{
case
q
"$mods class $className(..$fields) extends ..$bases { ..$body }"
=>
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration className: $className, fields: $fields"
,
force
=
true
)
(
mods
,
className
,
fields
)
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
s
"Annotation is only supported on class. classDef: $classDecl"
)
}
c
.
info
(
c
.
enclosingPosition
,
s
"modifiedDeclaration compDeclOpt: $compDeclOpt, fields: $fields"
,
force
=
true
)
className
match
{
case
tp
:
TypeName
=>
val
builder
=
builderTemplate
(
tp
,
fields
.
asInstanceOf
[
List
[
Tree
]],
mods
.
asInstanceOf
[
Modifiers
].
hasFlag
(
Flag
.
CASE
))
val
compDecl
=
modifiedCompanion
(
compDeclOpt
,
builder
,
tp
)
c
.
info
(
c
.
enclosingPosition
,
s
"builder: $builder, compDecl: $compDecl"
,
force
=
true
)
// Return both the class and companion object declarations
c
.
Expr
(
q
"""
$classDecl
$compDecl
"""
)
}
}
c
.
info
(
c
.
enclosingPosition
,
s
"builder annottees: $annottees"
,
true
)
val
resTree
=
annottees
.
map
(
_
.
tree
)
match
{
case
(
classDecl
:
ClassDef
)
::
Nil
=>
modifiedDeclaration
(
classDecl
)
case
(
classDecl
:
ClassDef
)
::
(
compDecl
:
ModuleDef
)
::
Nil
=>
modifiedDeclaration
(
classDecl
,
Some
(
compDecl
))
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
"Invalid annottee"
)
}
// Print the ast
c
.
info
(
c
.
enclosingPosition
,
"\n###### Expanded macro ######\n"
+
resTree
.
toString
()
+
"\n###### Expanded macro ######\n"
,
force
=
true
)
resTree
}
}
src/main/scala/io/github/dreamylost/json.scala
浏览文件 @
f7b44644
...
...
@@ -77,6 +77,8 @@ object jsonMacro {
}
c
.
info
(
c
.
enclosingPosition
,
s
"json annottees: $annottees"
,
true
)
annottees
.
map
(
_
.
tree
)
match
{
case
(
classDecl
:
ClassDef
)
::
Nil
=>
modifiedDeclaration
(
classDecl
)
case
(
classDecl
:
ClassDef
)
::
(
compDecl
:
ModuleDef
)
::
Nil
=>
modifiedDeclaration
(
classDecl
,
Some
(
compDecl
))
...
...
src/main/scala/io/github/dreamylost/toString.scala
浏览文件 @
f7b44644
...
...
@@ -8,9 +8,9 @@ import scala.reflect.macros.whitebox
* toString for classes
*
* @author 梦境迷离
* @param verbose
Whether to enable detailed log.
* @param verbose Whether to enable detailed log.
* @param withInternalField Whether to include the fields defined within a class.
* @param withFieldName
Whether to include the name of the field in the toString.
* @param withFieldName Whether to include the name of the field in the toString.
* @since 2021/6/13
* @version 1.0
*/
...
...
@@ -102,11 +102,13 @@ object stringMacro {
case
q
"new toString()"
=>
(
false
,
true
,
true
)
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
"unexpected annotation pattern!"
)
}
c
.
info
(
c
.
enclosingPosition
,
s
"toString annottees: $annottees"
,
true
)
val
argument
=
Argument
(
arg
.
_1
,
arg
.
_2
,
arg
.
_3
)
// Check the type of the class, which can only be defined on the ordinary class
val
annotateeClass
:
ClassDef
=
annottees
.
map
(
_
.
tree
).
toList
match
{
case
(
claz
:
ClassDef
)
::
Nil
=>
claz
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
"Unexpected annottee. Only applicable to class definitions."
)
case
(
classDecl
:
ClassDef
)
::
Nil
=>
classDecl
case
(
classDecl
:
ClassDef
)
::
(
compDecl
:
ModuleDef
)
::
Nil
=>
classDecl
case
_
=>
c
.
abort
(
c
.
enclosingPosition
,
"Unexpected annottee. Only applicable to class definitions."
)
}
val
isCase
:
Boolean
=
{
annotateeClass
match
{
...
...
src/test/scala/io/github/dreamylost/BuilderTest.scala
0 → 100644
浏览文件 @
f7b44644
package
io.github.dreamylost
import
org.scalatest.
{
FlatSpec
,
Matchers
}
/**
*
* @author 梦境迷离
* @since 2021/6/19
* @version 1.0
*/
class
BuilderTest
extends
FlatSpec
with
Matchers
{
"builder1"
should
"case class, non companion object"
in
{
@builder
case
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
// field : <caseaccessor> <paramaccessor> val i: Int = 0, so default value is "_"
val
ret
=
TestClass1
.
builder
().
i
(
1
).
j
(
0
).
x
(
"x"
).
build
()
println
(
ret
)
assert
(
ret
.
toString
==
"TestClass1(1,0,x,Some())"
)
}
"builder2"
should
"case class with companion object"
in
{
@builder
case
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
object
TestClass1
val
ret
=
TestClass1
.
builder
().
i
(
1
).
j
(
0
).
x
(
"x"
).
build
()
println
(
ret
)
assert
(
ret
.
toString
==
"TestClass1(1,0,x,Some())"
)
}
"builder3"
should
"class with toString, non companion object"
in
{
@toString
//"toString" must be before "builder"
@builder
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
val
ret
=
TestClass1
.
builder
().
i
(
1
).
j
(
0
).
x
(
"x"
).
build
()
println
(
ret
)
assert
(
ret
.
toString
==
"TestClass1(i=1, j=0, x=x, o=Some())"
)
}
"builder4"
should
"class toString and companion object"
in
{
@toString
@builder
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
object
TestClass1
val
ret
=
TestClass1
.
builder
().
i
(
1
).
j
(
0
).
x
(
"x"
).
build
()
println
(
ret
)
assert
(
ret
.
toString
==
"TestClass1(i=1, j=0, x=x, o=Some())"
)
}
"builder5"
should
"case class with toString and companion object"
in
{
@toString
@builder
case
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
object
TestClass1
val
ret
=
TestClass1
.
builder
().
i
(
1
).
j
(
0
).
x
(
"x"
).
build
()
println
(
ret
)
assert
(
ret
.
toString
==
"TestClass1(i=1, j=0, x=x, o=Some())"
)
}
"builder6"
should
"case class with toString, non companion object"
in
{
@toString
@builder
case
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
val
ret
=
TestClass1
.
builder
().
i
(
1
).
j
(
0
).
x
(
"x"
).
build
()
println
(
ret
)
assert
(
ret
.
toString
==
"TestClass1(i=1, j=0, x=x, o=Some())"
)
}
}
src/test/scala/io/github/dreamylost/JsonTest.scala
浏览文件 @
f7b44644
...
...
@@ -11,7 +11,7 @@ import play.api.libs.json.Json
*/
class
JsonTest
extends
FlatSpec
with
Matchers
{
// class must be wr
ited
here
// class must be wr
ote
here
@json
case
class
TestClass1
(
val
i
:
Int
=
0
,
var
j
:
Int
,
x
:
String
,
o
:
Option
[
String
]
=
Some
(
""
))
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录