README_EN.md 13.5 KB
Newer Older
I
IceMimosa 已提交
1 2 3 4 5
# 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)
6 7 8 9 10 11 12 13

Motivation
--

Learn Scala macro and abstract syntax tree.

> The project is currently experimental

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

# Features

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


## Known Issues

- Currying is not supported.
- Generic is not supported.
- When `@constructor` and `@toString` are used together, the former must be put last.
- IDE support is not very good, a red prompt will appear, but the compilation is OK. 

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

## @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`.

- Example

```scala
梦境迷离's avatar
梦境迷离 已提交
49
@toString class TestClass(val i: Int = 0, var j: Int) {
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
  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
梦境迷离's avatar
梦境迷离 已提交
68 69 70 71 72
  - 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.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
- 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
梦境迷离's avatar
梦境迷离 已提交
94
  - Support `case class` / `class`.
梦境迷离's avatar
梦境迷离 已提交
95
  - Only support for **primary constructor**.  
梦境迷离's avatar
梦境迷离 已提交
96
  - If there is no companion object, one will be generated to store the `builder` class and method.
97 98 99 100 101 102 103 104 105 106 107

- 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())")
```

梦境迷离's avatar
梦境迷离 已提交
108
Compiler macro code:
109 110 111 112 113 114 115

```scala
object TestClass1 extends scala.AnyRef {
  def <init>() = {
    super.<init>();
    ()
  };
梦境迷离's avatar
梦境迷离 已提交
116 117
  def builder(): TestClass1Builder = new TestClass1Builder();
  class TestClass1Builder extends scala.AnyRef {
118 119 120 121 122 123 124 125
    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
梦境迷离 已提交
126
    def i(i: Int): TestClass1Builder = {
127 128 129
      this.i = i;
      this
    };
梦境迷离's avatar
梦境迷离 已提交
130
    def j(j: Int): TestClass1Builder = {
131 132 133
      this.j = j;
      this
    };
梦境迷离's avatar
梦境迷离 已提交
134
    def x(x: String): TestClass1Builder = {
135 136 137
      this.x = x;
      this
    };
梦境迷离's avatar
梦境迷离 已提交
138
    def o(o: Option[String]): TestClass1Builder = {
139 140 141 142 143 144 145 146 147 148 149 150 151
      this.o = o;
      this
    };
    def build(): TestClass1 = TestClass1(i, j, x, o)
  }
}
```

## @synchronized

The `@synchronized` is a more convenient and flexible synchronous annotation.

- Note
梦境迷离's avatar
梦境迷离 已提交
152
  - `lockedName` The name of the custom lock obj, default is `this`.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
  - 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 + ""
}
```

梦境迷离's avatar
梦境迷离 已提交
173
Compiler macro code:
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188

```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`.
梦境迷离's avatar
梦境迷离 已提交
189 190 191
    - `io.github.dreamylost.logs.LogType.JLog` use `java.util.logging.Logger`
    - `io.github.dreamylost.logs.LogType.Log4j2` use `org.apache.logging.log4j.Logger`
    - `io.github.dreamylost.logs.LogType.Slf4j` use `org.slf4j.Logger`
梦境迷离's avatar
梦境迷离 已提交
192
  - Support `class`, `case class` and `object`.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

- 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**.

- 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))
```

梦境迷离's avatar
梦境迷离 已提交
220 221
## @constructor

梦境迷离's avatar
梦境迷离 已提交
222
The `@constructor` used to generate secondary constructor method for classes, only when it has inner fields.
梦境迷离's avatar
梦境迷离 已提交
223 224 225 226 227

- Note
  - `verbose` Whether to enable detailed log.
  - `excludeFields` Whether to exclude the specified `var` fields, default is `Nil`.
  - Only support `class`.
梦境迷离's avatar
梦境迷离 已提交
228
  - The inner fields are placed in the first bracket block if constructor is currying.
梦境迷离's avatar
梦境迷离 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

- Example

```scala
@constructor(excludeFields = Seq("c"))
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 //The default value of the field is not carried to the apply parameter, so all parameters are required.
  protected var c: Int = _

  def helloWorld: String = "hello world"
}

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

Compiler macro code(Only constructor def):

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

254 255 256 257 258 259 260 261 262 263 264 265
# 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                                                                                                                                                                                                  |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
梦境迷离's avatar
梦境迷离 已提交
266
| 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) |
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
| 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`.