diff --git a/.gitignore b/.gitignore index 4dda2bdb..8cdd6a4d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ docs/mdoc-invocation.txt local-bindgen bin worktree-main +/.idea/ diff --git a/modules/bindgen/src/main/scala/Config.scala b/modules/bindgen/src/main/scala/Config.scala index 4e5cf123..efda258f 100644 --- a/modules/bindgen/src/main/scala/Config.scala +++ b/modules/bindgen/src/main/scala/Config.scala @@ -26,6 +26,7 @@ case class Config( linkName: Option[LinkName], indentSize: IndentationSize, indents: Indentation, + useBraces: UseBraces, cImports: List[CImport], clangFlags: List[ClangFlag], quiet: Quiet, @@ -56,6 +57,7 @@ object Config: linkName = None, indentSize = defaults.indentSize, indents = defaults.indents, + useBraces = UseBraces.No, cImports = Nil, clangFlags = Nil, quiet = Quiet.No, @@ -99,6 +101,9 @@ object Quiet extends YesNo[Quiet] opaque type MultiFile = Boolean object MultiFile extends YesNo[MultiFile] +opaque type UseBraces = Boolean +object UseBraces extends YesNo[UseBraces] + enum Lang: case Scala, C diff --git a/modules/bindgen/src/main/scala/cli/arguments.scala b/modules/bindgen/src/main/scala/cli/arguments.scala index e73b4bd6..169cc9f6 100644 --- a/modules/bindgen/src/main/scala/cli/arguments.scala +++ b/modules/bindgen/src/main/scala/cli/arguments.scala @@ -80,6 +80,16 @@ object CLI: .withDefault(2) .map(IndentationSize.apply(_)) + private val bracesNotIndents = + Opts + .flag( + "use-braces", + help = + "Generate Scala part of the binding with braces or significant whitespace" + ) + .orFalse + .map(UseBraces.apply(_)) + private val isScala = Opts.flag("scala", help = "Generate Scala part of the binding").orFalse @@ -378,6 +388,7 @@ object CLI: linkName, indentationSize, indentation, + bracesNotIndents, cImport, (clangInclude, clangFlag).mapN(_ ++ _), quiet, diff --git a/modules/bindgen/src/main/scala/render/alias.scala b/modules/bindgen/src/main/scala/render/alias.scala index 74c84468..357a5785 100644 --- a/modules/bindgen/src/main/scala/render/alias.scala +++ b/modules/bindgen/src/main/scala/render/alias.scala @@ -9,6 +9,7 @@ def alias(model: Def.Alias, line: Appender)(using AliasResolver, Config ): Exported = + val (underlyingType, enableConstructor) = model.underlying match case Pointer(Reference(Name.Unnamed)) => @@ -31,8 +32,7 @@ def alias(model: Def.Alias, line: Appender)(using renderComment(line, model.meta) line(s"${modifier}type ${model.name} = ${scalaType(underlyingType)}") - line(s"object ${sanitiseBeforeColon(model.name)}: ") - nest { + objectBlock(line)(s"object ${sanitiseBeforeColon(model.name)}") { model.underlying match case Reference(Name.BuiltIn(name)) => line(s"val _tag: Tag[${model.name}] = summon[Tag[${name.full}]]") @@ -50,8 +50,7 @@ def alias(model: Def.Alias, line: Appender)(using line( s"inline def apply(inline o: ${scalaType(underlyingType)}): ${model.name} = o" ) - line(s"extension (v: ${model.name})") - nest { + defBlock(line)(s"extension (v: ${model.name})") { line(s"inline def value: ${scalaType(underlyingType)} = v") if isFunctionPointer then line(s"inline def toPtr: $voidPtr = CFuncPtr.toPtr(v)") diff --git a/modules/bindgen/src/main/scala/render/binding.scala b/modules/bindgen/src/main/scala/render/binding.scala index 49b1a979..ab8e6f06 100644 --- a/modules/bindgen/src/main/scala/render/binding.scala +++ b/modules/bindgen/src/main/scala/render/binding.scala @@ -220,13 +220,14 @@ def renderBinding( if !multiFileMode && hasAnyTypes then val l = to(simpleStream("types")) - l("object types:") - nest { - val l = to(simpleStream("types")) - if hasStructs then l(s"export _root_.${packageName}.structs.*") - if hasAliases then l(s"export _root_.${packageName}.aliases.*") - if hasUnions then l(s"export _root_.${packageName}.unions.*") - if hasAnyEnums then l(s"export _root_.${packageName}.enumerations.*") + objectBlock(l)("object types") { + nest { + val l = to(simpleStream("types")) + if hasStructs then l(s"export _root_.${packageName}.structs.*") + if hasAliases then l(s"export _root_.${packageName}.aliases.*") + if hasUnions then l(s"export _root_.${packageName}.unions.*") + if hasAnyEnums then l(s"export _root_.${packageName}.enumerations.*") + } } end if @@ -245,6 +246,24 @@ private def commentException(element: Any, exc: Throwable) = throw exc end commentException +//wraps a block in the supplied object/class/trait if the render mode is such +private def maybeObjectBlock(out: LineBuilder, mode: RenderMode)( + objectHeader: String +)( + f: Config ?=> Unit +)(using Config) = + lazy val openDelimiter: String = + if summon[Config].useBraces.value then " {" else ":" + lazy val closeDelimiter: Option[String] = + if summon[Config].useBraces.value then Some("}") else None + + if mode == RenderMode.Objects then to(out)(s"$objectHeader$openDelimiter") + nestIf(mode == RenderMode.Objects) { + f + } + if mode == RenderMode.Objects then closeDelimiter.foreach(to(out)) +end maybeObjectBlock + private def renderAliases( aliases: List[Def.Alias], out: LineBuilder, @@ -252,9 +271,7 @@ private def renderAliases( typeImports: TypeImports )(using Config, AliasResolver, Context) = val exported = List.newBuilder[Exported] - if mode == RenderMode.Objects then out.appendLine("object aliases:") - - nestIf(mode == RenderMode.Objects) { + maybeObjectBlock(out, mode)("object aliases") { if mode == RenderMode.Objects then typeImports.render(out) exported ++= renderAll(aliases, out, alias) } @@ -270,8 +287,7 @@ private def renderExports( case RenderMode.Objects => if exports.nonEmpty then out.emptyLine - to(out)("object all:") - nest { + maybeObjectBlock(out, mode)("object all") { exports.distinct.foreach { (scope, name) => to(out)(s"export _root_.$packageName.$scope.$name") } @@ -290,10 +306,7 @@ private def renderUnions( typeImports: TypeImports )(using Config, AliasResolver, Context) = val exported = List.newBuilder[Exported] - - if mode == RenderMode.Objects then out.appendLine("object unions:") - - nestIf(mode == RenderMode.Objects) { + maybeObjectBlock(out, mode)("object unions") { if mode == RenderMode.Objects then typeImports.render(out) exported ++= renderAll(unions, out, union) } @@ -307,9 +320,7 @@ private def renderStructs( typeImports: TypeImports )(using Config, AliasResolver, Context) = val exported = List.newBuilder[Exported] - if mode == RenderMode.Objects then out.appendLine("object structs:") - - nestIf(mode == RenderMode.Objects) { + maybeObjectBlock(out, mode)("object structs") { if mode == RenderMode.Objects then typeImports.render(out) exported ++= renderAll(structs, out, struct) } @@ -322,8 +333,7 @@ private def renderConstants( mode: RenderMode )(using Config, AliasResolver) = if enums.nonEmpty then - if mode == RenderMode.Objects then to(out)("object constants:") - nestIf(mode == RenderMode.Objects) { + maybeObjectBlock(out, mode)("object constants") { constants(Constants(enums), to(out)) } @@ -392,17 +402,18 @@ private def enumPredef( val traitName = enumBaseTraitName(intType) - lb.appendLine( - s"private[${safePackageName}] trait $traitName[T](using eq: T =:= $renderedScalaType):" - ) - lb.appendLine(s" given Tag[T] = Tag.$renderedTagName.asInstanceOf[Tag[T]]") - lb.appendLine(s" extension (inline t: T)") - lb.appendLine(s" inline def value: $renderedScalaType = eq.apply(t)") - if intType.base == IntegralBase.Int then - lb.appendLine(s" inline def int: CInt = eq.apply(t).toInt") - if intType.sign == SignType.Unsigned then - lb.appendLine(s" inline def uint: CUnsignedInt = eq.apply(t)") - + objectBlock(to(lb))( + s"private[${safePackageName}] trait $traitName[T](using eq: T =:= $renderedScalaType)" + ) { + to(lb)(s"given Tag[T] = Tag.$renderedTagName.asInstanceOf[Tag[T]]") + defBlock(to(lb))("extension (inline t: T)") { + to(lb)(s"inline def value: $renderedScalaType = eq.apply(t)") + if intType.base == IntegralBase.Int then + to(lb)(s"inline def int: CInt = eq.apply(t).toInt") + if intType.sign == SignType.Unsigned then + to(lb)(s"inline def uint: CUnsignedInt = eq.apply(t)") + } + } lb.result.linesIterator.toList end enumPredef @@ -420,15 +431,13 @@ private def renderEnumerations( val exported = List.newBuilder[Exported] if enumBases.nonEmpty then - if mode == RenderMode.Objects then out.appendLine("object predef:") - nestIf(mode == RenderMode.Objects) { + maybeObjectBlock(out, mode)("object predef") { val safePackageName = packageName.split('.').last enumBases.foreach: base => enumPredef(safePackageName, base).foreach(to(out)) } - if mode == RenderMode.Objects then - out.appendLine("\n\nobject enumerations:") - nestIf(mode == RenderMode.Objects) { + if mode == RenderMode.Objects then out.emptyLine + maybeObjectBlock(out, mode)("object enumerations") { if mode == RenderMode.Objects then to(out)("import predef.*") exported ++= renderAll( enums, @@ -480,20 +489,14 @@ private def renderScalaFunctions( if exportMode == ExportMode.No then if hasExternFunctions then - if renderMode == RenderMode.Objects then - summon[Config].linkName.foreach { l => - out.append(s"""@link("$l")""") + val objectHeader = summon[Config].linkName + .map { l => + s"""@link("$l")\n""" } - end if - - nestIf(renderMode == RenderMode.Objects) { - if renderMode == RenderMode.Objects then - out.appendLine( - s"\n@extern\nprivate[$safePackageName] object extern_functions:" - ) - typeImports.render(out) - else out.appendLine("\n") - + .getOrElse("") + + s"\n@extern\nprivate[$safePackageName] object extern_functions" + maybeObjectBlock(out, renderMode)(objectHeader) { + if renderMode == RenderMode.Objects then typeImports.render(out) exported ++= renderAll( scalaExternFunctions.toList.sortBy(functionSorter), out, @@ -503,9 +506,7 @@ private def renderScalaFunctions( end if if hasRegularFunctions || hasExternFunctions then - if renderMode == RenderMode.Objects then - out.appendLine(s"\nobject functions:") - nestIf(renderMode == RenderMode.Objects) { + maybeObjectBlock(out, renderMode)(s"\nobject functions") { if renderMode == RenderMode.Objects then typeImports.render(out) if hasExternFunctions && renderMode == RenderMode.Objects then @@ -539,8 +540,9 @@ private def renderScalaFunctions( !isInit } - out.appendLine("trait ExportedFunctions:") - nest { + val line = to(out) + + objectBlock(line)("trait ExportedFunctions") { if renderMode == RenderMode.Objects then typeImports.render(out) renderAll( modified(ExportLocation.Trait), @@ -549,9 +551,9 @@ private def renderScalaFunctions( ) } - if renderMode == RenderMode.Objects then - out.appendLine(s"\nobject functions extends ExportedFunctions:") - nestIf(renderMode == RenderMode.Objects) { + maybeObjectBlock(out, renderMode)( + s"\nobject functions extends ExportedFunctions" + ) { if renderMode == RenderMode.Objects then typeImports.render(out) renderAll( modified( @@ -561,7 +563,6 @@ private def renderScalaFunctions( renderFunction(_, _, renderMode) ) } - end if end if exported.result() diff --git a/modules/bindgen/src/main/scala/render/enumeration.scala b/modules/bindgen/src/main/scala/render/enumeration.scala index 5b6bf7c9..418447c1 100644 --- a/modules/bindgen/src/main/scala/render/enumeration.scala +++ b/modules/bindgen/src/main/scala/render/enumeration.scala @@ -14,9 +14,7 @@ def enumeration(model: Def.Enum, line: Appender)(using renderComment(line, model.meta) line(s"opaque type $opaqueType = $underlyingTypeRender") - line(s"object $opaqueType extends $traitName[$opaqueType]:") - - nest { + objectBlock(line)(s"object $opaqueType extends $traitName[$opaqueType]") { line(s"given _tag: Tag[$opaqueType] = ${scalaTag(numericType)}") if numericType.sign == SignType.Signed then line( @@ -38,32 +36,31 @@ def enumeration(model: Def.Enum, line: Appender)(using line(lhs + " = " + rhs) } - line(s"inline def getName(inline value: $opaqueType): Option[String] =") - nest { - line("inline value match") - nest { + defBlock(line)( + s"inline def getName(inline value: $opaqueType): Option[String] =" + ) { + defBlock(line)("inline value match") { model.values.foreach { case (constName, value) => line(s"""case ${escape(constName)} => Some("${escape(constName)}")""") } line("case _ => _root_.scala.None") } } - line(s"extension (a: $opaqueType)") - def wrap(exp: String) = - numericType match - case CType.NumericIntegral( - bindgen.IntegralBase.Char, - SignType.Signed - ) => - s"(($exp) & 0xff).toByte" - case CType.NumericIntegral( - bindgen.IntegralBase.Char, - SignType.Unsigned - ) => - s"(($exp) & 0xff.toUInt).toUByte" - case _ => exp + defBlock(line)(s"extension (a: $opaqueType)") { + def wrap(exp: String) = + numericType match + case CType.NumericIntegral( + bindgen.IntegralBase.Char, + SignType.Signed + ) => + s"(($exp) & 0xff).toByte" + case CType.NumericIntegral( + bindgen.IntegralBase.Char, + SignType.Unsigned + ) => + s"(($exp) & 0xff.toUInt).toUByte" + case _ => exp - nest { line(s"inline def &(b: $opaqueType): $opaqueType = ${wrap("a & b")}") line(s"inline def |(b: $opaqueType): $opaqueType = ${wrap("a | b")}") line(s"inline def is(b: $opaqueType): Boolean = (a & b) == b") diff --git a/modules/bindgen/src/main/scala/render/function.scala b/modules/bindgen/src/main/scala/render/function.scala index c42ca80c..73460ca1 100644 --- a/modules/bindgen/src/main/scala/render/function.scala +++ b/modules/bindgen/src/main/scala/render/function.scala @@ -73,10 +73,9 @@ def renderFunction( a @ Allocations(indices, returnAsWell) ) => val hasZone = if a.hasAny then "(using Zone)" else "" - line( - s"def ${f.name}$arglist$hasZone: ${scalaType(f.returnType)} = " - ) - nest { + defBlock(line)( + s"def ${f.name}$arglist$hasZone: ${scalaType(f.returnType)} =" + ) { import scala.collection.mutable.Map as MutableMap val allocationSizes: CType MutableMap Int = diff --git a/modules/bindgen/src/main/scala/render/struct.scala b/modules/bindgen/src/main/scala/render/struct.scala index 11362200..5c3c4432 100644 --- a/modules/bindgen/src/main/scala/render/struct.scala +++ b/modules/bindgen/src/main/scala/render/struct.scala @@ -71,8 +71,7 @@ def struct(struct: Def.Struct, line: Appender)(using renderComment(line, struct.meta) line(s"opaque type $structName = ${scalaType(finalStructType)}") - line(s"object ${sanitiseBeforeColon(structName.value)}:") - nest { + objectBlock(line)(s"object ${sanitiseBeforeColon(structName.value)}") { struct.anonymous.foreach { case s: Def.Struct => rendering.struct(s, line) @@ -113,10 +112,9 @@ def struct(struct: Def.Struct, line: Appender)(using ignored match case None => - line( - s"def apply(${applyArgList.result.mkString(", ")})(using Zone): Ptr[$structName] = " - ) - nest { + defBlock(line)( + s"def apply(${applyArgList.result.mkString(", ")})(using Zone): Ptr[$structName] =" + ) { line(s"val ____ptr = apply()") namedFields.filter(_._1.value.nonEmpty).foreach { case (fieldName, _) => @@ -132,8 +130,7 @@ def struct(struct: Def.Struct, line: Appender)(using ) end match - line(s"extension (struct: $structName)") - nest { + defBlock(line)(s"extension (struct: $structName)") { if !structIsOpaque then namedFieldsWithIndex.filter(_._1._1.value.nonEmpty).foreach { case ((fieldName, fieldType), idx) => @@ -178,8 +175,10 @@ def struct(struct: Def.Struct, line: Appender)(using } if structIsOpaque then - line("val offsets: Array[Int] = ") - nest { + defBlock(line)( + start = s"val offsets: Array[Int] =", + defNameForEnd = Some("offsets") + ) { line(s"val res = Array.ofDim[Int](${namedFieldsWithIndex.length})") alignMethod.foreach(line(_)) @@ -220,8 +219,6 @@ def struct(struct: Def.Struct, line: Appender)(using } line("res") } - - line("end offsets") end if else line(s"given _tag: Tag[$structName] = Tag.materializeCStruct0Tag") end if diff --git a/modules/bindgen/src/main/scala/render/union.scala b/modules/bindgen/src/main/scala/render/union.scala index 771565ca..eb7f45ab 100644 --- a/modules/bindgen/src/main/scala/render/union.scala +++ b/modules/bindgen/src/main/scala/render/union.scala @@ -26,8 +26,7 @@ def union(model: Def.Union, line: Appender)(using Config)(using renderComment(line, model.meta) line(s"opaque type $unionName = $tpe") - line(s"object ${sanitiseBeforeColon(unionName.value)}:") - nest { + objectBlock(line)(s"object ${sanitiseBeforeColon(unionName.value)}") { model.anonymous.foreach { case s: Def.Struct => rendering.struct(s, line) @@ -40,8 +39,7 @@ def union(model: Def.Union, line: Appender)(using Config)(using line(tag) if model.fields.nonEmpty then - line(s"def apply()(using Zone): Ptr[$unionName] = ") - nest { + defBlock(line)(s"def apply()(using Zone): Ptr[$unionName] =") { line( s"val ___ptr = _root_.scala.scalanative.unsafe.alloc[$unionName](1)" ) @@ -53,10 +51,9 @@ def union(model: Def.Union, line: Appender)(using Config)(using val setterName = setter(fieldName.value) // It's important we don't use the escape(...) function here line(s"@scala.annotation.targetName(\"apply_${fieldName.value}\")") - line( + defBlock(line)( s"def apply($getterName: $typ)(using Zone): Ptr[$unionName] =" - ) - nest { + ) { line( s"val ___ptr = _root_.scala.scalanative.unsafe.alloc[$unionName](1)" ) @@ -67,8 +64,7 @@ def union(model: Def.Union, line: Appender)(using Config)(using line("___ptr") } } - line(s"extension (struct: $unionName)") - nest { + defBlock(line)(s"extension (struct: $unionName)") { model.fields.foreach { case (fieldName, fieldType) => val getterName = getter(fieldName.value) val setterName = setter(fieldName.value) diff --git a/modules/bindgen/src/main/scala/render/utils.scala b/modules/bindgen/src/main/scala/render/utils.scala index de4c3751..443edaa3 100644 --- a/modules/bindgen/src/main/scala/render/utils.scala +++ b/modules/bindgen/src/main/scala/render/utils.scala @@ -91,6 +91,32 @@ def to(sb: LineBuilder)(using config: Config): Appender = def aliasResolver(name: String)(using ar: AliasResolver): CType = ar(name) +//objects, traits, classes +def objectBlock(line: Appender)(start: String)( + f: Config ?=> Unit +)(using config: Config): Unit = + val startDelimiter = if config.useBraces.value then " {" else ":" + line(start + startDelimiter) + nest { + f + } + if config.useBraces.value then line("}") + +//defs, vals, extensions +def defBlock( + line: Appender +)(start: String, defNameForEnd: Option[String] = None)( + f: Config ?=> Unit +)(using config: Config): Unit = + val startDelimiter = if config.useBraces.value then " {" else "" + line(start + startDelimiter) + nest { + f + } + if config.useBraces.value then line("}") + else defNameForEnd.foreach(n => line(s"end $n")) +end defBlock + def packageName(using conf: Context): String = conf.packageName.value type Appender = Config ?=> String => Unit diff --git a/modules/interface/src/main/scala/Binding.scala b/modules/interface/src/main/scala/Binding.scala index 4b26ca81..864709eb 100644 --- a/modules/interface/src/main/scala/Binding.scala +++ b/modules/interface/src/main/scala/Binding.scala @@ -28,6 +28,7 @@ class Binding private ( def scalaFile: String = impl.scalaFile def cFile: String = impl.cFile def flavour: Option[Flavour] = impl.flavour + def useBraces: Boolean = impl.useBraces def withLinkName(name: String) = copy(_.copy(linkName = Some(name))) @@ -116,6 +117,9 @@ class Binding private ( def addBindgenArguments(arguments: List[String]): Binding = copy(b => b.copy(bindgenArguments = b.bindgenArguments ++ arguments)) + def withBraces(b: Boolean): Binding = + copy(_.copy(useBraces = b)) + def toCommand(lang: BindingLang): List[String] = { val sb = List.newBuilder[String] @@ -157,6 +161,7 @@ class Binding private ( else flag("c") + if (useBraces && lang == BindingLang.Scala) flag("use-braces") if (multiFile && lang == BindingLang.Scala) flag("multi-file") if (noComments && lang == BindingLang.Scala) flag("render.no-comments") if (noLocation && lang == BindingLang.Scala) flag("render.no-location") @@ -234,7 +239,8 @@ object Binding { opaqueStructs: Set[String] = Defaults.opaqueStructs, multiFile: Boolean = Defaults.multiFile, noComments: Boolean = Defaults.noComments, - noLocation: Boolean = Defaults.noLocation + noLocation: Boolean = Defaults.noLocation, + bracesNotIndents: Boolean = Defaults.useBraces ): Binding = { apply(headerFile, packageName).copy( _.copy( @@ -248,7 +254,8 @@ object Binding { opaqueStructs = opaqueStructs, multiFile = multiFile, noComments = noComments, - noLocation = noLocation + noLocation = noLocation, + useBraces = bracesNotIndents ) ) } @@ -274,7 +281,8 @@ object Binding { excludeSystemPaths: List[Path] = Defaults.excludeSystemPaths, flavour: Option[Flavour] = None, scalaFile: String, - cFile: String + cFile: String, + useBraces: Boolean = Defaults.useBraces ) private[interface] object Defaults { @@ -295,6 +303,7 @@ object Binding { val excludeSystemPaths = List.empty[Path] val exportMode = false val flavour = Flavour.ScalaNative04 + val useBraces = false } } diff --git a/modules/sbt-plugin/src/main/scala/BindgenPlugin.scala b/modules/sbt-plugin/src/main/scala/BindgenPlugin.scala index f65d9ec9..4d0ebd68 100644 --- a/modules/sbt-plugin/src/main/scala/BindgenPlugin.scala +++ b/modules/sbt-plugin/src/main/scala/BindgenPlugin.scala @@ -189,8 +189,11 @@ object BindgenPlugin extends AutoPlugin { bindgenGenerateScalaSources := { val selected = (addConf / bindgenBindings).value.map { b => b.flavour match { - case None => b.withFlavour(bindgenFlavour.value) - case Some(_) => b + case None => + b.withFlavour(bindgenFlavour.value) + .withBraces(scalacOptions.value.contains("-no-indent")) + case Some(_) => + b.withBraces(scalacOptions.value.contains("-no-indent")) } }