Skip to content

Commit

Permalink
Scala Native support (#87)
Browse files Browse the repository at this point in the history
* Added native support

* Changed scalatest dependency and used js tests for native

* Ready for final publishing
  • Loading branch information
j-mie6 authored Mar 7, 2021
1 parent 6894687 commit 67a5839
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
version: "2"
exclude_patterns:
- "**/scala-3.x"
- "**/native"
- "**/js"
- "**/test"
- ".github"
- "project"
Expand Down
62 changes: 60 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ jobs:
- name: Setup Scala
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: olafurpg/setup-scala@v10
with:
java-version: [email protected]

- name: Cache Coursier
#if: needs.check-duplicate.outputs.should_skip != 'true'
Expand All @@ -123,6 +121,66 @@ jobs:
- name: Scaladoc
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION parsleyJS/doc

validate-native:
name: Scala-Native ${{ matrix.scala }} LLVM 10
runs-on: ubuntu-20.04
needs: check-duplicate

env:
SCALA_VERSION: ${{ matrix.scala }}

strategy:
fail-fast: false
matrix:
scala: [2.12.13, 2.13.5]

steps:
- name: Checkout repository
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: actions/[email protected]

- name: Cache LLVM and Clang
#if: needs.check-duplicate.outputs.should_skip != 'true'
id: cache-llvm
uses: actions/cache@v2
with:
path: ${{ runner.temp }}/llvm
key: llvm-10.0

- name: Setup LLVM + Clang
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: KyleMayes/[email protected]
with:
version: "10.0"
directory: ${{ runner.temp }}/llvm
cached: ${{ steps.cache-llvm.outputs.cache-hit }}

- name: Setup Scala
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: olafurpg/setup-scala@v10

- name: Cache Coursier
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: actions/cache@v2
with:
path: ~/.cache/coursier
key: sbt-coursier-cache

- name: Cache SBT
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: actions/cache@v2
with:
path: ~/.sbt
key: sbt-${{ hashFiles('**/build.sbt') }}

- name: Test
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION parsleyNative/test

- name: Scaladoc
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION parsleyNative/doc
# End attrocity

coverage:
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,13 @@ jobs:
PGP_SECRET: ${{ secrets.PGP_SECRET }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}

- name: Publish Native
run: sbt ci-release
env:
CI_RELEASE: +parsleyNative/publishSigned
CI_SNAPSHOT_RELEASE: +parsleyNative/publishSigned
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
10 changes: 8 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ def extraSources(rootSrcFile: File, base: String, version: String): Seq[File] =
case None => Seq.empty
}

def scalaTestDependency(version: String): String = Map("0.27.0-RC1" -> "3.2.2", "3.0.0-M3" -> "3.2.3").getOrElse(version, "3.2.5")
def scalaTestDependency(version: String): String =
Map("0.27.0-RC1" -> "3.2.2",
"3.0.0-M3" -> "3.2.3")
.getOrElse(version, "3.2.5")

val PureVisible: CrossType = new CrossType {
def projectDir(crossBase: File, projectType: String): File =
Expand All @@ -57,7 +60,7 @@ Global / onChangedBuildSource := ReloadOnSourceChanges
// See https://github.com/sbt/sbt/issues/1224
onLoad in Global ~= (_ andThen ("project parsley" :: _))

lazy val parsley = crossProject(JSPlatform, JVMPlatform)
lazy val parsley = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(PureVisible)
.in(file("."))
Expand Down Expand Up @@ -89,3 +92,6 @@ lazy val parsley = crossProject(JSPlatform, JVMPlatform)
.jsSettings(
crossScalaVersions := List(scala212Version, scala213Version)
)
.nativeSettings(
crossScalaVersions := List(scala212Version, scala213Version)
)
23 changes: 23 additions & 0 deletions native/src/test/scala/parsley/CoreNativeTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package parsley

import parsley.combinator.manyUntil
import parsley.character.{anyChar, string}
import parsley.implicits.{charLift, stringLift}

import scala.language.implicitConversions

import java.io.File

class CoreJvmTests extends ParsleyTest {
"parseFromFile" should "work" in {
(manyUntil(anyChar, "Jamie Willis") *> anyChar).parseFromFile(new File("LICENSE")) shouldBe Success('\n')
}

"stack overflows" should "not occur" in {
def repeat(n: Int, p: Parsley[Char]): Parsley[Char] = {
if (n > 0) p *> repeat(n-1, p)
else p
}
noException should be thrownBy repeat(4000, 'a').runParser("a")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package parsley.internal.machine.errors

import parsley.ParsleyTest

import parsley.internal.errors.{TrivialError, FailError, Raw, Desc, EndOfInput}
import scala.language.implicitConversions

import MockedBuilders.mockedErrorItemBuilder

class DefuncErrorTests extends ParsleyTest {
"ClassicExpectedError" should "evaluate to TrivialError" in {
val err = ClassicExpectedError(0, 0, 0, None)
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "only be empty when its label is" in {
ClassicExpectedError(0, 0, 0, None).isExpectedEmpty shouldBe true
ClassicExpectedError(0, 0, 0, Some(EndOfInput)).isExpectedEmpty shouldBe false
}

"ClassicExpectedErrorWithReason" should "evaluate to TrivialError" in {
val err = ClassicExpectedErrorWithReason(0, 0, 0, None, "")
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "only be empty when its label is" in {
ClassicExpectedErrorWithReason(0, 0, 0, None, "").isExpectedEmpty shouldBe true
ClassicExpectedErrorWithReason(0, 0, 0, Some(EndOfInput), "").isExpectedEmpty shouldBe false
}

"ClassicUnexpectedError" should "evaluate to TrivialError" in {
val err = ClassicUnexpectedError(0, 0, 0, None, EndOfInput)
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "only be empty when its label is" in {
ClassicUnexpectedError(0, 0, 0, None, EndOfInput).isExpectedEmpty shouldBe true
ClassicUnexpectedError(0, 0, 0, Some(EndOfInput), EndOfInput).isExpectedEmpty shouldBe false
}

"ClassicFancyError" should "evaluate to FancyError" in {
val err = ClassicFancyError(0, 0, 0, "")
err.isTrivialError shouldBe false
err.asParseError shouldBe a [FailError]
}
it should "always be empty" in {
ClassicFancyError(0, 0, 0, "hi").isExpectedEmpty shouldBe true
}

"EmptyError" should "evaluate to TrivialError" in {
val err = EmptyError(0, 0, 0, None)
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "only be empty when its label is" in {
EmptyError(0, 0, 0, None).isExpectedEmpty shouldBe true
EmptyError(0, 0, 0, Some(EndOfInput)).isExpectedEmpty shouldBe false
}

"TokenError" should "evaluate to TrivialError" in {
val err = TokenError(0, 0, 0, None, 1)
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "only be empty when its label is" in {
TokenError(0, 0, 0, None, 1).isExpectedEmpty shouldBe true
TokenError(0, 0, 0, Some(EndOfInput), 1).isExpectedEmpty shouldBe false
}

"EmptyErrorWithReason" should "evaluate to TrivialError" in {
val err = EmptyErrorWithReason(0, 0, 0, None, "")
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "only be empty when its label is" in {
EmptyErrorWithReason(0, 0, 0, None, "").isExpectedEmpty shouldBe true
EmptyErrorWithReason(0, 0, 0, Some(EndOfInput), "").isExpectedEmpty shouldBe false
}

"MultiExpectedError" should "evaluate to TrivialError" in {
val err = MultiExpectedError(0, 0, 0, Set.empty, 1)
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "only be empty when its label is" in {
MultiExpectedError(0, 0, 0, Set.empty, 1).isExpectedEmpty shouldBe true
MultiExpectedError(0, 0, 0, Set(EndOfInput), 1).isExpectedEmpty shouldBe false
}

"MergedErrors" should "be trivial if both children are" in {
val err = MergedErrors(EmptyError(0, 0, 0, None), MultiExpectedError(0, 0, 0, Set.empty, 1))
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
they should "be a trivial error if one trivial child is further than the other fancy child" in {
val err1 = MergedErrors(EmptyError(1, 0, 0, None), ClassicFancyError(0, 0, 0, ""))
err1.isTrivialError shouldBe true
err1.asParseError shouldBe a [TrivialError]

val err2 = MergedErrors(ClassicFancyError(0, 0, 0, ""), EmptyError(1, 0, 0, None))
err2.isTrivialError shouldBe true
err2.asParseError shouldBe a [TrivialError]
}
they should "be a fancy error in any other case" in {
val err1 = MergedErrors(EmptyError(0, 0, 0, None), ClassicFancyError(0, 0, 0, ""))
err1.isTrivialError shouldBe false
err1.asParseError shouldBe a [FailError]

val err2 = MergedErrors(ClassicFancyError(0, 0, 0, ""), EmptyError(0, 0, 0, None))
err2.isTrivialError shouldBe false
err2.asParseError shouldBe a [FailError]

val err3 = MergedErrors(EmptyError(0, 0, 0, None), ClassicFancyError(1, 0, 0, ""))
err3.isTrivialError shouldBe false
err3.asParseError shouldBe a [FailError]

val err4 = MergedErrors(ClassicFancyError(1, 0, 0, ""), EmptyError(0, 0, 0, None))
err4.isTrivialError shouldBe false
err4.asParseError shouldBe a [FailError]

val err5 = MergedErrors(ClassicFancyError(0, 0, 0, ""), ClassicFancyError(0, 0, 0, ""))
err5.isTrivialError shouldBe false
err5.asParseError shouldBe a [FailError]

val err6 = MergedErrors(ClassicFancyError(1, 0, 0, ""), ClassicFancyError(0, 0, 0, ""))
err6.isTrivialError shouldBe false
err6.asParseError shouldBe a [FailError]
}
they should "be empty when trivial and same offset only when both children are empty" in {
MergedErrors(EmptyError(0, 0, 0, None), EmptyError(0, 0, 0, None)).isExpectedEmpty shouldBe true
MergedErrors(EmptyError(0, 0, 0, None), EmptyError(0, 0, 0, Some(EndOfInput))).isExpectedEmpty shouldBe false
MergedErrors(EmptyError(0, 0, 0, Some(EndOfInput)), EmptyError(0, 0, 0, None)).isExpectedEmpty shouldBe false
MergedErrors(EmptyError(0, 0, 0, Some(EndOfInput)), EmptyError(0, 0, 0, Some(EndOfInput))).isExpectedEmpty shouldBe false
}
they should "contain all the expecteds from both branches when appropriate" in {
val err = MergedErrors(MultiExpectedError(0, 0, 0, Set(Raw("a"), Raw("b")), 1),
MultiExpectedError(0, 0, 0, Set(Raw("b"), Raw("c")), 1))
err.asParseError.asInstanceOf[TrivialError].expecteds should contain only (Raw("a"), Raw("b"), Raw("c"))
}

"WithHints" should "be trivial if its child is" in {
val err = WithHints(ClassicExpectedError(0, 0, 0, None), EmptyHints)
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "support fancy errors as not trivial" in {
val err = WithHints(ClassicFancyError(0, 0, 0, ""), EmptyHints)
err.isTrivialError shouldBe false
err.asParseError shouldBe a [FailError]
}
it should "only be empty when its label is" in {
WithHints(EmptyError(0, 0, 0, None), EmptyHints).isExpectedEmpty shouldBe true
WithHints(EmptyError(0, 0, 0, Some(EndOfInput)), EmptyHints).isExpectedEmpty shouldBe false
}

"WithReason" should "be trivial if its child is" in {
val err = WithReason(ClassicExpectedError(0, 0, 0, None), "")
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "support fancy errors as not trivial" in {
val err = WithReason(ClassicFancyError(0, 0, 0, ""), "")
err.isTrivialError shouldBe false
err.asParseError shouldBe a [FailError]
}
it should "only be empty when its label is" in {
WithReason(EmptyError(0, 0, 0, None), "").isExpectedEmpty shouldBe true
WithReason(EmptyError(0, 0, 0, Some(EndOfInput)), "").isExpectedEmpty shouldBe false
}

"WithLabel" should "be trivial if its child is" in {
val err = WithLabel(ClassicExpectedError(0, 0, 0, None), "")
err.isTrivialError shouldBe true
err.asParseError shouldBe a [TrivialError]
}
it should "support fancy errors as not trivial" in {
val err = WithLabel(ClassicFancyError(0, 0, 0, ""), "")
err.isTrivialError shouldBe false
err.asParseError shouldBe a [FailError]
}
it should "be empty if the label is empty and not otherwise" in {
WithLabel(EmptyError(0, 0, 0, None), "").isExpectedEmpty shouldBe true
WithLabel(EmptyError(0, 0, 0, None), "a").isExpectedEmpty shouldBe false
WithLabel(EmptyError(0, 0, 0, Some(Desc("x"))), "").isExpectedEmpty shouldBe true
WithLabel(EmptyError(0, 0, 0, Some(Desc("x"))), "a").isExpectedEmpty shouldBe false
}
it should "replace all expected" in {
val errShow = WithLabel(MultiExpectedError(0, 0, 0, Set(Raw("a"), Raw("b")), 1), "x")
val errHide = WithLabel(MultiExpectedError(0, 0, 0, Set(Raw("a"), Raw("b")), 1), "")
errShow.asParseError.asInstanceOf[TrivialError].expecteds should contain only (Desc("x"))
errHide.asParseError.asInstanceOf[TrivialError].expecteds shouldBe empty
}
}
2 changes: 2 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.4")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.0")
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.0.0")

0 comments on commit 67a5839

Please sign in to comment.