README.md 13.9 KB
Newer Older
I
IceMimosa 已提交
1 2 3 4 5 6
# 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) 
[![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.jxnu-liguobin%22%20AND%20a:%22scala-macro-tools_2.13%22)
[![Version](https://img.shields.io/jetbrains/plugin/v/17202-scala-macro-tools)](https://plugins.jetbrains.com/plugin/17202-scala-macro-tools)

梦境迷离's avatar
梦境迷离 已提交
7

8
我写该库的动机
梦境迷离's avatar
梦境迷离 已提交
9
--
梦境迷离's avatar
梦境迷离 已提交
10

11
学习Scala宏编程(macro)和抽象语法树(ast)。
梦境迷离's avatar
梦境迷离 已提交
12

梦境迷离's avatar
梦境迷离 已提交
13
> 本项目目前处于实验阶段,有建议、意见或者问题欢迎提issue。如果本项目对你有帮助,欢迎点个star。
梦境迷离's avatar
梦境迷离 已提交
14

I
IceMimosa 已提交
15
[中文说明](./README.md) | [English](./README_EN.md)
16 17 18 19 20 21 22 23 24 25


# 功能

- `@toString`
- `@json`
- `@builder`
- `@synchronized`
- `@log`
- `@apply`
梦境迷离's avatar
梦境迷离 已提交
26 27 28 29 30 31 32
- `@constructor`

## 已知问题

- 不支持泛型。
- `@constructor``@toString`同时使用,必须放最后。
- IDEA对宏的支持不是很好,所以会出现标红,不过编译没问题,调用结果也符合预期。
梦境迷离's avatar
梦境迷离 已提交
33 34

## @toString
梦境迷离's avatar
梦境迷离 已提交
35

36
`@toString`注解用于为Scala类生成`toString`方法。
梦境迷离's avatar
梦境迷离 已提交
37

38 39 40 41 42 43
- 说明
  - `verbose` 指定是否开启详细编译日志。可选,默认`false`
  - `includeFieldNames` 指定是否在`toString`中包含字段的名称。可选,默认`true`
  - `includeInternalFields` 指定是否包含类内部定义的字段。它们不是在主构造函数中。可选,默认`true`
  - `callSuper`             指定是否包含`super``toString`方法值。如果超级类是一种特质,则不支持。可选,默认`false`
  - 支持普通类和样例类。
梦境迷离's avatar
梦境迷离 已提交
44

45
- 示例
梦境迷离's avatar
梦境迷离 已提交
46

梦境迷离's avatar
梦境迷离 已提交
47
```scala
梦境迷离's avatar
梦境迷离 已提交
48
@toString class TestClass(val i: Int = 0, var j: Int) {
梦境迷离's avatar
梦境迷离 已提交
49 50 51 52
  val y: Int = 0
  var z: String = "hello"
  var x: String = "world"
}
梦境迷离's avatar
梦境迷离 已提交
53

梦境迷离's avatar
pre  
梦境迷离 已提交
54
println(new TestClass(1, 2));
梦境迷离's avatar
梦境迷离 已提交
55
```
梦境迷离's avatar
梦境迷离 已提交
56

57 58 59 60
| 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)``` |
梦境迷离's avatar
梦境迷离 已提交
61

梦境迷离's avatar
梦境迷离 已提交
62 63
## @json

64
`@json`注解是向Play项目的样例类添加json format对象的最快方法。
梦境迷离's avatar
梦境迷离 已提交
65

66
- 说明
梦境迷离's avatar
梦境迷离 已提交
67 68
  - 此注释启发来自[json-annotation](https://github.com/kifi/json-annotation),并做了优化,现在它可以与其他注解同时使用。
  - 只有一个隐式的`val`值会被自动生成(如果伴生对象不存在的话,还会生成一个伴生对象用于存放该隐式值),此外没有其他的操作。
69 70

- 示例
梦境迷离's avatar
梦境迷离 已提交
71 72 73 74 75

```scala
@json case class Person(name: String, age: Int)
```

76
现在,您可以使用Play的转化方法序列化或反序列化对象:
梦境迷离's avatar
梦境迷离 已提交
77 78 79 80 81 82 83 84 85

```scala
import play.api.libs.json._

val person = Person("Victor Hugo", 46)
val json = Json.toJson(person)
Json.fromJson[Person](json)
```

梦境迷离's avatar
梦境迷离 已提交
86
## @builder
梦境迷离's avatar
梦境迷离 已提交
87

88
`@builder`注解用于为Scala类生成构造器模式。
梦境迷离's avatar
梦境迷离 已提交
89

90
- 说明
梦境迷离's avatar
梦境迷离 已提交
91
  - 支持普通类和样例类。
梦境迷离's avatar
梦境迷离 已提交
92
  - 仅支持对主构造函数使用。   
梦境迷离's avatar
梦境迷离 已提交
93
  - 如果该类没有伴生对象,将生成一个伴生对象来存储`builder`方法和类。
梦境迷离's avatar
梦境迷离 已提交
94

95
- 示例
梦境迷离's avatar
梦境迷离 已提交
96 97 98 99

```scala
@builder
case class TestClass1(val i: Int = 0, var j: Int, x: String, o: Option[String] = Some(""))
梦境迷离's avatar
梦境迷离 已提交
100

梦境迷离's avatar
梦境迷离 已提交
101 102 103 104
val ret = TestClass1.builder().i(1).j(0).x("x").build()
assert(ret.toString == "TestClass1(1,0,x,Some())")
```

梦境迷离's avatar
梦境迷离 已提交
105
宏生成的中间代码:
梦境迷离's avatar
梦境迷离 已提交
106

梦境迷离's avatar
梦境迷离 已提交
107 108 109 110 111 112
```scala
object TestClass1 extends scala.AnyRef {
  def <init>() = {
    super.<init>();
    ()
  };
梦境迷离's avatar
梦境迷离 已提交
113 114
  def builder(): TestClass1Builder = new TestClass1Builder();
  class TestClass1Builder extends scala.AnyRef {
梦境迷离's avatar
梦境迷离 已提交
115 116 117 118 119 120 121 122
    def <init>() = {
      super.<init>();
      ()
    };
    private var i: Int = 0;
    private var j: Int = _;
    private var x: String = _;
    private var o: Option[String] = Some("");
梦境迷离's avatar
梦境迷离 已提交
123
    def i(i: Int): TestClass1Builder = {
梦境迷离's avatar
梦境迷离 已提交
124 125 126
      this.i = i;
      this
    };
梦境迷离's avatar
梦境迷离 已提交
127
    def j(j: Int): TestClass1Builder = {
梦境迷离's avatar
梦境迷离 已提交
128 129 130
      this.j = j;
      this
    };
梦境迷离's avatar
梦境迷离 已提交
131
    def x(x: String): TestClass1Builder = {
梦境迷离's avatar
梦境迷离 已提交
132 133 134
      this.x = x;
      this
    };
梦境迷离's avatar
梦境迷离 已提交
135
    def o(o: Option[String]): TestClass1Builder = {
梦境迷离's avatar
梦境迷离 已提交
136 137 138 139 140 141 142 143
      this.o = o;
      this
    };
    def build(): TestClass1 = TestClass1(i, j, x, o)
  }
}
```

梦境迷离's avatar
梦境迷离 已提交
144 145
## @synchronized

146
`@synchronized`注解是一个更方便、更灵活的用于同步方法的注解。
梦境迷离's avatar
梦境迷离 已提交
147

148
- 说明
梦境迷离's avatar
梦境迷离 已提交
149
  - `lockedName` 指定自定义的锁对象的名称。可选,默认`this`
150
  - 支持静态方法(`object`中的函数)和实例方法(`class`中的函数)。
梦境迷离's avatar
梦境迷离 已提交
151

152
- 示例
梦境迷离's avatar
梦境迷离 已提交
153 154 155 156 157

```scala

private final val obj = new Object

158
@synchronized(lockedName = "obj") // 如果您填写一个不存在的字段名,编译将失败。
梦境迷离's avatar
梦境迷离 已提交
159 160 161 162
def getStr3(k: Int): String = {
  k + ""
}

163 164
// 或者
@synchronized //使用 this 作为锁对象
梦境迷离's avatar
梦境迷离 已提交
165 166 167 168 169
def getStr(k: Int): String = {
  k + ""
}
```

梦境迷离's avatar
梦境迷离 已提交
170
宏生成的中间代码:
梦境迷离's avatar
梦境迷离 已提交
171 172

```scala
173 174 175
// 注意,它不会判断synchronized是否已经存在,因此如果synchronized已经存在,它将被使用两次。如下 
// `def getStr(k: Int): String = this.synchronized(this.synchronized(k.$plus("")))
// 目前还不确定是否在字节码级别会被优化。
梦境迷离's avatar
梦境迷离 已提交
176 177 178
def getStr(k: Int): String = this.synchronized(k.$plus(""))
```

179 180 181 182 183 184 185
## @log

`@log`注解不使用混入和包装,而是直接使用宏生成默认的log对象来操作log。

- 说明
  - `verbose` 指定是否开启详细编译日志。可选,默认`false`
  - `logType` 指定需要生成的`log`的类型。可选,默认`JLog`
梦境迷离's avatar
梦境迷离 已提交
186 187 188
    - `io.github.dreamylost.logs.LogType.JLog` 使用 `java.util.logging.Logger`
    - `io.github.dreamylost.logs.LogType.Log4j2` 使用 `org.apache.logging.log4j.Logger`
    - `io.github.dreamylost.logs.LogType.Slf4j` 使用 `org.slf4j.Logger`
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
  - 支持普通类,样例类,单例对象。

- 示例

```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`
梦境迷离's avatar
梦境迷离 已提交
207
  - 仅支持在`class`上使用且仅支持主构造函数。
208 209 210 211 212

- 示例

```scala
@apply @toString class B2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L))
梦境迷离's avatar
梦境迷离 已提交
213 214 215 216 217
println(B2(1, 2, None, None)) //0.1.0,不携带字段的默认值到apply参数中,所以参数都是必传
```

## @constructor

梦境迷离's avatar
梦境迷离 已提交
218
`@constructor`注解用于为普通类生成辅助构造函数。仅当类有内部字段时可用。
梦境迷离's avatar
梦境迷离 已提交
219 220 221 222 223

- 说明
  - `verbose` 指定是否开启详细编译日志。可选,默认`false`
  - `excludeFields` 指定是否需要排除不需要用于构造函数的`var`字段。可选,默认空(所有class内部的`var`字段都将作为构造函数的入参)。
  - 仅支持在`class`上使用。
梦境迷离's avatar
梦境迷离 已提交
224
  - 主构造函数存在柯里化时,内部字段被放置在柯里化的第一个括号块中。(生成的仍然是柯里化的辅助构造)
梦境迷离's avatar
梦境迷离 已提交
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247

- 示例

```scala
@constructor(excludeFields = Seq("c")) //排除c字段。其中,a是val的不需要手动指定,自动排除。
class A2(int: Int, val j: Int, var k: Option[String] = None, t: Option[Long] = Some(1L)) {
  private val a: Int = 1
  var b: Int = 1 // 不携带字段的默认值到apply参数中,所以参数都是必传
  protected var c: Int = _

  def helloWorld: String = "hello world"
}

println(new A2(1, 2, None, None, 100))
```

宏生成的中间代码(仅构造函数部分):

```scala
def <init>(int: Int, j: Int, k: Option[String], t: Option[Long], b: Int) = {
  <init>(int, j, k, t);
  this.b = b
}
248 249 250 251 252
```

# 如何使用

添加库依赖,在sbt中
梦境迷离's avatar
梦境迷离 已提交
253

254
> 在gradle,maven中,通常`scala-macro-tools`被替换为`scala-macro-tools_2.12`这种。其中,`2.12`表示Scala版本号。
梦境迷离's avatar
梦境迷离 已提交
255

梦境迷离's avatar
梦境迷离 已提交
256 257 258 259
```scala
"io.github.jxnu-liguobin" %% "scala-macro-tools" % "<VERSION>"
```

260
该库已发布到maven中央仓库,请使用最新版本。
梦境迷离's avatar
梦境迷离 已提交
261

262 263
| Library Version | Scala 2.11                                                                                                                                                                                                  | Scala 2.12                                                                                                                                                                                                  | Scala 2.13                                                                                                                                                                                                  |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
梦境迷离's avatar
梦境迷离 已提交
264
| 0.0.6           | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.11/0.0.6/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.12/0.0.6/jar) | [![Maven Central](https://img.shields.io/maven-central/v/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.6)](https://search.maven.org/artifact/io.github.jxnu-liguobin/scala-macro-tools_2.13/0.0.6/jar) |
265 266 267 268 269
| 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) |
梦境迷离's avatar
梦境迷离 已提交
270

271
仅将本库导入构建系统(例如gradle、sbt)是不够的。你需要多走一步。
梦境迷离's avatar
梦境迷离 已提交
272

273 274 275
| Scala 2.11               | Scala 2.12               | Scala 2.13                            |
| ------------------------ | ------------------------ | ------------------------------------- |
| 导入 macro paradise 插件 | 导入 macro paradise 插件 | 开启 编译器标记 `-Ymacro-annotations` |
梦境迷离's avatar
梦境迷离 已提交
276 277 278 279 280

```scala
addCompilerPlugin("org.scalamacros" % "paradise_<your-scala-version>" % "<plugin-version>")
```

281
`<your-scala-version>`必须是Scala版本号的完整编号,如`2.12.13`,而不是`2.12`
梦境迷离's avatar
梦境迷离 已提交
282

283
如果这不起作用,可以谷歌寻找替代品。
梦境迷离's avatar
梦境迷离 已提交
284

285
`scala 2.13.x`版本中,macro paradise的功能直接包含在scala编译器中。然而,仍然必须启用编译器标志`-Ymacro annotations`