From 468904432df3a5afcd294ade0ac72a5ad9befc01 Mon Sep 17 00:00:00 2001
From: "P. Oscar Boykin" <johnynek@users.noreply.github.com>
Date: Fri, 15 Mar 2024 17:50:34 -0700
Subject: [PATCH] Support comments in headers (#1168)

* Support comments in headers

* share more code
---
 .../scala/org/bykn/bosatsu/CommentStatement.scala  |  4 ++--
 core/src/main/scala/org/bykn/bosatsu/Package.scala | 14 ++++++++++----
 core/src/main/scala/org/bykn/bosatsu/Padding.scala |  6 ++++--
 core/src/main/scala/org/bykn/bosatsu/Parser.scala  |  3 ++-
 .../test/scala/org/bykn/bosatsu/ParserTest.scala   |  5 ++++-
 5 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/core/src/main/scala/org/bykn/bosatsu/CommentStatement.scala b/core/src/main/scala/org/bykn/bosatsu/CommentStatement.scala
index 042acdd98..d5adc46be 100644
--- a/core/src/main/scala/org/bykn/bosatsu/CommentStatement.scala
+++ b/core/src/main/scala/org/bykn/bosatsu/CommentStatement.scala
@@ -31,12 +31,12 @@ object CommentStatement {
       val commentBlock: P[NonEmptyList[String]] =
         // if the next line is part of the comment until we see the # or not
         (Parser.maybeSpace.with1.soft *> commentPart)
-          .repSep(sep = sep) <* Parser.newline.orElse(P.end)
+          .repSep(sep = sep) <* Parser.termination
 
       (commentBlock ~ onP(indent))
         .map { case (m, on) => CommentStatement(m, on) }
     }
 
   val commentPart: P[String] =
-    P.char('#') *> P.until0(P.char('\n'))
+    P.char('#') *> P.until0(Parser.termination)
 }
diff --git a/core/src/main/scala/org/bykn/bosatsu/Package.scala b/core/src/main/scala/org/bykn/bosatsu/Package.scala
index 3d1285c36..5ed481ff1 100644
--- a/core/src/main/scala/org/bykn/bosatsu/Package.scala
+++ b/core/src/main/scala/org/bykn/bosatsu/Package.scala
@@ -190,12 +190,17 @@ object Package {
         Doc.intercalate(Doc.empty, p :: i :: e :: b)
     }
 
+
   def headerParser(defaultPack: Option[PackageName]): P0[Header] = {
-    // TODO: support comments before the Statement
+    val spaceComment: P0[Unit] =
+      (Parser.spaces.? ~ CommentStatement.commentPart.?).void
+
+    val eol = spaceComment <* Parser.termination
     val parsePack = Padding
       .parser(
         (P.string("package")
-          .soft ~ spaces) *> PackageName.parser <* Parser.toEOL
+          .soft ~ spaces) *> PackageName.parser <* eol,
+        spaceComment
       )
       .map(_.padded)
     val pname: P0[PackageName] =
@@ -204,12 +209,13 @@ object Package {
         case Some(p) => parsePack.?.map(_.getOrElse(p))
       }
 
-    val im = Padding.parser(Import.parser <* Parser.toEOL).map(_.padded).rep0
+    val im = Padding.parser(Import.parser <* eol, spaceComment).map(_.padded).rep0
     val ex = Padding
       .parser(
         (P.string("export")
           .soft ~ spaces) *> ExportedName.parser.itemsMaybeParens
-          .map(_._2) <* Parser.toEOL
+          .map(_._2) <* eol,
+        spaceComment
       )
       .map(_.padded)
 
diff --git a/core/src/main/scala/org/bykn/bosatsu/Padding.scala b/core/src/main/scala/org/bykn/bosatsu/Padding.scala
index f9286d121..231dbc061 100644
--- a/core/src/main/scala/org/bykn/bosatsu/Padding.scala
+++ b/core/src/main/scala/org/bykn/bosatsu/Padding.scala
@@ -22,13 +22,15 @@ object Padding {
 
   /** This allows an empty padding
     */
-  def parser[T](p: P[T]): P[Padding[T]] = {
-    val spacing = (maybeSpace.with1.soft ~ Parser.newline).void.rep0
+  def parser[T](p: P[T], space: P0[Unit]): P[Padding[T]] = {
+    val spacing = (space.with1.soft ~ Parser.newline).void.rep0
 
     (spacing.with1.soft ~ p)
       .map { case (vec, t) => Padding(vec.size, t) }
   }
 
+  def parser[T](p: P[T]): P[Padding[T]] = parser(p, maybeSpace)
+
   /** Parses a padding of length 1 or more, then p
     */
   def parser1[T](p: P0[T]): P[Padding[T]] =
diff --git a/core/src/main/scala/org/bykn/bosatsu/Parser.scala b/core/src/main/scala/org/bykn/bosatsu/Parser.scala
index 89c9db043..3bb3e3a7e 100644
--- a/core/src/main/scala/org/bykn/bosatsu/Parser.scala
+++ b/core/src/main/scala/org/bykn/bosatsu/Parser.scala
@@ -448,7 +448,8 @@ object Parser {
     (P.char('(') ~ ws) *> pa <* (ws ~ P.char(')'))
 
   val newline: P[Unit] = P.char('\n')
-  val toEOL: P0[Unit] = maybeSpace *> newline.orElse(P.end)
+  val termination: P0[Unit] = newline.orElse(P.end)
+  val toEOL: P0[Unit] = maybeSpace *> termination
   val toEOL1: P[Unit] = maybeSpace.with1 *> newline
 
   def optionParse[A](pa: P0[A], str: String): Option[A] =
diff --git a/core/src/test/scala/org/bykn/bosatsu/ParserTest.scala b/core/src/test/scala/org/bykn/bosatsu/ParserTest.scala
index 4d1929f5d..4f0507613 100644
--- a/core/src/test/scala/org/bykn/bosatsu/ParserTest.scala
+++ b/core/src/test/scala/org/bykn/bosatsu/ParserTest.scala
@@ -1722,9 +1722,12 @@ external def foo2(i: Integer, b: a) -> String
     roundTrip(
       Package.parser(None),
       """
+# we can comment the package
 package Foo/Bar
+# comments are allowed
 from Baz import Bippy
-export foo
+# even here
+export foo # or here
 
 foo = 1
 """