Skip to content

Commit

Permalink
Parsley 3 (j-mie6#80)
Browse files Browse the repository at this point in the history
* enforced deprecation >:)

* removed old docs

* More enforced deprecation

* Removed deprecation folder from sbt

* removed a couple more things, moved withFilter to a 2.12 specific file

* Inverted a predicate in Lexer

* Removed deprecated folder from code climate yaml

* Parser API Changes (j-mie6#81)

* modified the parser API, breaking into IO and non-IO, as well as adding a Try wrapper to parseFromFile. runParser -> parse.

* Added missing io file

* Changing higher-arity `map` to a `zipped` (j-mie6#82)

* Broke up implicits module into submodules, ignored lift and zipped

* fixed typo in the codeclimate.yaml

* Shuffled the ignores a little

* Renamed to zipped and added the tupled form

* Fixed laziness of zipped, and added lazyZip for Zipped2 and Zipped3, which can't be lazy due to a conflict with scala Tuple2 and Tuple3 .zipped

* Moved io into JVM specific code, we can't use Source in scala-js

* Parsley 3 debug (j-mie6#83)

* Parser API Changes (j-mie6#81)

* Added ascii flag to combinator, just needs to be hooked in

* Added ability to disable colours through to the instruction level

* Added the native IO binding

* Updated tests for native

* Parsley 3 expr (j-mie6#88)

* Changed the API for precedence

* Added postfix1 and prefix1

* Generalised the type of postfix1 and prefix1

* Added tests and fixed type of prefix1

* Disabled coverage for debug

* Updated documentation

* Fixed test coverage

* Parsley 3 errors (j-mie6#90)

* Fixed token error for unclosed bracket to be an explain

* Split errors up and moved caret completion

* Added build phase to CI

* Moved error combinators into the errors package

* Added the ErrorBuilder typeclass

* Fixed the filename in io from error builder, because scala-js doesn't support it

* Removed some lines in context... really?!

* Parsley 3 Precedence Enhancements (j-mie6#91)

* Changed the Levels datatype so that the atom is incorporated in, can now be written either way round

* Fixed name for Atoms

* Added documentation, removed unneeded implicit class, improved identity wrapping detection

* Added SOps

* removed redundant code

* Added a prefix

* Added NonAssoc, with basic error explain for it

* Added reversed precedence operator

* Removed build phase, it seems to cause coverage to skip?

* Turned on push coverage runs again

* Made notFollowedBy's error stretch as long as there were characters consumed

* Added a default error builder which the user can extend

* Added documentation to the error builder

* Added revision 0 bound to the default error builder

* Removed references to additional contextual information in the ErrorBuilder API, these can be added in a revision when the functionality appears

* Added in a missing future implementation

* Removed releases for parsley-3

* Updated all the documentation
  • Loading branch information
j-mie6 authored Mar 23, 2021
1 parent 590de35 commit 96843b4
Show file tree
Hide file tree
Showing 55 changed files with 2,034 additions and 2,081 deletions.
3 changes: 2 additions & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ exclude_patterns:
- ".gitignore"
- "*.pdf"
- "LICENSE"
- "**/deprecated"
- "**/lift.scala"
- "**/implicits/zipped.scala"
plugins:
scalastyle:
enabled: true
Expand Down
19 changes: 16 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ jobs:
path: ~/.sbt
key: sbt-${{ hashFiles('**/build.sbt') }}

- name: Build
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION compile

- name: Test
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION test
Expand Down Expand Up @@ -114,6 +118,10 @@ jobs:
path: ~/.sbt
key: sbt-${{ hashFiles('**/build.sbt') }}

- name: Build
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION parsleyJS/compile

- name: Test
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION parsleyJS/test
Expand Down Expand Up @@ -174,6 +182,10 @@ jobs:
path: ~/.sbt
key: sbt-${{ hashFiles('**/build.sbt') }}

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

- name: Test
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION parsleyNative/test
Expand All @@ -189,9 +201,10 @@ jobs:
needs: validate

# Temp: Also run on pull requests when validate is skipped, due to codeclimate/coverage being a required check
if: >-
github.ref == 'refs/heads/master' && (needs.validate.result == 'success' || needs.validate.result == 'skipped') ||
github.event_name == 'pull_request' && (needs.validate.result == 'success' || needs.validate.result == 'skipped')
# FIXME: This seems not to be working on the parsley-3 branch?
#if: >-
# github.ref == 'refs/heads/master' && (needs.validate.result == 'success' || needs.validate.result == 'skipped') ||
# github.event_name == 'pull_request' && (needs.validate.result == 'success' || needs.validate.result == 'skipped')

env:
SCALA_VERSION: 2.13.5
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import parsley.character.{char, string, digit}
import parsley.implicits.{charLift, stringLift}

val hello: Parsley[Unit] = void('h' *> ("ello" <|> "i") *> " world!")
hello.runParser("hello world!") // returns Success(())
hello.runParser("hi world!") // returns Success(())
hello.runParser("hey world!") // returns a Failure
hello.parse("hello world!") // returns Success(())
hello.parse("hi world!") // returns Success(())
hello.parse("hey world!") // returns a Failure

val natural: Parsley[Int] = digit.foldLeft1(0)((n, d) => n * 10 + d.asDigit)
natural.runParser("0") // returns Success(0)
natural.runParser("123") // returns Success(123)
natural.parse("0") // returns Success(0)
natural.parse("123") // returns Success(123)
```

For more see [the Wiki](https://github.com/j-mie6/Parsley/wiki)!
Expand Down
17 changes: 12 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ val PureVisible: CrossType = new CrossType {
Global / onChangedBuildSource := ReloadOnSourceChanges

// See https://github.com/sbt/sbt/issues/1224
onLoad in Global ~= (_ andThen ("project parsley" :: _))
Global / onLoad ~= (_ andThen ("project parsley" :: _))

Compile / bloopGenerate := None
Test / bloopGenerate := None

lazy val parsley = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.withoutSuffixFor(JVMPlatform)
Expand All @@ -70,10 +73,10 @@ lazy val parsley = crossProject(JSPlatform, JVMPlatform, NativePlatform)

libraryDependencies += "org.scalatest" %%% "scalatest" % scalaTestDependency(scalaVersion.value) % Test,

// temporary until Parsley 3.0
Compile / unmanagedSourceDirectories += file(s"${baseDirectory.value.getParentFile.getPath}/src/main/deprecated"),
Compile / unmanagedSourceDirectories ++= extraSources(baseDirectory.value.getParentFile, "main", scalaVersion.value),
Compile / unmanagedSourceDirectories ++= extraSources(baseDirectory.value, "main", scalaVersion.value),
Test / unmanagedSourceDirectories ++= extraSources(baseDirectory.value.getParentFile, "test", scalaVersion.value),
Test / unmanagedSourceDirectories ++= extraSources(baseDirectory.value, "test", scalaVersion.value),

scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature"),
scalacOptions ++= (if (isDotty.value) Seq("-source:3.0-migration") else Seq.empty),
Expand All @@ -90,8 +93,12 @@ lazy val parsley = crossProject(JSPlatform, JVMPlatform, NativePlatform)
crossScalaVersions := List(scala212Version, scala213Version, scala3Version, dottyVersion),
)
.jsSettings(
crossScalaVersions := List(scala212Version, scala213Version)
crossScalaVersions := List(scala212Version, scala213Version),
Compile / bloopGenerate := None,
Test / bloopGenerate := None
)
.nativeSettings(
crossScalaVersions := List(scala212Version, scala213Version)
crossScalaVersions := List(scala212Version, scala213Version),
Compile / bloopGenerate := None,
Test / bloopGenerate := None
)
47 changes: 47 additions & 0 deletions jvm/src/main/scala/parsley/io.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package parsley

import scala.io.{Source, Codec}
import scala.util.Try
import java.io.File

import parsley.internal.machine.Context

import scala.language.{higherKinds, implicitConversions}
import parsley.errors.ErrorBuilder

/** This module contains utilities to have parsers interact with IO, including the very useful `parseFromFile` method (exposed by `ParseFromIO`)
* @since 3.0.0
*/
object io {
/**
* This class exposes a method of running parsers from a file.
*
* @param p The parser which serves as the method receiver
* @param con A conversion (if required) to turn `p` into a parser
* @version 3.0.0
*/
implicit final class ParseFromIO[P, +A](p: P)(implicit con: P => Parsley[A]) {
/** This method executes a parser, but collects the input to the parser from the given file.
* The file name is used to annotate any error messages. The result of this method handles
* exceptions and ensures the file has been properly closed.
* @param file The file to load and run against
* @param codec The encoding of the file
* @return a `Try` containing a result of either a success with a value of type `A` or a failure with error message on success,
* and a failure if an IOException occured
* @since 3.0.0
*/
def parseFromFile[Err: ErrorBuilder](file: File)(implicit codec: Codec): Try[Result[Err, A]] = {
for {
src <- Try(Source.fromFile(file))
input <- Try(src.mkString).recoverWith {
case err: Throwable =>
src.close()
scala.util.Failure(err)
}
} yield {
src.close()
new Context(p.internal.threadSafeInstrs, input, Some(file.getName)).runParser()
}
}
}
}
10 changes: 7 additions & 3 deletions jvm/src/test/scala/parsley/CoreJvmTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ package parsley

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

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')
(manyUntil(anyChar, "Jamie Willis") *> anyChar).parseFromFile(new File("LICENSE")).get shouldBe Success('\n')
}
it should "fail with an error when file does not exist" in {
Parsley.empty.parseFromFile(new File("foo.diuh")) shouldBe a [scala.util.Failure[_]]
}

"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")
noException should be thrownBy repeat(4000, 'a').parse("a")
}
}
47 changes: 47 additions & 0 deletions native/src/main/scala/parsley/io.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package parsley

import scala.io.{Source, Codec}
import scala.util.Try
import java.io.File

import parsley.internal.machine.Context

import scala.language.{higherKinds, implicitConversions}
import parsley.errors.ErrorBuilder

/** This module contains utilities to have parsers interact with IO, including the very useful `parseFromFile` method (exposed by `ParseFromIO`)
* @since 3.0.0
*/
object io {
/**
* This class exposes a method of running parsers from a file.
*
* @param p The parser which serves as the method receiver
* @param con A conversion (if required) to turn `p` into a parser
* @version 3.0.0
*/
implicit final class ParseFromIO[P, +A](p: P)(implicit con: P => Parsley[A]) {
/** This method executes a parser, but collects the input to the parser from the given file.
* The file name is used to annotate any error messages. The result of this method handles
* exceptions and ensures the file has been properly closed.
* @param file The file to load and run against
* @param codec The encoding of the file
* @return a `Try` containing a result of either a success with a value of type `A` or a failure with error message on success,
* and a failure if an IOException occured
* @since 3.0.0
*/
def parseFromFile[Err: ErrorBuilder](file: File)(implicit codec: Codec): Try[Result[Err, A]] = {
for {
src <- Try(Source.fromFile(file))
input <- Try(src.mkString).recoverWith {
case err: Throwable =>
src.close()
scala.util.Failure(err)
}
} yield {
src.close()
new Context(p.internal.threadSafeInstrs, input, Some(file.getName)).runParser()
}
}
}
}
10 changes: 7 additions & 3 deletions native/src/test/scala/parsley/CoreNativeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@ package parsley

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

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')
(manyUntil(anyChar, "Jamie Willis") *> anyChar).parseFromFile(new File("LICENSE")).get shouldBe Success('\n')
}
it should "fail with an error when file does not exist" in {
Parsley.empty.parseFromFile(new File("foo.diuh")) shouldBe a [scala.util.Failure[_]]
}

"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")
noException should be thrownBy repeat(4000, 'a').parse("a")
}
}
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ 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")
addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.8") // This is here purely to enable the niceness settings
46 changes: 21 additions & 25 deletions rootdoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,28 @@ is defined as being an object which mocks a package):
strings, as well as combinators to match specific sub-sets of characters.
- [[parsley.debug$ `parsley.debug`]] contains debugging combinators, helpful for identifying faults
in parsers.
- [[parsley.io$ `parsley.io`]] contains extension methods to run parsers with input sourced from
IO sources.
- [[parsley.expr `parsley.expr`]] contains the following sub modules:
- [[parsley.expr.chain `parsley.expr.chain`]] contains combinators used in expression parsing
- [[parsley.expr.precedence `parsley.expr.precedence`]] is a builder for expression parsers built
- [[parsley.expr.chain$ `parsley.expr.chain`]] contains combinators used in expression parsing
- [[parsley.expr.precedence$ `parsley.expr.precedence`]] is a builder for expression parsers built
on a precedence table.
- [[parsley.implicits$ `parsley.implicits`]] contains several implicits to add syntactic sugar
to the combinators, such as being able to use character and string literals directly as parsers,
as well as enabling lifting of functions to work on parsers.
- [[parsley.implicits `parsley.implicits`]] contains several implicits to add syntactic sugar
to the combinators. These are sub-categorised into the following sub modules:
- [[parsley.implicits.character$ `parsley.implicits.character`]] contains implicits to allow you
to use character and string literals as parsers.
- [[parsley.implicits.combinator$ `parsley.implicits.combinator`]] contains implicits related to
combinators, such as the ability to make any parser into a `Parsley[Unit]` automatically.
- [[parsley.implicits.lift$ `parsley.implicits.lift`]] enables postfix application of the lift
combinator onto a function (or value).
- [[parsley.implicits.zipped$ `parsley.implicits.zipped`]] enables boths a reversed form of
lift where the function appears on the right and is applied on a tuple (useful when type
inference has failed) as well as a `.zipped` method for building tuples out of several
combinators.
- [[parsley.errors `parsley.errors`]] contains modules to deal with error messages, their refinement
and generation.
- [[parsley.errors.combinator$ `parsley.errors.combinator`]] provides combinators that can be
used to either produce more detailed errors as well as refine existing errors.
- [[parsley.lift$ `parsley.lift`]] contains functions which lift functions that work on regular
types to those which now combine the results of parsers returning those same types. these are
ubiquitous.
Expand All @@ -30,23 +45,4 @@ is defined as being an object which mocks a package):
- [[parsley.token `parsley.token`]] contains the [[parsley.token.Lexer `Lexer`]] class that provides
a host of helpful lexing combinators when provided with the description of a language.
- [[parsley.unsafe$ `parsley.unsafe`]] contains unsafe (and not thread-safe) ways of speeding up
the execution of a parser.

In addition to the modules and packages outlined above, this version of Parsley (up to version 3.0),
also includes the so-called `old-style` API, which is deprecated (see
[[https://github.com/j-mie6/Parsley/wiki/The-Newstyle-API-vs-the-Oldstyle-API the Parsley wiki]] for
a discussion of these differences). You should use the modules described above, and '''avoid''' the following:

- `parsley.BitGen`
- `parsley.Char`
- `parsley.CharSet`
- `parsley.Combinator`
- `parsley.ExpressionParser`
- `parsley.Impl`
- `parsley.Implicits`
- `parsley.LanguageDef`
- `parsley.NotRequired`
- `parsley.Parser`
- `parsley.Predicate`
- `parsley.Reg`
- `parsley.TokenParser`
the execution of a parser.
Loading

0 comments on commit 96843b4

Please sign in to comment.