FastParse Documentation

repository·master·Indexed 22 days ago

https://github.com/com-lihaoyi/fastparse

A high-performance parser combinators library for Scala, designed for cross-building on ScalaJVM and Scala.js. FastParse provides an expressive way to build parsers for structured text, featuring a sealed Parsed[T] hierarchy for result handling, detailed error reporting via TracedFailure, and flexible ParserInput options for Strings, Iterators, and Readable sources. It includes built-in combinators for common parsing tasks and multiple pre-defined Whitespace strategies including Scala, Java, and Jsonnet styles.

Tokens
6.9K
Snippets
26
Records
36
Agent score
78%

What's inside FastParse

  1. Getting started with FastParse

    master

    FastParse is a parser combinators library for Scala. To learn how to use the library effectively, the following resources are recommended:

  2. Run FastParse tests using Mill

    master

    If you are contributing to the FastParse repository, you can use the mill build tool (version 0.11 or later) to run the test suites.

    To run the main test suite on the JVM (e.g., using Scala 2.12.10):

    mill -w "fastparse.jvm[2.12.10].test"

    To run all tests specifically under JVM/Scala-2.12 (which is faster than the aggregate command):

    mill "__ .jvm[2.12.17].test"

    To run the aggregate test-all command (note: this is slow):

    mill __.test.test
  3. Use Expressions for Python expression parsing

    master

    The pythonparse.Expressions object provides a set of fastparse parsers for Python's expression grammar. These parsers are designed to handle components that can be used within larger expressions, such as literals, operators, and complex structures like list comprehensions or lambdas.

    Key Characteristics:

    • Whitespace/Indentation: The parsers ignore whitespace and do not rely on indentation (they are designed for expression-level parsing, not full statement/block parsing).
    • AST Output: Most parsers return an Ast.expr or related type, representing the parsed structure as an Abstract Syntax Tree.
    • Grammar Source: The grammar is manually transcribed from the official Python documentation.
    import pythonparse.Expressions
    import fastparse._
    
    // Example of how one might use the 'test' parser to parse a Python expression
    val input = "x + 1 if True else y"
    val result = parse(input, Expressions.test)
    // result will contain the parsed Ast.expr structure
  4. Choose a Whitespace strategy for your parser

    master

    FastParse provides several pre-defined Whitespace implementations that can be used as implicit whitespace handlers in your parsers. These strategies determine how the parser skips whitespace and handles comments.

    To use one, you can provide the desired object as an implicit value in the scope where your parser is defined. Common strategies include:

    • NoWhitespace: Consumes nothing.
    • SingleLineWhitespace: Consumes only spaces ( ) and tabs (\t).
    • MultiLineWhitespace: Consumes spaces, tabs, carriage returns (\r), and newlines (\n).
    • ScriptWhitespace: Supports # line comments (like Bash or Python).
    • JavaWhitespace: Supports // line comments and /* */ multiline comments (non-nesting).
    • JsonnetWhitespace: Supports both # and // line comments, and /* */ multiline comments (non-nesting).
    • ScalaWhitespace: Supports // line comments and /* */ multiline comments with nesting support.
    import fastparse.Whitespace._
    
    // Example: Using ScalaWhitespace implicitly
    // This will allow your parser to skip spaces, newlines, // comments, and nested /* comments */
    object MyParser extends RecursiveParser {
      implicit val whitespace: Whitespace = ScalaWhitespace
    
      val number = parsers.number
      val expression = number ~ number
    }
  5. Debug parse failures with .trace()

    master

    When a parse fails, the standard Failure.msg might not provide enough context. You can call .trace() on a Failure object to re-run the parse with verboseFailures = true and failure aggregation enabled. This provides a TracedFailure object containing much more detailed information about what the parser expected at the failure point.

    Note: Tracing takes approximately 2x longer than the original parse.

    val result = myParser.parse(input)
    
    result match {
      case f: Parsed.Failure =>
        // Get a highly detailed error report
        val detailedError = f.trace()
        println(detailedError.aggregateMsg)
      case _: Parsed.Success => println("Success!")
    }
  6. Handle Scala whitespace and comments

    master

    The Literals trait provides several whitespace and comment management utilities for fine-grained control over parsing:

    • WS: Parses whitespace excluding newlines (useful for blocks where semicolon inference depends on line structure).
    • WL0: Parses all whitespace including newlines.
    • WL: A non-cutting version of WL0.
    • Newline: Parses whitespace followed by a newline.
    • NotNewline: A lookahead that ensures the current position is not followed by a newline.
    • TrailingComma: Parses an optional trailing comma followed by whitespace and a newline.
  7. Parse through a Readable source

    master

    To parse data from a java.io.Readable (like a file or network stream) without loading it all into memory, use ParserInputSource.parseThrough. This method manages the lifecycle of a ReaderParserInput and ensures the stream is read correctly.

    By default, the bufferSize is 4096. You can customize this using ParserInputSource.FromReadable.

    import fastparse.ParserInputSource
    
    val myReadable: geny.Readable = ???
    val result = ParserInputSource.parseThrough(myReadable) { input: ParserInput =>
      // Run your parser here
      myParser.parse(input)
    }
  8. Parse Python collections with Expressions.atom

    master

    The atom parser handles the most basic building blocks of expressions, including collections and parenthesized expressions:

    • Empty collections: (), [], {}
    • Parenthesized expressions: (expr)
    • List comprehensions/lists: [x for x in xs] or [1, 2, 3]
    • Dict/Set comprehensions/literals: {k: v for k, v in x} or {1, 2, 3}
    • Tuples: (1, 2, 3)
    • Strings and Names: Literal strings and variable names.
    def atom[$: P]: P[Ast.expr] = {
        def empty_tuple = ("(" ~ ")").map(_ => Ast.expr.Tuple(Nil, Ast.expr_context.Load))
        def empty_list = ("[" ~ "]").map(_ => Ast.expr.List(Nil, Ast.expr_context.Load))
        def empty_dict = ("{" ~ "}").map(_ => Ast.expr.Dict(Nil, Nil))
        P(
          empty_tuple  |
          empty_list |
          empty_dict |
          "(" ~ (yield_expr | generator | tuple | test) ~ ")" |
          "[" ~ (list_comp | list) ~ "]" |
          "{" ~ dictorsetmaker ~ "}" |
          "`" ~ testlist1.map(x => Ast.expr.Repr(Ast.expr.Tuple(x, Ast.expr_context.Load))) ~ "`" |
          STRING.rep(1).map(_.mkString).map(Ast.expr.Str.apply) |
          NAME.map(Ast.expr.Name(_, Ast.expr_context.Load)) |
          NUMBER
        )
    }
  9. Handle parse results with the Parsed type

    master

    The Parsed[T] class represents the outcome of a parsing operation. It is a sealed hierarchy with two subtypes:

    1. Success[T]: Contains the parsed value and the index where parsing completed.
    2. Failure: Contains a label (error hint), the index where it failed, and extra metadata for debugging.

    You can use .fold to handle both cases safely without manual type checking.

    val result: Parsed[MyType] = myParser.parse(input)
    
    result.fold(
      onFailure = (label, index, extra) => s"Failed at $index: $label",
      onSuccess = (value, index) => s"Success: $value at $index"
    )
  10. Use common parser combinators

    master

    FastParse provides several built-in combinators for common parsing tasks:

    • Start: Succeeds only if the current index is 0.
    • End: Succeeds only if the parser has reached the end of the input.
    • IgnoreCase(s: String): Parses the string s case-insensitively.
    • Pass: A no-op parser that always succeeds without consuming any characters. Can optionally return a value: Pass(value).
    • Fail: A no-op parser that always fails. Can take a custom error message: Fail("error message").
    • Index: Returns the current index in the input as an Int. Useful for capturing source locations.
    • AnyChar: Consumes and succeeds on any single character, provided the input is not at its end.
    • SingleChar: Consumes and returns the single character parsed. Useful for one-character lookahead patterns.
    • P0: A type alias for P[Unit] (parsers that return no value).
    import fastparse._
    
    // Example of using combinators in a parser
    def myParser: P[Unit] = {
      Start ~ IgnoreCase("Hello") ~ SingleChar ~ End
    }
  11. Hide parser errors with opaque()

    master

    The opaque[T](parse0: () => P[T], msg: String) method wraps a parser and ensures that if it fails, the error message is replaced by the provided msg. If the parser succeeds, it returns the success value normally. This is useful for providing high-level, user-friendly error messages for specific grammar branches.

    import fastparse.SharedPackageDefs.opaque
    
    def userFriendlyParser: P[Unit] = opaque(internalParser, "Invalid configuration format")