A parser combinator library written in the Swift programming language.

Overview

SwiftParsec

SwiftParsec is a Swift port of the Parsec parser combinator library. It allows the creation of sophisticated parsers from a set of simple parsers. It is also easy to extend the available parsers. The parsers are fully integrated into the language, they can be put into arrays, passed as parameters, returned as values, etc. SwiftParsec provides expressiveness, is well documented and simple.

Key Features

  • Reusable combinators
  • Lexical analysis
  • Expression parser
  • Permutation phrases parser
  • Extensive error messages
  • Unicode support

Documentation

See the wiki

License

SwiftParsec is released under the “Simplified BSD License”. See the LICENSE file in the repository.

Comments
  • Faster Parsing

    Faster Parsing

    Hey @davedufresne !

    We managed to build a working parser for something like ledger-cli. The 5000-line sample file that we tried to parse (naively) took a massive amount of time to parse (the first iteration even took 13 minutes). We got it down to a few minutes.

    The popFirst() on strings is really expensive, as it reallocates a new copy of the string. We managed to speed it up a little bit by using a CharacterView, but still, this mutates the underlying _StringCore type.

    As Stream only needs the ArrayLiteralConvertible and the popFirst(), we implemented a new struct that doesn't copy at all. It only changes the "pointer" to the first element in the array. We also tried storing String.CharacterView in this struct, but that was a lot slower.

    struct ImmutableCharacters: Stream {
        var characters: [Character]
        var start: Int = 0
    
        init(string: String) {
            characters = Array(string.characters)
        }
    
        init(arrayLiteral elements: Character...) {
            characters = elements
        }
    
        mutating func popFirst() -> Character? {
            guard start < characters.count else { return nil }
            let oldStart = start
            start += 1
            return characters[oldStart]
        }
    }
    

    This helped a lot for speed. Our file now parses in 2 seconds. This is still quite slow (we'd like to be able to parse it in milliseconds).

    When running this in Instruments, we noticed at least half a second was spent retaining/releasing things. Almost certainly, the characters array. If could work on something like an UnsafeBufferPointer, there would be no need for retain or release. However, this is hard to make work with both the Parsec.string parser, and also the ArrayLiteralConvertible that Stream inherits from.

    Have you thought about these issues, and do you have any more ideas for how to make this fast? We're happy to help out. Maybe we could integrate the ImmutableCharacters struct already? It's a drop-in replacement.

    opened by chriseidhof 6
  • Support for Ubuntu

    Support for Ubuntu

    Hello again!

    I present the Linux-compatible package! I've documented the source, but let me know if there is anything that seems off.

    Closes #7


    Here are some "fun" things I learned on the way (hopefully putting it here will also make it more Google-able for others):

    Unicode order is not consistent between platforms

    "\u{2000}"..."\u{200D}" // creates a Range on macOS, crashes on Ubuntu
    "\u{200D}"..."\u{2000}" // creates a Range on Ubuntu, crashes on macOS
    

    Protocol / implementation variable names can cause linker crashes

    https://bugs.swift.org/browse/SR-2598

    This is actually the problem that previously forced me to use -whole-module-optimization. To fix it, I moved the DefaultTokenParser stuff into the same file as the protocol definition. Unfortunately, this puts the TokenParser file at > 1000 lines.

    opened by kofigumbs 5
  • Building on Ubuntu

    Building on Ubuntu

    Greetings,

    I am having trouble building this library on Ubuntu 16.04 (Swift 3.0.2). The issue may be related to a compiler bug, but I decided to post it here in case you thought otherwise. The code I'll refer to below is that in https://github.com/hkgumbs/SwiftParsec/master, which has only compatibility changes. I think the quickest way to play with Ubuntu 16.04 would be use Docker: docker run -it -v $PWD:/code -w /code swiftdocker/swift bash.

    Sources

    swift build
    ...
    	Cross-reference to module 'SwiftParsec'
    	... TokenParser
    	... in an extension in module 'SwiftParsec'
    	... identifier
    	... with type GenericParser<String, τ_0_0.UserState, String>
    

    I found a SO post that described similar symptoms, and the recommendation there is to use the -whole-module-optimization flag.

    swift build -Xswiftc -whole-module-optimization -c release
    

    The release configuration is what ultimately tells the swift-build-tool to look elsewhere for dependency mappings (Source). Otherwise, swift build fails with unable to open dependencies file '/code/.build/debug/SwiftParsec.build/CharacterMembership.d'.

    Tests

    These flags don't port as nicely to tests. I think this is the major issue since I want to verify that the changes and optimizations are not affecting the behavior of the library. The root of the problem seems to be that I cannot force -c release in test builds. One workaround is run swift test -Xswiftc -whole-module-optimization then manually modify .build/debug.yaml:

    --- a/.build/debug.yaml
    +++ b/.build/debug.yaml
    @@ -22 +22 @@ commands:
    -    enable-whole-module-optimization: false
    +    enable-whole-module-optimization: true
    @@ -38 +38 @@ commands:
    -    enable-whole-module-optimization: false
    +    enable-whole-module-optimization: true
    

    Once you make those changes, you can run the swift-build-tool manually, and it will compile the tests. However, you'll still see a problem at link time. The output is mostly gibberish - this is the farthest I've gotten:

    /usr/bin/swift-build-tool -f /code/.build/debug.yaml test
    ...
    

    Like I mentioned at the start, this doesn't seem to be an issue with your project specifically, but I thought I'd bring it up just in case you had any ideas.

    Cheers!

    opened by kofigumbs 4
  • Fixes for Swift 4.2 compiler error.

    Fixes for Swift 4.2 compiler error.

    I changed the initializations to overcome Swift 4.2 compiler error. I also had to change a string in a test case that failed -- no idea how it ran successfully before.

    opened by bradhowes 3
  • Feature/match interval

    Feature/match interval

    Adds a variant of oneOf() with a ClosedInterval<Character> argument, allowing code similar to the following:

    let alnum = 
            StringParser.oneOf("A"..."Z")
        <|> StringParser.oneOf("a"..."z") 
        <|> StringParser.oneOf("0"..."9")
    
    opened by monyschuk 3
  • Latest GitHub release does not include fix for Xcode 12.5

    Latest GitHub release does not include fix for Xcode 12.5

    Hi,

    I am using Swift Package Manager and I added this package with its latest version 4.0.0. I am using Xcode 12.5 and I got a compiler error. Unfortunately, the existing fix https://github.com/davedufresne/SwiftParsec/commit/e8d092de0c206decadfbd15c4b5a82209594bed0 (fix compiler error in Xcode 12.5) is not part of release 4.0.0 and I have to refer to the master branch. Would be great if a release 4.0.1 would be tagged including this important fix.

    Kind regards, Marco

    opened by MarcoEidinger 2
  • Getting the source position out?

    Getting the source position out?

    Hey @davedufresne ! Awesome project. We're thinking of using it for a project. The code looks really solid.

    I want to get the source position out, so that we can parse into an AST that is annotated with the source positions. Using the public API, this is currently not possible, right? Would you mind adding this if I create a PR?

    opened by chriseidhof 2
  • Operator matching '>=' vs '>'

    Operator matching '>=' vs '>'

    Hi, Thanks for a great library!

    I am porting a parser from FParsec to SwiftParsec and it's gone pretty well. One difference in behaviour is operator matching. I would like to parse '>=' and if that fails '>', but the operator behaviour doesn't allow this e.g when constructing the OperatorTable:

    [
            binary(">=", function: { Expr.Comparison($0, .Ge, $1) }, assoc: .none),
            binary("<=", function: { Expr.Comparison($0, .Le, $1) }, assoc: .none),
            binary("<", function: { Expr.Comparison($0, .Lt, $1) }, assoc: .none),
            binary(">", function: { Expr.Comparison($0, .Gt, $1) }, assoc: .none)
    ],
    

    So for var1 > 3 FParsec will attempt '>=' and then succeed on '>'. Is there a way to replicate this behaviour?

    Thanks again.

    opened by malcolm-p 1
  • Question about the ExpressionParser

    Question about the ExpressionParser

    Hi Dave..

    I have been trying to use the Expression parser to build an AST for a project.... So I dove into the code of the library and pulled out your example in the ExpressionParser.swift file..

    Here's my code:

    import SwiftParsec
    let java = LanguageDefinition<()>.javaStyle
    let lexer = GenericTokenParser(languageDefinition: java)
    let integer = lexer.integer
    
    indirect enum T {
      case I(Int)
      case MulOp(T,T)
      case DivOp(T,T)
      case AddOp(T,T)
      case SubOp(T,T)
     }
    
     func binary( name: String, function: (T, T) -> T, assoc: Associativity ) -> Operator<String, (), T> {
         let opParser = StringParser.string(name) *> GenericParser(result: function)
         return .infix(opParser, assoc)
     }
    
     let opTable: OperatorTable<String, (), T> = [
         [
            binary(name:"*", function: {a,b in return T.MulOp(a,b)}, assoc: .left),
            binary(name:"/", function: {a,b in return T.DivOp(a,b)}, assoc: .left)
         ],
         [
            binary(name:"+", function: {a,b in return T.AddOp(a,b)}, assoc: .left),
            binary(name:"-", function: {a,b in return T.SubOp(a,b)}, assoc: .left)
         ]
     ]
    
     let openingParen = StringParser.character("(")
     let closingParen = StringParser.character(")")
     let decimal = GenericTokenParser<()>.decimal
     let tint = {a in T.I(a)} <^> integer
     let expression = opTable.makeExpressionParser { expression in
         expression.between(openingParen, closingParen) <|>
             tint <?> "simple expression"
    
     } <?> "expression"
    
    expression.runSafe(userState: (), sourceName:"", input: "345+143*54")
    

    The problem is that i get an error "Playground execution failed: error: Untitled Page.xcplaygroundpage:56:24: error: cannot convert value of type 'GenericParser<String, (), (T, T) -> T>' to expected argument type 'GenericParser<_, , (, _) -> _>' return .infix(opParser, assoc) " The error is referring to the type of opParser.

    Everything appears ok to me... but then I'm not a swift (or functional programming) expert.

    Any ideas?

    Thanks bob

    opened by bobbaileyjr898 1
  • testRecursive possibly fails due to wrong associativity

    testRecursive possibly fails due to wrong associativity

    I've been studying the testRecursive method in CombinatorParsersTests.swift because I'm trying to understand how the whole recursive thing works. In order to test my understanding, I was playing around with different input and have discovered a test failure.

    If I change the matching and expected definitions to this:

            let matching = ["3-(1+2)", "3-1+2", "3-(1-(3+1))"]
            let expected = [3-(1+2), 3-1+2, 3-(1-(3+1))]
    

    It fails with the message, CombinatorParsersTests.swift:1114: error: -[SwiftParsecTests.CombinatorTests testRecursive] : XCTAssertEqual failed: ("4") is not equal to ("0")

    Whether or not this is an actual failure depends on the intended associativity of the operators, since that isn't stated anywhere, and the context leads me to believe Swift-like associativity is desired, I thought I would note it here; sorry if that is not what is intended.

    I am also sorry I can't offer a fix, but to offer a fix would require my understanding recursive and that is the very reason I'm here looking at recursive! 🤷‍♂️

    opened by DressTheMonkey 1
  • No longer compiles (Xcode 12.5)

    No longer compiles (Xcode 12.5)

    /Users/.../qubv/SourcePackages/checkouts/SwiftParsec/Sources/SwiftParsec/TokenParser.swift:435:24: Cannot convert value of type 'GenericParser<String, Self.UserState, Character?>' to expected argument type 'GenericParser<String, Self.UserState, Character>'
    
    /Users/.../qubv/SourcePackages/checkouts/SwiftParsec/Sources/SwiftParsec/TokenParser.swift:451:28: Unable to infer complex closure return type; add explicit type to disambiguate
    
    opened by daniel-beard 0
  • Able to create own parser with other types?

    Able to create own parser with other types?

    Hi! I love this library. I want to use this to parse byte sequence [UInt8]. How can I extend? I tried to extends like:

    extension Parsec
    where StreamType.Iterator.Element == UInt8, Result ==  Token {
        internal static func satisfy(
            _ predicate: @escaping (UInt8) -> Bool
            ) -> GenericParser<StreamType, UserState, Result> {
            
            return tokenPrimitive(
                tokenDescription: { String(reflecting: $0) },
                nextPosition: { position, elem in
                    
                    var pos = position
                    pos.updatePosition(elem)
                    
                    return pos
                    
            },
                match: { elem in
                    
                    predicate(elem) ? Token.seq([elem]) : nil
            })
        }
        
        static func oneOf(_ list: [UInt8]) -> GenericParser<StreamType, UserState, Result> {
        }
    }
    

    But I got an error with pos.updatePosition ..

    opened by pocket7878 1
Owner
David Dufresne
David Dufresne
A repository for showcasing my knowledge of the Swift programming language, and continuing to learn the language.

Learning Swift (programming language) I know very little about programming in the Swift programming language. This document will go over all my knowle

Sean P. Myrick V19.1.7.2 2 Nov 8, 2022
A Cocoa library to extend the Objective-C programming language.

The Extended Objective-C library extends the dynamism of the Objective-C programming language to support additional patterns present in other programm

Justin Spahr-Summers 4.5k Dec 30, 2022
Numerals is a package containing additional numeric types for the Swift programming language.

swift-numerals Numerals is a package containing additional numeric types for the Swift programming language. Contents The package currently provides t

Alexandre H. Saad 0 Jul 28, 2022
Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. (Pure Swift, Supports Linux)

SwiftFoundation Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. Goals Provide a cross-platform in

null 620 Oct 11, 2022
A Powerful , Extensible CSS Parser written in pure Swift.

A Powerful , Extensible CSS Parser written in pure Swift.

null 273 Sep 9, 2022
A simple, but efficient CSV Parser, written in Swift.

CSV CSV.swift is a powerful swift library for parsing CSV files that supports reading as [String], [String: String] and Decodable, without sacrificing

Ben Koska 4 Nov 28, 2022
An SSH config parser library with a fancy API

The SshConfig makes it quick and easy to load, parse, and decode/encode the SSH configs. It also helps to resolve the properties by hostname and use them safely in your apps (thanks for Optional and static types in Swift).

Artem Labazin 8 Nov 25, 2022
🏹 Bow is a cross-platform library for Typed Functional Programming in Swift

Bow is a cross-platform library for Typed Functional Programming in Swift. Documentation All documentation and API reference is published in our websi

Bow 613 Dec 20, 2022
.DS_Store file parser/viewer.

.DS_Store file parser/viewer.

JD Gadina 51 Dec 1, 2022
ParserCombinators - String Parser Construction Kit

ParserCombinators provides a set of elementary building blocks for deriving stru

Marcel Tesch 0 Jan 7, 2022
HxSTLParser is a basic STL parser capable of loading STL files into an SCNNode

HxSTLParser HxSTLParser is a basic STL parser capable of loading STL files into an SCNNode. Installing Via Carthage Just add it to your Cartfile githu

Victor 23 Dec 16, 2022
A result builder that build HTML parser and transform HTML elements to strongly-typed result, inspired by RegexBuilder.

HTMLParserBuilder A result builder that build HTML parser and transform HTML elements to strongly-typed result, inspired by RegexBuilder. Note: Captur

null 4 Aug 25, 2022
iOS Logs, Events, And Plist Parser

iLEAPP iOS Logs, Events, And Plists Parser Details in blog post here: https://abrignoni.blogspot.com/2019/12/ileapp-ios-logs-events-and-properties.htm

Brigs 421 Jan 5, 2023
Pure Declarative Programming in Swift, Among Other Things

Basis The Basis is an exploration of pure declarative programming and reasoning in Swift. It by no means contains idiomatic code, but is instead inten

TypeLift 314 Dec 22, 2022
Functional programming in Swift

Swiftz Swiftz is a Swift library for functional programming. It defines functional data structures, functions, idioms, and extensions that augment the

TypeLift 3.3k Dec 25, 2022
A functional tool-belt for Swift Language similar to Lo-Dash or Underscore.js in Javascript

Dollar Dollar is a Swift library that provides useful functional programming helper methods without extending any built in objects. It is similar to L

Ankur Patel 4.2k Jan 2, 2023
noppefoxwolf/notion is a notion.so API library written in swift.

notion noppefoxwolf/notion is a notion.so API library written in swift. Installation Xcode Project > Swift Packages [email protected]:noppefoxwolf/notion

noppefoxwolf 44 Oct 7, 2022
Swift-HorizontalPickerView - Customizable horizontal picker view component written in Swift for UIKit/iOS

Horizontal Picker View Customizable horizontal picker view component written in

Afraz Siddiqui 8 Aug 1, 2022