Language Guide - Scala¶
Installation¶
Prerequisites:
Alex, which can be installed with
stack install alex
Happy, which can be installed with
stack install happy
The Scala target compiler is located in directory lang/scala
,
type stack install lang/scala
.
The compiler consists of only one executable, you can put it anywhere you want.
Usage¶
Usage: gugugu-scala (-i|--input INPUT) (-o|--output OUTPUT)
(-p|--package-prefix PREFIX)
[-t|--field-transformer ARG] [--with-codec]
[--with-client] [--with-server]
Available options:
-i,--input INPUT the directory containing the definition files
-o,--output OUTPUT the directory to put the generated sources
-p,--package-prefix PREFIX
the package prefix, e.g. com.example.foo.generated
-t,--field-transformer ARG
transformer for field names. Possible values are:
id no transformation, default
snake camelCase to snake_case
--with-codec pass this flag to generate codecs, default to false
--with-client pass this flag to generate client, default to false
--with-server pass this flag to generate server, default to false
-h,--help Show this help text
Module¶
Gugugu module is represented by Scala packages, with module name lower-cased, without underscores.
Type Map¶
Primitives¶
Gugugu Type |
Scala Type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Record Type¶
Record type are represented by case class
.
data Book
= Book
{ id :: Int64
, name :: String
}
becomes
case class Book
( id : Long
, name : String
)
Enum Type¶
Enum type are represented by sealed trait
.
data Color
= Red
| Green
| Blue
becomes
sealed trait Color
object Color {
case object Red extends Color
case object Green extends Color
case object Blue extends Color
}
Encoder and Decoder¶
The generated codecs have type
package gugugu
trait Encoder[a] {
def encode[s, r](s: s, a: a, impl: EncoderImpl[s, r]): s
}
trait Decoder[a] {
def decode[s, r](s: s, impl: DecoderImpl[s, r]): (s, a)
}
object Encoder {
def apply[a](implicit encoder: Encoder[a]): Encoder[a]
def encode[s, r, a](a: a, impl: EncoderImpl[s, r])(implicit encoder: Encoder[a]): r
}
object Decoder {
def apply[a](implicit decoder: Decoder[a]): Decoder[a]
def decode[s, r, a](buf: r, impl: DecoderImpl[s, r])(implicit decoder: Decoder[a]): a
}
The encoder and decoder are always defined as implicits of the companion object,
so you can always get them with the expression Encoder[a]
or Decoder[a]
.
The EncoderImpl[s, r]
and DecoderImpl[s, r]
is two values you have to
provide to describe you do you encode the.
Use the Encoder.encode[s, r, a]
to encode a value of type a
to type
r
, with the encoder and the EncoderImpl[s, r]
.
Likewise, ese the Decoder.decode[s, r, a]
to decode a value of type a
from type r
, with the decoder and the DecoderImpl[s, r]
.
The codec are polymorphic over s
and r
,
with the EncoderImpl
/DecoderImpl
provided,
you can encode/decode values to/from any type you want.
The EncoderImpl
/DecoderImpl
are defined as.
package gugugu
trait EncoderImpl[s, r] {
def initializeState(): s
def encodeState(s: s): r
def encodeUnit(s: s, v: Unit): s
def encodeBool(s: s, v: Boolean): s
def encodeInt32(s: s, v: Int): s
def encodeInt64(s: s, v: Long): s
def encodeDouble(s: s, v: Double): s
def encodeString(s: s, v: String): s
def encodeMaybe(s: s, isNothing: Boolean, k: s => s): s
def encodeList(s: s, len: Int, k: s => s): s
def encodeListNth(s: s, i: Int, k: s => s): s
def encodeRecord(s: s, nFields: Int, k: s => s): s
def encodeRecordField( s: s, i: Int
, name: String
, k: s => s
): s
def encodeEnum[a]( s: s
, indexMap: Map[a, Int], nameMap: Map[a, String]
, v: a): s
}
trait DecoderImpl[s, r] {
def decodeState(r: r): s
def finalizeState(s: s): Unit
def decodeUnit(s: s): (s, Unit)
def decodeBool(s: s): (s, Boolean)
def decodeInt32(s: s): (s, Int)
def decodeInt64(s: s): (s, Long)
def decodeDouble(s: s): (s, Double)
def decodeString(s: s): (s, String)
def decodeMaybe[a](s: s, k: (s, Boolean) => (s, a)): (s, a)
def decodeList[a](s: s, k: (s, Int) => (s, a)): (s, a)
def decodeListNth[a](s: s, i: Int, k: s => (s, a)): (s, a)
def decodeRecord[a](s: s, nFields: Int, k: s => (s, a)): (s, a)
def decodeRecordField[a]( s: s, i: Int
, name: String
, k: s => (s, a)
): (s, a)
def decodeEnum[a]( s: s
, indexMap: Map[Int, a], nameMap: Map[String, a]
): (s, a)
}
The type variable s
is the intermediate state of the encoder/decoder,
and the r
is the final representation of the serialized type.
The encoder/decoder state is initialized by initializeState/decodeState,
and finalized by encodeState/finalizeState.
Every function of the EncoderImpl
/DecoderImpl
transforms the state,
it is up to you to use immutable state or mutable state.
Please consult the example located in
examples/impls/scala/src/main/scala/guguguexamples/common/JsonCodecImpl.scala
to write a EncoderImpl
/DecoderImpl
.