Skip to content

Commit

Permalink
LF: rationalize archive Parser/Reader/Decoder (digital-asset#10239)
Browse files Browse the repository at this point in the history
CHANGELOG_BEGIN
CHANGELOG_END
  • Loading branch information
remyhaemmerle-da authored Jul 14, 2021
1 parent 0043b81 commit caf85a2
Show file tree
Hide file tree
Showing 63 changed files with 245 additions and 313 deletions.
2 changes: 0 additions & 2 deletions compiler/repl-service/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ da_scala_binary(
deps = [
"//compiler/repl-service/protos:repl_service_java_proto",
"//daml-lf/archive:daml_lf_archive_reader",
"//daml-lf/archive:daml_lf_dev_archive_proto_java",
"//daml-lf/data",
"//daml-lf/interpreter",
"//daml-lf/language",
Expand All @@ -51,7 +50,6 @@ da_scala_binary(
"//ledger/ledger-api-common",
"//libs-scala/auth-utils",
"//libs-scala/scala-utils",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:io_grpc_grpc_api",
"@maven//:io_grpc_grpc_core",
"@maven//:io_grpc_grpc_netty",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import akka.actor.ActorSystem
import akka.stream._
import com.daml.auth.TokenHolder
import com.daml.lf.PureCompiledPackages
import com.daml.lf.archive.{Decode, Reader}
import com.daml.lf.data.Ref._
import com.daml.lf.engine.script._
import com.daml.lf.language.Ast._
Expand Down Expand Up @@ -225,7 +224,7 @@ class ReplService(
req: LoadPackageRequest,
respObs: StreamObserver[LoadPackageResponse],
): Unit = {
val (pkgId, pkg) = Decode.decode(Reader.readArchive(req.getPackage.newInput))
val (pkgId, pkg) = archive.ArchiveDecoder.fromByteString(req.getPackage)
val newSignatures = signatures.updated(pkgId, AstUtil.toSignature(pkg))
val newCompiledDefinitions = compiledDefinitions ++
new Compiler(new language.Interface(newSignatures), compilerConfig)
Expand All @@ -241,13 +240,7 @@ class ReplService(
respObs: StreamObserver[RunScriptResponse],
): Unit = {
val lfVer = LanguageVersion(LanguageVersion.Major.V1, LanguageVersion.Minor(req.getMinor))
val dop: Decode.OfPackage[_] = Decode.decoders
.lift(lfVer)
.getOrElse(throw new RuntimeException(s"No decode support for LF ${lfVer.pretty}"))
.decoder
val lfScenarioModule =
dop.protoScenarioModule(Reader.damlLfCodedInputStream(req.getDamlLf1.newInput))
val mod: Ast.Module = dop.decodeScenarioModule(homePackageId, lfScenarioModule)
val mod = archive.moduleDecoder(lfVer, homePackageId).fromByteString(req.getDamlLf1)
val pkg = Package((mainModules + (mod.name -> mod)).values, Seq(), lfVer, None)
// TODO[AH] Provide daml-script package id from REPL client.
val Some(scriptPackageId) = this.signatures.collectFirst {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import java.util.concurrent.atomic.AtomicLong

import akka.stream.Materializer
import com.daml.grpc.adapter.ExecutionSequencerFactory
import com.daml.lf.archive.{Decode, Reader}
import com.daml.lf.archive
import com.daml.lf.data.{assertRight, ImmArray}
import com.daml.lf.data.Ref.{DottedName, Identifier, ModuleName, PackageId, QualifiedName}
import com.daml.lf.engine.script.ledgerinteraction.{IdeLedgerClient, ScriptTimeMode}
Expand Down Expand Up @@ -77,18 +77,6 @@ class Context(val contextId: Context.ContextId, languageVersion: LanguageVersion
newCtx
}

private[this] val dop: Decode.OfPackage[_] = Decode.decoders
.lift(languageVersion)
.getOrElse(
throw Context.ContextException(s"No decode support for LF ${languageVersion.pretty}")
)
.decoder

private def decodeModule(bytes: ByteString): Ast.Module = {
val lfScenarioModule = dop.protoScenarioModule(Reader.damlLfCodedInputStream(bytes.newInput))
dop.decodeScenarioModule(homePackageId, lfScenarioModule)
}

@throws[archive.Error]
def update(
unloadModules: Set[ModuleName],
Expand All @@ -98,13 +86,15 @@ class Context(val contextId: Context.ContextId, languageVersion: LanguageVersion
omitValidation: Boolean,
): Unit = synchronized {

val newModules = loadModules.map(module => decodeModule(module.getDamlLf1))
val newModules = loadModules.map(module =>
archive.moduleDecoder(languageVersion, homePackageId).fromByteString(module.getDamlLf1)
)
modules --= unloadModules
newModules.foreach(mod => modules += mod.name -> mod)

val newPackages =
loadPackages.map { archive =>
Decode.decode(Reader.readArchive(archive.newInput))
loadPackages.map { bytes =>
archive.Decode.decodeArchive(archive.ArchiveParser.fromByteString(bytes))
}.toMap

val modulesToCompile =
Expand Down
2 changes: 0 additions & 2 deletions daml-lf/archive/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,8 @@ da_scala_binary(
name = "decode-tester",
srcs = ["src/test/scala/com/digitalasset/daml/lf/archive/DecodeMain.scala"],
main_class = "com.daml.lf.archive.DecodeMain",
versioned_deps = {"2.13": ["@maven//:com_google_protobuf_protobuf_java"]},
deps = [
":daml_lf_archive_reader",
":daml_lf_dev_archive_proto_java",
"//daml-lf/data",
"//daml-lf/language",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,89 +1,36 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.lf
package archive
package com.daml.lf.archive

import com.daml.lf.data.Ref._
import com.daml.lf.language.Ast._
import com.daml.lf.language.LanguageMajorVersion._
import com.daml.lf.language.LanguageVersion
import com.daml.daml_lf_dev.DamlLf
import com.google.protobuf.CodedInputStream

sealed class Decode(onlySerializableDataDefs: Boolean) {
import Decode._

private[lf] val decoders: PartialFunction[LanguageVersion, PayloadDecoder] = {
case LanguageVersion(V1, minor) if V1.supportedMinorVersions.contains(minor) =>
PayloadDecoder(new DecodeV1(minor))(_.getDamlLf1)
}

def decode(archive: DamlLf.Archive): (PackageId, Package) =
decode(Reader.readArchive(archive))

def decode(payload: ArchivePayload): (PackageId, Package) = {
val decoder =
decoders
.lift(payload.version)
.getOrElse(throw Error.Parsing(s"${payload.version} unsupported"))
(
payload.pkgId,
decoder.decoder.decodePackage(
payload.pkgId,
decoder.extract(payload.proto),
onlySerializableDataDefs,
),
)
}
}

object Decode extends Decode(onlySerializableDataDefs = false) {

/** inlined [[scalaz.ContravariantCoyoneda]]`[OfPackage, DamlLf.ArchivePayload]` */
private[lf] sealed abstract class PayloadDecoder {
type I
val extract: DamlLf.ArchivePayload => I
val decoder: OfPackage[I]
}

private[archive] object PayloadDecoder {
def apply[I0](fi: OfPackage[I0])(k: DamlLf.ArchivePayload => I0): PayloadDecoder =
new PayloadDecoder {
type I = I0
override val extract = k
override val decoder = fi
}
}

private[lf] trait OfPackage[-Pkg] {
type ProtoScenarioModule
def protoScenarioModule(cis: CodedInputStream): ProtoScenarioModule
@throws[Error.Parsing]
def decodePackage(
packageId: PackageId,
lfPackage: Pkg,
onlySerializableDataDefs: Boolean = false,
): Package
@throws[Error.Parsing]
def decodeScenarioModule(packageId: PackageId, lfModuleForScenario: ProtoScenarioModule): Module
}

private def identifierStart(c: Char) =
'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '$' || c == '_'

private def identifierPart(c: Char): Boolean =
identifierStart(c) || '0' <= c && c <= '9'

def checkIdentifier(s: String): Unit = {
if (s.isEmpty)
throw Error.Parsing("empty identifier")
else if (!(identifierStart(s.head) && s.tail.forall(identifierPart)))
throw Error.Parsing(s"identifier $s contains invalid character")
}

private val decimalPattern = "[+-]*[0-9]{0,28}(\\.[0-9]{0,10})*".r.pattern
def checkDecimal(s: String): Boolean =
decimalPattern.matcher(s).matches()
import com.daml.lf.data.Ref.PackageId
import com.daml.lf.language.{Ast, LanguageMajorVersion, LanguageVersion}

object Decode {

// decode an ArchivePayload
def decodeArchivePayload(
payload: ArchivePayload,
onlySerializableDataDefs: Boolean = false,
): (PackageId, Ast.Package) =
payload.version match {
case LanguageVersion(LanguageMajorVersion.V1, minor)
if LanguageMajorVersion.V1.supportedMinorVersions.contains(minor) =>
payload.pkgId ->
new DecodeV1(minor).decodePackage(
payload.pkgId,
payload.proto.getDamlLf1,
onlySerializableDataDefs,
)
case v => throw Error.Parsing(s"$v unsupported")
}

// decode an Archive
def decodeArchive(
archive: DamlLf.Archive,
onlySerializableDataDefs: Boolean = false,
): (PackageId, Ast.Package) =
decodeArchivePayload(Reader.readArchive(archive), onlySerializableDataDefs)

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,19 @@ import com.daml.lf.data.{Decimal, ImmArray, Numeric, Struct, Time}
import com.daml.lf.language.Ast._
import com.daml.lf.language.Util._
import com.daml.lf.language.{LanguageVersion => LV}
import com.google.protobuf.CodedInputStream

import scala.Ordering.Implicits.infixOrderingOps
import scala.collection.compat._
import scala.collection.mutable
import scala.jdk.CollectionConverters._

private[archive] class DecodeV1(minor: LV.Minor) extends Decode.OfPackage[PLF.Package] {
private[archive] class DecodeV1(minor: LV.Minor) {

import DecodeV1._

private val languageVersion = LV(LV.Major.V1, minor)

override def decodePackage(
def decodePackage(
packageId: PackageId,
lfPackage: PLF.Package,
onlySerializableDataDefs: Boolean,
Expand Down Expand Up @@ -91,13 +90,7 @@ private[archive] class DecodeV1(minor: LV.Minor) extends Decode.OfPackage[PLF.Pa
// each LF scenario module is wrapped in a distinct proto package
type ProtoScenarioModule = PLF.Package

override def protoScenarioModule(cis: CodedInputStream): ProtoScenarioModule =
PLF.Package.parser().parseFrom(cis)

override def decodeScenarioModule(
packageId: PackageId,
lfScenarioModule: ProtoScenarioModule,
): Module = {
def decodeScenarioModule(packageId: PackageId, lfScenarioModule: ProtoScenarioModule): Module = {

val internedStrings =
ImmArray(lfScenarioModule.getInternedStringsList.asScala).toSeq
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,44 @@

package com.daml.lf.archive

import com.daml.daml_lf_dev.DamlLf
import com.daml.lf.data.Bytes
import com.daml.lf.data.TryOps.sequence

import java.io.{File, FileInputStream, IOException, InputStream}
import java.io.{File, FileInputStream, IOException}
import java.util.zip.ZipInputStream
import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try, Using}

class GenDarReader[A](parseDalf: Bytes => Try[A]) {
sealed abstract class GenDarReader[A] {
import GenDarReader._

def readArchiveFromFile(
darFile: File,
entrySizeThreshold: Int = EntrySizeThreshold,
): Try[Dar[A]]

def readArchive(
name: String,
darStream: ZipInputStream,
entrySizeThreshold: Int = EntrySizeThreshold,
): Try[Dar[A]]
}

private[archive] final class GenDarReaderImpl[A](reader: GenReader[A]) extends GenDarReader[A] {

import GenDarReader._

/** Reads an archive from a File. */
def readArchiveFromFile(darFile: File): Try[Dar[A]] =
Using(new ZipInputStream(new FileInputStream(darFile)))(readArchive(darFile.getName, _)).flatten
override def readArchiveFromFile(
darFile: File,
entrySizeThreshold: Int = EntrySizeThreshold,
): Try[Dar[A]] =
Using(new ZipInputStream(new FileInputStream(darFile)))(
readArchive(darFile.getName, _, entrySizeThreshold)
).flatten

/** Reads an archive from a ZipInputStream. The stream will be closed by this function! */
def readArchive(
override def readArchive(
name: String,
darStream: ZipInputStream,
entrySizeThreshold: Int = EntrySizeThreshold,
Expand Down Expand Up @@ -68,7 +87,7 @@ class GenDarReader[A](parseDalf: Bytes => Try[A]) {
sequence(names.map(parseOne(getPayload)))

private[this] def parseOne(getPayload: String => Try[Bytes])(s: String): Try[A] =
getPayload(s).flatMap(parseDalf)
getPayload(s).flatMap(bytes => Try(reader.fromBytes(bytes)))

}

Expand All @@ -77,24 +96,17 @@ object GenDarReader {
private val ManifestName = "META-INF/MANIFEST.MF"
private[archive] val EntrySizeThreshold = 1024 * 1024 * 1024 // 1 GB

private[archive] case class ZipEntry(size: Long, getStream: () => InputStream)

private[archive] case class ZipEntries(name: String, entries: Map[String, Bytes]) {
private[GenDarReader] def get(entryName: String): Try[Bytes] = {
private[archive] def get(entryName: String): Try[Bytes] = {
entries.get(entryName) match {
case Some(is) => Success(is)
case None => Failure(Error.InvalidZipEntry(entryName, this))
}
}

private[GenDarReader] def readDalfNames: Try[Dar[String]] =
private[archive] def readDalfNames: Try[Dar[String]] =
get(ManifestName)
.flatMap(DarManifestReader.dalfNames)
.recoverWith { case NonFatal(e1) => Failure(Error.InvalidDar(this, e1)) }
}
}

object DarReader extends GenDarReader[ArchivePayload](is => Try(Reader.readArchive(is)))

object RawDarReader
extends GenDarReader[DamlLf.Archive](is => Try(DamlLf.Archive.parseFrom(is.toByteString)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.lf
package archive

import com.google.protobuf.{ByteString, CodedInputStream}

import scala.util.Using

final class GenReader[X] private[archive] (val fromCodedInputStream: CodedInputStream => X) {

def fromInputStream(is: java.io.InputStream): X =
fromCodedInputStream(CodedInputStream.newInstance(is))

def fromByteArray(bytes: Array[Byte]): X =
fromCodedInputStream(CodedInputStream.newInstance(bytes))

def fromByteString(bytes: ByteString): X =
fromCodedInputStream(CodedInputStream.newInstance(bytes.asReadOnlyByteBuffer()))

def fromBytes(bytes: data.Bytes): X =
fromByteString(bytes.toByteString)

def fromFile(file: java.nio.file.Path): X =
Using.resource(java.nio.file.Files.newInputStream(file))(fromInputStream)

def fromFile(file: java.io.File): X =
fromFile(file.toPath)

private[archive] def andThen[Y](f: X => Y): GenReader[Y] =
new GenReader(x => f(fromCodedInputStream(x)))

}
Loading

0 comments on commit caf85a2

Please sign in to comment.