未验证 提交 beb77b36 编写于 作者: 梦境迷离's avatar 梦境迷离 提交者: GitHub

add `@log`, `@apply` and refactor code (#22)

上级 e1f5fd02
# scala-macro-tools [![Build](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml/badge.svg)](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml)
Motivation
我写该库的动机
--
Learn Scala macro and abstract syntax tree.
学习Scala宏编程(macro)和抽象语法树(ast)。
> The project is currently experimental
> 本项目目前处于实验阶段
# Features
[中文说明](./README.md)|[English](./README_EN.md)
# 功能
- `@toString`
- `@json`
- `@builder`
- `@synchronized`
- `@log`
- `@apply`
## @toString
The `@toString` used to generate `toString` for Scala classes or a `toString` with parameter names for the case classes.
`@toString`注解用于为Scala类生成`toString`方法。
- Note
- `verbose` Whether to enable detailed log.
- `includeFieldNames` Whether to include the names of the field in the `toString`.
- `includeInternalFields` Whether to include the fields defined within a class. Not in a primary constructor.
- `callSuper` Whether to include the super's `toString`. Not support if super class is a trait.
- Support `case class` and `class`.
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `includeFieldNames` 指定是否在`toString`中包含字段的名称。可选,默认`true`
- `includeInternalFields` 指定是否包含类内部定义的字段。它们不是在主构造函数中。可选,默认`true`
- `callSuper` 指定是否包含`super``toString`方法值。如果超级类是一种特质,则不支持。可选,默认`false`
- 支持普通类和样例类。
- Example
- 示例
```scala
class TestClass(val i: Int = 0, var j: Int) {
......@@ -32,28 +42,26 @@ class TestClass(val i: Int = 0, var j: Int) {
println(new TestClass(1, 2));
```
|includeInternalFields / includeFieldNames| false |true
| --------------------------------- | ---------------------------------- |----------------------------------|
|false|```TestClass(1, 2)``` |```TestClass(i=0, j=2)```|
|true|```TestClass(1, 2, 0, hello, world)```|```TestClass(i=1, j=2, y=0, z=hello, x=world)```|
| includeInternalFields / includeFieldNames | false | true |
| ----------------------------------------- | -------------------------------------- | ------------------------------------------------ |
| false | ```TestClass(1, 2)``` | ```TestClass(i=0, j=2)``` |
| true | ```TestClass(1, 2, 0, hello, world)``` | ```TestClass(i=1, j=2, y=0, z=hello, x=world)``` |
## @json
The `@json` scala macro annotation is the quickest way to add a JSON format to your Play project's case classes.
`@json`注解是向Play项目的样例类添加json format对象的最快方法。
- Note
- This annotation is drawn from [json-annotation](https://github.com/kifi/json-annotation) and have some
optimization.
- It can also be used when there are other annotations on the case classes.
- Only an implicit `val` was generated automatically(Maybe generate a companion object if it not exists), and there are no other
operations.
- Example
- 说明
- 此注释启发来自[json-annotation](https://github.com/kifi/json-annotation),并做了优化,现在它可以与其他注解同时使用。
- 只有一个隐式的`val`值会被自动生成(如果伴生对象不存在的话,还会生成一个伴生对象用于存放该隐式值),此外没有其他的操作。
- 示例
```scala
@json case class Person(name: String, age: Int)
```
You can now serialize/deserialize your objects using Play's convenience methods:
现在,您可以使用Play的转化方法序列化或反序列化对象:
```scala
import play.api.libs.json._
......@@ -65,15 +73,16 @@ Json.fromJson[Person](json)
## @builder
The `@builder` used to generate builder pattern for Scala classes.
`@builder`注解用于为Scala类生成构造器模式。
- Note
- Support `case class` / `class`.
- It can be used with `@toString`. But `@builder` needs to be put in the back.
- If there is no companion object, one will be generated to store the `builder` method and `Builder` class.
- IDE support is not very good, a red prompt will appear, but the compilation is OK.
- 说明
- 支持普通类和样例类。
- 如果该类没有伴生对象,将生成一个伴生对象来存储`builder`方法和类。
- 目前不支持主构造函数是柯里化的。
> IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。这意味着,目前不支持语法提示。
- Example
- 示例
```scala
@builder
......@@ -83,7 +92,7 @@ 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 {
......@@ -124,26 +133,25 @@ object TestClass1 extends scala.AnyRef {
## @synchronized
The `@synchronized` is a more convenient and flexible synchronous annotation.
`@synchronized`注解是一个更方便、更灵活的用于同步方法的注解。
- Note
- `lockedName` The name of custom lock obj.
- Support static and instance methods.
- It can customize the lock object, and the default is `this`.
- 说明
- `lockedName` 指定自定义的锁对象的名称。可选,默认`this`
- 支持静态方法(`object`中的函数)和实例方法(`class`中的函数)。
- Example
- 示例
```scala
private final val obj = new Object
@synchronized(lockedName = "obj") // The default is this. If you fill in a non existent field name, the compilation will fail.
@synchronized(lockedName = "obj") // 如果您填写一个不存在的字段名,编译将失败。
def getStr3(k: Int): String = {
k + ""
}
// or
@synchronized //use this
// 或者
@synchronized //使用 this 作为锁对象
def getStr(k: Int): String = {
k + ""
}
......@@ -152,43 +160,88 @@ def getStr(k: Int): String = {
Compiler intermediate code:
```scala
// Note that it will not judge whether synchronized already exists, so if synchronized already exists, it will be used twice.
// For example `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// It is not sure whether it will be optimized at the bytecode level.
// 注意,它不会判断synchronized是否已经存在,因此如果synchronized已经存在,它将被使用两次。如下
// `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// 目前还不确定是否在字节码级别会被优化。
def getStr(k: Int): String = this.synchronized(k.$plus(""))
```
# How to use
## @log
`@log`注解不使用混入和包装,而是直接使用宏生成默认的log对象来操作log。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- `logType` 指定需要生成的`log`的类型。可选,默认`JLog`
- `io.github.dreamylost.LogType.JLog` 使用 `java.util.logging.Logger`
- `io.github.dreamylost.LogType.Log4j2` 使用 `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.LogType.Slf4j` 使用 `org.slf4j.Logger`
- 支持普通类,样例类,单例对象。
> IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。这意味着,目前不支持语法提示。
- 示例
```scala
@log(verbose = true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}
@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
```
## @apply
`@apply`注解用于为普通类的主构造函数生成`apply`方法。
- 说明
- `verbose` 指定是否开启详细编译日志。可选,默认`false`
- 仅支持在`class`上使用。
- 仅支持主构造函数。
- 目前不支持主构造函数是柯里化的。
> IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。这意味着,目前不支持语法提示。
- 示例
```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2))
```
# 如何使用
添加库依赖,在sbt中
Add library dependency
> 在gradle,maven中,通常`scala-macro-tools`被替换为`scala-macro-tools_2.12`这种。其中,`2.12`表示Scala版本号。
```scala
"io.github.jxnu-liguobin" %% "scala-macro-tools" % "<VERSION>"
```
The artefacts have been uploaded to Maven Central.
该库已发布到maven中央仓库,请使用最新版本。
| Library Version | Scala 2.11 | Scala 2.12 | Scala 2.13 |
|---------|------------|------------|------------|
| 0.0.5 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5/jar) |
| 0.0.4 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4/jar) |
| 0.0.3 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3/jar) |
| 0.0.2 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2/jar) |
| 0.0.1 |-|-| [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1/jar) |
| Library Version | Scala 2.11 | Scala 2.12 | Scala 2.13 |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0.0.5 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5/jar) |
| 0.0.4 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4/jar) |
| 0.0.3 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3/jar) |
| 0.0.2 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2/jar) |
| 0.0.1 | - | - | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1/jar) |
Importing the library into your build system (e.g gradle, sbt), is not enough. You need to follow an extra step.
仅将本库导入构建系统(例如gradle、sbt)是不够的。你需要多走一步。
| Scala 2.11 | Scala 2.12 | Scala 2.13 |
|------------|-------------|------------|
| Import macro paradise plugin | Import macro paradise plugin | Enable compiler flag `-Ymacro-annotations` required |
| Scala 2.11 | Scala 2.12 | Scala 2.13 |
| ------------------------ | ------------------------ | ------------------------------------- |
| 导入 macro paradise 插件 | 导入 macro paradise 插件 | 开启 编译器标记 `-Ymacro-annotations` |
```scala
addCompilerPlugin("org.scalamacros" % "paradise_<your-scala-version>" % "<plugin-version>")
```
Where `<your-scala-version>` must be the full scala version. For example 2.12.13, and not 2.12.
`<your-scala-version>`必须是Scala版本号的完整编号,如`2.12.13`,而不是`2.12`
If that doesn't work, google for alternatives.
如果这不起作用,可以谷歌寻找替代品。
In version scala`2.13.x`, the functionality of macro paradise has been included in the scala compiler directly. However,
you must still enable the compiler flag `-Ymacro-annotations`.
`scala 2.13.x`版本中,macro paradise的功能直接包含在scala编译器中。然而,仍然必须启用编译器标志`-Ymacro annotations`
# scala-macro-tools [![Build](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml/badge.svg)](https://github.com/jxnu-liguobin/scala-macro-tools/actions/workflows/ScalaCI.yml)
Motivation
--
Learn Scala macro and abstract syntax tree.
> The project is currently experimental
[中文说明](./README.md)|[English](./README_EN.md)
# Features
- `@toString`
- `@json`
- `@builder`
- `@synchronized`
- `@log`
- `@apply`
## @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.
- `includeFieldNames` Whether to include the names of the field in the `toString`, default is `true`.
- `includeInternalFields` Whether to include the fields defined within a class. Not in a primary constructor, default is `true`.
- `callSuper` Whether to include the super's `toString`, default is `false`. Not support if super class is a trait.
- Support `case class` and `class`.
- Currying is not supported in constructors at present.
- Example
```scala
class TestClass(val i: Int = 0, var j: Int) {
val y: Int = 0
var z: String = "hello"
var x: String = "world"
}
println(new TestClass(1, 2));
```
| includeInternalFields / includeFieldNames | false | true |
| ----------------------------------------- | -------------------------------------- | ------------------------------------------------ |
| false | ```TestClass(1, 2)``` | ```TestClass(i=0, j=2)``` |
| true | ```TestClass(1, 2, 0, hello, world)``` | ```TestClass(i=1, j=2, y=0, z=hello, x=world)``` |
## @json
The `@json` scala macro annotation is the quickest way to add a JSON format to your Play project's case classes.
- Note
- This annotation is drawn from [json-annotation](https://github.com/kifi/json-annotation) and have some
optimization.
- It can also be used when there are other annotations on the case classes.
- Only an implicit `val` was generated automatically(Maybe generate a companion object if it not exists), and there are no other
operations.
- Example
```scala
@json case class Person(name: String, age: Int)
```
You can now serialize/deserialize your objects using Play's convenience methods:
```scala
import play.api.libs.json._
val person = Person("Victor Hugo", 46)
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`.
- If there is no companion object, one will be generated to store the `builder` class and method.
- Currying is not supported in constructors at present.
> IDE support is not very good, a red prompt will appear, but the compilation is OK. It only for the fields in the primary constructor
- 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)
}
}
```
## @synchronized
The `@synchronized` is a more convenient and flexible synchronous annotation.
- Note
- `lockedName` The name of the custom lock obj, default is `this`.
- Support static and instance methods.
- Example
```scala
private final val obj = new Object
@synchronized(lockedName = "obj") // The default is this. If you fill in a non existent field name, the compilation will fail.
def getStr3(k: Int): String = {
k + ""
}
// or
@synchronized //use this
def getStr(k: Int): String = {
k + ""
}
```
Compiler intermediate code:
```scala
// Note that it will not judge whether synchronized already exists, so if synchronized already exists, it will be used twice.
// For example `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// It is not sure whether it will be optimized at the bytecode level.
def getStr(k: Int): String = this.synchronized(k.$plus(""))
```
## @log
The `@log` does not use mixed or wrapper, but directly uses macro to generate default log object and operate log.
- Note
- `verbose` Whether to enable detailed log.
- `logType` Specifies the type of `log` that needs to be generated, default is `io.github.dreamylost.LogType.JLog`.
- `io.github.dreamylost.LogType.JLog` use `java.util.logging.Logger`
- `io.github.dreamylost.LogType.Log4j2` use `org.apache.logging.log4j.Logger`
- `io.github.dreamylost.LogType.Slf4j` use `org.slf4j.Logger`
- Support `class`, `case class` and `object`.
> IDE support is not very good, a red prompt will appear, but the compilation is OK. You need to provide their dependencies and configuration, please refer to the test.
- Example
```scala
@log(verbose = true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}
@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }
```
## @apply
The `@apply` used to generate `apply` method for primary construction of ordinary classes.
- Note
- `verbose` Whether to enable detailed log.
- Only support `class`.
- Only support **primary construction**.
- Currying is not supported for constructors at present.
> IDE support is not very good, a red prompt will appear, but the compilation is OK. You need to provide their dependencies and configuration, please refer to the test.
- Example
```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2))
```
# How to use
Add library dependency
```scala
"io.github.jxnu-liguobin" %% "scala-macro-tools" % "<VERSION>"
```
The artefacts have been uploaded to Maven Central.
| Library Version | Scala 2.11 | Scala 2.12 | Scala 2.13 |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0.0.5 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.5/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.5/jar) |
| 0.0.4 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.4/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.4/jar) |
| 0.0.3 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.3/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.3/jar) |
| 0.0.2 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.2/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.2/jar) |
| 0.0.1 | - | - | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.1/jar) |
Importing the library into your build system (e.g gradle, sbt), is not enough. You need to follow an extra step.
| Scala 2.11 | Scala 2.12 | Scala 2.13 |
| ---------------------------- | ---------------------------- | --------------------------------------------------- |
| Import macro paradise plugin | Import macro paradise plugin | Enable compiler flag `-Ymacro-annotations` required |
```scala
addCompilerPlugin("org.scalamacros" % "paradise_<your-scala-version>" % "<plugin-version>")
```
Where `<your-scala-version>` must be the full scala version. For example 2.12.13, and not 2.12.
If that doesn't work, google for alternatives.
In version scala`2.13.x`, the functionality of macro paradise has been included in the scala compiler directly. However,
you must still enable the compiler flag `-Ymacro-annotations`.
......@@ -15,6 +15,9 @@ lazy val root = (project in file("."))
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.apache.logging.log4j" % "log4j-api" % "2.14.1" % Test,
"org.apache.logging.log4j" % "log4j-core" % "2.14.1" % Test,
"org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.14.1" % Test,
"com.typesafe.play" %% "play-json" % "2.7.4" % Test,
"org.scalatest" %% "scalatest" % "3.0.9" % Test
), Compile / scalacOptions ++= {
......
package io.github.dreamylost
import scala.reflect.macros.whitebox
/**
* Common methods
*
* @author 梦境迷离
* @since 2021/6/28
* @version 1.0
*/
trait MacroCommon {
def printTree(c: whitebox.Context)(force: Boolean, resTree: c.Tree): Unit = {
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = force
)
}
/**
* Check the class and its companion object, and return the class definition.
*
* @param c
* @param annottees
* @return Return ClassDef
*/
def checkAndReturnClass(c: whitebox.Context)(annottees: c.Expr[Any]*): c.universe.ClassDef = {
import c.universe._
val annotateeClass: ClassDef = annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => classDecl
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => classDecl
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.")
}
annotateeClass
}
/**
* Get companion object if it exists.
*
* @param c
* @param annottees
* @return
*/
def getCompanionObject(c: whitebox.Context)(annottees: c.Expr[Any]*): Option[c.universe.ModuleDef] = {
import c.universe._
annottees.map(_.tree).toList match {
case (classDecl: ClassDef) :: Nil => None
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => Some(compDecl)
case _ => c.abort(c.enclosingPosition, "Unexpected annottee. Only applicable to class definitions.")
}
}
/**
* Modify the associated object itself according to whether there is an associated object.
*
* @param c
* @param annottees
* @param modifyAction The dependent type need aux-pattern in scala2. Now let's get around this.
* @return Return the result of modifyAction
*/
def handleWithImplType(c: whitebox.Context)(annottees: c.Expr[Any]*)
(modifyAction: (c.universe.ClassDef, Option[c.universe.ModuleDef]) => Any): c.Expr[Nothing] = {
import c.universe._
annottees.map(_.tree) match {
case (classDecl: ClassDef) :: Nil => modifyAction(classDecl, None).asInstanceOf[c.Expr[Nothing]]
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifyAction(classDecl, Some(compDecl)).asInstanceOf[c.Expr[Nothing]]
case _ => c.abort(c.enclosingPosition, "Invalid annottee")
}
}
/**
* Expand the class and check whether the class is a case class.
*
* @param c
* @param annotateeClass classDef
* @return Return true if it is a case class
*/
def isCaseClass(c: whitebox.Context)(annotateeClass: c.universe.ClassDef): Boolean = {
import c.universe._
annotateeClass match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
if (mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.info(c.enclosingPosition, "Annotation is used on 'case class'.", force = true)
true
} else false
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class. classDef: $annotateeClass")
}
}
/**
* Expand the constructor and get the field TermName
*
* @param c
* @param field
* @return
*/
def fieldTermNameMethod(c: whitebox.Context)(field: c.universe.Tree): c.universe.Tree = {
import c.universe._
field match {
case q"$mods val $tname: $tpt = $expr" => q"""$tname"""
case q"$mods var $tname: $tpt = $expr" => q"""$tname"""
}
}
def modifiedCompanion(c: whitebox.Context)(
compDeclOpt: Option[c.universe.ModuleDef],
apply: c.Tree, className: c.TypeName): c.universe.Tree = {
import c.universe._
compDeclOpt map { compDecl =>
val q"$mods object $obj extends ..$bases { ..$body }" = compDecl
val o =
q"""
$mods object $obj extends ..$bases {
..$body
..$apply
}
"""
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} { ..$apply }"
c.info(c.enclosingPosition, s"modifiedCompanion className: $className, new obj: $o", force = true)
o
}
}
}
package io.github.dreamylost
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* annotation to generate apply method for primary construction of ordinary classes.
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
* @since 2021/6/30
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class apply(
verbose: Boolean = false
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro applyMacro.impl
}
object applyMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val args: Boolean = c.prefix.tree match {
case q"new apply(verbose=$verbose)" => c.eval[Boolean](c.Expr(verbose))
case q"new apply()" => false
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args)
val annotateeClass: ClassDef = checkAndReturnClass(c)(annottees: _*)
val isCase: Boolean = isCaseClass(c)(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $args, isCase: $isCase", force = args)
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fields) = classDecl match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = args)
(tpname, paramss)
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 = args)
val fieldNames = fields.asInstanceOf[List[List[Tree]]].flatten.map(f => fieldTermNameMethod(c)(f))
val cName = className match {
case t: TypeName => t
}
val compDecl = modifiedCompanion(c)(compDeclOpt, q"""def apply(...$fields): $className = new $className(..$fieldNames)""", cName)
c.Expr(
q"""
$classDecl
$compDecl
""")
}
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = args, resTree.tree)
resTree
}
}
......@@ -5,6 +5,7 @@ import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* annotation to generate builder pattern for classes.
*
* @author 梦境迷离
* @since 2021/6/19
......@@ -16,7 +17,7 @@ final class builder extends StaticAnnotation {
}
object builderMacro {
object builderMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
......@@ -77,64 +78,37 @@ object builderMacro {
"""
}
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)
// The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fields) = classDecl match {
case q"$mods class $tpname[..$tparams](...$paramss) extends ..$bases { ..$body }" =>
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname, paramss)
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"""
val cName = className match {
case t: TypeName => t
}
val isCase = isCaseClass(c)(classDecl)
val builder = builderTemplate(cName, fields.asInstanceOf[List[List[Tree]]].flatten, isCase)
val compDecl = modifiedCompanion(c)(compDeclOpt, builder, cName)
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)
c.info(c.enclosingPosition, s"builder annottees: $annottees", force = 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")
}
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = true, resTree.tree)
// Print the ast
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = true
)
resTree
}
}
......@@ -5,7 +5,7 @@ import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* annotation for case classes
* annotation to generate play-json implicit object for case classes.
*
* @author 梦境迷离
* @since 2021/6/13
......@@ -16,7 +16,7 @@ final class json extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro jsonMacro.impl
}
object jsonMacro {
object jsonMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
......@@ -29,60 +29,38 @@ object jsonMacro {
}
}
def modifiedCompanion(compDeclOpt: Option[ModuleDef], format: Tree, className: TypeName): c.universe.Tree = {
compDeclOpt map { compDecl =>
// Add the formatter to the existing companion object
val q"object $obj extends ..$bases { ..$body }" = compDecl
val o =
q"""
object $obj extends ..$bases {
..$body
$format
}
"""
c.info(c.enclosingPosition, s"modifiedCompanion className: $className, exists obj: $o", force = true)
o
} getOrElse {
// Create a companion object with the formatter
val o = q"object ${className.toTermName} { $format }"
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] = {
// The dependent type need aux-pattern in scala2. Now let's get around this.
def modifiedDeclaration(classDecl: ClassDef, compDeclOpt: Option[ModuleDef] = None): Any = {
val (className, fields) = classDecl match {
case q"$mods class $className(..$fields) extends ..$bases { ..$body }" =>
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$bases { ..$body }" =>
if (!mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl, mods: $mods")
} else {
c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true)
(className, fields)
c.info(c.enclosingPosition, s"modifiedDeclaration className: $tpname, paramss: $paramss", force = true)
(tpname, paramss)
}
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on case class. classDef: $classDecl")
}
c.info(c.enclosingPosition, s"modifiedDeclaration className: $className, fields: $fields", force = true)
className match {
case t: TypeName =>
val format = jsonFormatter(t, fields.asInstanceOf[List[Tree]])
val compDecl = modifiedCompanion(compDeclOpt, format, t)
c.info(c.enclosingPosition, s"format: $format, compDecl: $compDecl", force = true)
// Return both the class and companion object declarations
c.Expr(
q"""
val cName = className match {
case t: TypeName => t
}
val format = jsonFormatter(cName, fields.asInstanceOf[List[List[Tree]]].flatten)
val compDecl = modifiedCompanion(c)(compDeclOpt, format, cName)
c.info(c.enclosingPosition, s"format: $format, compDecl: $compDecl", force = true)
// Return both the class and companion object declarations
c.Expr(
q"""
$classDecl
$compDecl
""")
}
}
c.info(c.enclosingPosition, s"json annottees: $annottees", true)
c.info(c.enclosingPosition, s"json annottees: $annottees", force = true)
val resTree = handleWithImplType(c)(annottees: _*)(modifiedDeclaration)
printTree(c)(force = true, resTree.tree)
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")
}
resTree
}
}
package io.github.dreamylost
import io.github.dreamylost.LogType.LogType
import scala.annotation.{ StaticAnnotation, compileTimeOnly }
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* annotation to generate log.
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
* @param logType Specifies the type of `log` that needs to be generated
* @since 2021/6/28
* @version 1.0
*/
@compileTimeOnly("enable macro to expand macro annotations")
final class log(
verbose: Boolean = false,
logType: LogType.LogType = LogType.JLog
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro logMacro.impl
}
sealed trait BaseLog {
val typ: LogType
def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree
}
object LogType extends Enumeration {
type LogType = Value
val JLog, Log4j2, Slf4j = Value
private lazy val types = Map(
JLog -> JLogImpl,
Log4j2 -> Log4J2Impl,
Slf4j -> Slf4jImpl
)
def getLogImpl(logType: LogType): BaseLog = {
types.getOrElse(logType, default = throw new Exception(s"Not support log: $logType"))
}
}
object JLogImpl extends BaseLog {
override val typ: LogType = LogType.JLog
override def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree = {
import c.universe._
if (isClass) {
q"""private final val log: java.util.logging.Logger = java.util.logging.Logger.getLogger(classOf[${TypeName(t)}].getName)"""
} else {
q"""private final val log: java.util.logging.Logger = java.util.logging.Logger.getLogger(${TermName(t)}.getClass.getName)"""
}
}
}
object Log4J2Impl extends BaseLog {
override val typ: LogType = LogType.Log4j2
override def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree = {
import c.universe._
if (isClass) {
q"""private final val log: org.apache.logging.log4j.Logger = org.apache.logging.log4j.LogManager.getLogger(classOf[${TypeName(t)}].getName)"""
} else {
q"""private final val log: org.apache.logging.log4j.Logger = org.apache.logging.log4j.LogManager.getLogger(${TermName(t)}.getClass.getName)"""
}
}
}
object Slf4jImpl extends BaseLog {
override val typ: LogType = LogType.Slf4j
override def getTemplate(c: whitebox.Context)(t: String, isClass: Boolean): c.Tree = {
import c.universe._
if (isClass) {
q"""private final val log: org.slf4j.Logger = org.slf4j.LoggerFactory.getLogger(classOf[${TypeName(t)}])"""
} else {
q"""private final val log: org.slf4j.Logger = org.slf4j.LoggerFactory.getLogger(${TermName(t)}.getClass)"""
}
}
}
object logMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val args: (Boolean, LogType) = c.prefix.tree match {
case q"new log(logType=$logType)" => (false, c.eval[LogType](c.Expr(logType)))
case q"new log(verbose=$verbose)" => (c.eval[Boolean](c.Expr(verbose)), LogType.JLog)
case q"new log($logType)" => (false, c.eval[LogType](c.Expr(logType)))
case q"new log(verbose=$verbose, logType=$logType)" => (c.eval[Boolean](c.Expr(verbose)), c.eval[LogType](c.Expr(logType)))
case q"new log()" => (false, LogType.JLog)
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
c.info(c.enclosingPosition, s"annottees: $annottees, args: $args", force = args._1)
val logTree = annottees.map(_.tree) match {
// Match a class, and expand, get class/object name.
case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TypeName].toTermName.decodedName.toString, isClass = true)
case (classDef @ q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ =>
LogType.getLogImpl(args._2).getTemplate(c)(tpname.asInstanceOf[TermName].decodedName.toString, isClass = false)
case _ => c.abort(c.enclosingPosition, s"Annotation is only supported on class or object.")
}
// add result into class
val resTree = annottees.map(_.tree) match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }"
case q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: _ =>
q"$mods object $tpname extends { ..$earlydefns } with ..$parents { $self => ..${List(logTree) ::: stats.toList} }"
}
printTree(c)(force = args._1, resTree)
c.Expr[Any](resTree)
}
}
......@@ -5,6 +5,7 @@ import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* annotation to generate synchronized for methods.
*
* @author 梦境迷离
* @param lockedName The name of custom lock obj.
......@@ -20,7 +21,7 @@ final class synchronized(
def macroTransform(annottees: Any*): Any = macro synchronizedMacro.impl
}
object synchronizedMacro {
object synchronizedMacro extends MacroCommon {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
......@@ -47,12 +48,7 @@ object synchronizedMacro {
}
case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a method")
}
// Print the ast
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = args._1
)
printTree(c)(args._1, resTree)
c.Expr[Any](resTree)
}
}
......@@ -5,7 +5,7 @@ import scala.language.experimental.macros
import scala.reflect.macros.whitebox
/**
* toString for classes
* annotation to generate toString for classes.
*
* @author 梦境迷离
* @param verbose Whether to enable detailed log.
......@@ -29,7 +29,7 @@ final class toString(
final case class Argument(verbose: Boolean, includeInternalFields: Boolean, includeFieldNames: Boolean, callSuper: Boolean)
object stringMacro {
object stringMacro extends MacroCommon {
def printField(c: whitebox.Context)(argument: Argument, lastParam: Option[String], field: c.universe.Tree): c.universe.Tree = {
import c.universe._
......@@ -71,7 +71,7 @@ object stringMacro {
case _: ValDef => true
case mem: MemberDef =>
c.info(c.enclosingPosition, s"MemberDef: ${mem.toString}", force = argument.verbose)
if (mem.toString().startsWith("override def toString")) {
if (mem.toString().startsWith("override def toString")) { // TODO better way
c.abort(mem.pos, "'toString' method has already defined, please remove it or not use'@toString'")
}
false
......@@ -131,20 +131,8 @@ object stringMacro {
val argument = Argument(arg._1, arg._2, arg._3, arg._4)
c.info(c.enclosingPosition, s"toString annottees: $annottees", force = argument.verbose)
// Check the type of the class, which can only be defined on the ordinary class
val annotateeClass: ClassDef = annottees.map(_.tree).toList match {
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 {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
if (mods.asInstanceOf[Modifiers].hasFlag(Flag.CASE)) {
c.warning(c.enclosingPosition, "'toString' annotation is used on 'case class'.")
true
} else false
}
}
val annotateeClass: ClassDef = checkAndReturnClass(c)(annottees: _*)
val isCase: Boolean = isCaseClass(c)(annotateeClass)
c.info(c.enclosingPosition, s"impl argument: $argument, isCase: $isCase", force = argument.verbose)
val resMethod = toStringTemplateImpl(c)(argument, annotateeClass)
......@@ -152,12 +140,18 @@ object stringMacro {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..${stats.toList.:+(resMethod)} }"
}
// Print the ast
c.info(
c.enclosingPosition,
"\n###### Expanded macro ######\n" + resTree.toString() + "\n###### Expanded macro ######\n",
force = argument.verbose
)
c.Expr[Any](resTree)
val companionOpt = getCompanionObject(c)(annottees: _*)
val res = if (companionOpt.isEmpty) {
resTree
} else {
val q"$mods object $obj extends ..$bases { ..$body }" = companionOpt.get
val companion = q"$mods object $obj extends ..$bases { ..$body }"
q"""
$resTree
$companion
"""
}
printTree(c)(argument.verbose, res)
c.Expr[Any](res)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="DEBUG">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level (%class{36}.java %L) %M - %msg%n"/>
</Console>
</Appenders>
<loggers>
<root level="DEBUG">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
package io.github.dreamylost
import org.scalatest.{ FlatSpec, Matchers }
/**
*
* @author 梦境迷离
* @since 2021/6/30
* @version 1.0
*/
class ApplyTest extends FlatSpec with Matchers {
"apply1" should "ok at class" in {
// int: Int => private[this] val int: Int = _;
// val j: Int => val j: Int = _;
// apply => def apply(int: Int, j: Int, k: Option[String] = None, t: Option[Long] = Some(1L)): A = new A(int, j, k, t)
"""@toString @apply class A(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))""" should compile
@toString
@apply class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(A2(1, 2))
"""@apply @toString class B(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))""" should compile
@apply
@toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
println(B2(1, 2))
// exists object
"""@apply @toString class B(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L));object B3""" should compile
@apply
@toString class B3(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
object B3
println(B3(1, 2))
}
"apply2" should "failed at class" in {
// FAILED, not support currying!!
"""@apply @toString class C(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))(o: Int = 1)""" shouldNot compile
}
}
......@@ -19,7 +19,6 @@ class BuilderTest extends FlatSpec with Matchers {
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(""))
......@@ -67,4 +66,13 @@ class BuilderTest extends FlatSpec with Matchers {
assert(ret.toString == "TestClass1(i=1, j=0, x=x, o=Some())")
}
"builder7" should "case class with toString and companion object not in order" in {
@builder
@toString //failed when companion object exists, fix in 0.0.6
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())")
}
}
......@@ -12,6 +12,7 @@ import play.api.libs.json.Json
class JsonTest extends FlatSpec with Matchers {
// class must be wrote here
@json
case class TestClass1(val i: Int = 0, var j: Int, x: String, o: Option[String] = Some(""))
......
package io.github.dreamylost
/**
*
* @author 梦境迷离
* @since 2021/6/29
* @version 1.0
*/
object LogMain extends App {
private final val log: java.util.logging.Logger = java.util.logging.Logger.getLogger(LogMain.getClass.getName)
// object is not type
private final val log2: org.apache.logging.log4j.Logger = org.apache.logging.log4j.LogManager.getLogger(LogMain.getClass.getName)
private final val log3: org.slf4j.Logger = org.slf4j.LoggerFactory.getLogger(LogMain.getClass)
log.info("hello1")
log2.info("hello2")
log3.info("hello3")
}
package io.github.dreamylost
import org.scalatest.{ FlatSpec, Matchers }
/**
*
* @author 梦境迷离
* @since 2021/6/28
* @version 1.0
*/
class LogTest extends FlatSpec with Matchers {
"log1" should "ok on class" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
"""@log class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true) class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.LogType.JLog) class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.JLog) class TestClass6(val i: Int = 0, var j: Int)""" should compile
}
"log2" should "ok on case class" in {
"""@log(verbose=true) case class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
"""@log case class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() case class TestClass3(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true) case class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.LogType.JLog) case class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.JLog) case class TestClass6(val i: Int = 0, var j: Int)""" should compile
}
"log3" should "ok on object" in {
"""@log(verbose=true) object TestClass1 {
log.info("hello")
}""" should compile
"""@log object TestClass2""" should compile
"""@log() object TestClass3""" should compile
"""@log(verbose=true) object TestClass4""" should compile
"""@log(logType=io.github.dreamylost.LogType.JLog) object TestClass5""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.JLog) object TestClass6""" should compile
}
"log4 log4j2" should "ok on object" in {
"""@log(verbose=true) object TestClass1 {
log.info("hello")
}""" should compile
"""@log object TestClass2""" should compile
"""@log() object TestClass3""" should compile
"""@log(verbose=true) object TestClass4""" should compile
"""@log(logType=io.github.dreamylost.LogType.Log4j2) object TestClass5""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Log4j2) object TestClass6""" should compile
}
"log5 slf4j" should "ok on object" in {
"""@log(verbose=true) object TestClass1 {
log.info("hello")
}""" should compile
"""@log object TestClass2""" should compile
"""@log() object TestClass3""" should compile
"""@log(verbose=true) object TestClass4""" should compile
"""@log(logType=io.github.dreamylost.LogType.Slf4j) object TestClass5""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) object TestClass6""" should compile
}
"log6 log4j2" should "ok on class" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
"""@log class TestClass2(val i: Int = 0, var j: Int)""" should compile
"""@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true) class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.LogType.Log4j2) class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Log4j2) class TestClass6(val i: Int = 0, var j: Int)""" should compile
}
"log7 slf4j" should "ok on class" in {
"""@log(verbose=true) class TestClass1(val i: Int = 0, var j: Int) {
log.info("hello")
}""" should compile
"""@toString @builder @log class TestClass2(val i: Int = 0, var j: Int)""" should compile //Use with multiple annotations
"""@log() class TestClass3(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true) class TestClass4(val i: Int = 0, var j: Int)""" should compile
"""@log(logType=io.github.dreamylost.LogType.Slf4j) class TestClass5(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int)""" should compile
"""@log(verbose=true, logType=io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile
"""@log(io.github.dreamylost.LogType.Slf4j) class TestClass6(val i: Int = 0, var j: Int){ log.info("hello world") }""" should compile //default verbose is false
}
}
......@@ -8,7 +8,7 @@ import org.scalatest.{ FlatSpec, Matchers }
* @since 2021/6/24
* @version 1.0
*/
class Synchronized extends FlatSpec with Matchers {
class SynchronizedTest extends FlatSpec with Matchers {
"synchronized1" should "is ok at class" in {
@synchronized
......
......@@ -9,7 +9,6 @@ import org.scalatest.{ FlatSpec, Matchers }
* @version 1.0
*/
class ToStringTest extends FlatSpec with Matchers {
"toString1" should "not contains internal field" in {
@toString(false, false, false)
class TestClass(val i: Int = 0, var j: Int) {
......@@ -260,4 +259,5 @@ class ToStringTest extends FlatSpec with Matchers {
// Because not support if super class is a trait
assert(s5.startsWith("TestClass5(super=io.github.dreamylost.ToStringTes") && s5.endsWith("1)"))
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册