├── lintPod.sh ├── pushPod.sh ├── releasePod.sh ├── renovate.json ├── AUTHORS ├── src ├── swift-petitparser.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcuserdata │ │ │ └── philipparndt.xcuserdatad │ │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ │ └── xcdebugger │ │ │ │ └── Expressions.xcexplist │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ ├── xcbaselines │ │ │ └── 22872F6523AA077300713E7E.xcbaseline │ │ │ │ ├── 1E6BBEE8-D55F-4BF0-B267-C4BBD1E161C2.plist │ │ │ │ ├── A5055CAB-26C4-4888-9CAB-744059C29025.plist │ │ │ │ └── Info.plist │ │ └── xcschemes │ │ │ └── swift-petitparser.xcscheme │ └── xcuserdata │ │ └── philipparndt.xcuserdatad │ │ ├── xcschemes │ │ └── xcschememanagement.plist │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── swift-petitparser │ ├── parser │ │ ├── primitive │ │ │ ├── StringPredicate.swift │ │ │ ├── EpsilonParser.swift │ │ │ ├── FailureParser.swift │ │ │ ├── NumbersParser.swift │ │ │ ├── CharacterRange.swift │ │ │ ├── StringParser.swift │ │ │ ├── CharacterParser.swift │ │ │ └── CharacterPredicate.swift │ │ ├── repeating │ │ │ ├── LimitedRepeatingParser.swift │ │ │ ├── PossessiveRepeatingParser.swift │ │ │ ├── LazyRepeatingParser.swift │ │ │ ├── RepeatingParser.swift │ │ │ └── GreedyRepeatingParser.swift │ │ ├── ParserOps.swift │ │ ├── combinators │ │ │ ├── ListParser.swift │ │ │ ├── AndParser.swift │ │ │ ├── SettableParser.swift │ │ │ ├── DelegateParser.swift │ │ │ ├── EndOfInputParser.swift │ │ │ ├── NotParser.swift │ │ │ ├── ChoiceParser.swift │ │ │ ├── OptionalParser.swift │ │ │ ├── ValidationParser.swift │ │ │ └── SequenceParser.swift │ │ ├── actions │ │ │ ├── TokenParser.swift │ │ │ ├── ContinuationParser.swift │ │ │ ├── ActionParser.swift │ │ │ ├── FlattenParser.swift │ │ │ └── TrimmingParser.swift │ │ └── Parser.swift │ ├── context │ │ ├── Failure.swift │ │ ├── Success.swift │ │ ├── Result.swift │ │ ├── Context.swift │ │ └── Token.swift │ ├── tools │ │ ├── GrammarParser.swift │ │ ├── ExpressionBuilder.swift │ │ ├── Mirror.swift │ │ ├── Tracer.swift │ │ ├── GrammarDefinition.swift │ │ └── ExpressionGroup.swift │ └── utils │ │ └── Functions.swift ├── .swiftlint.yml └── Tests │ ├── Info.plist │ ├── Ex.swift │ ├── TracerTest.swift │ ├── NumbersParserTest.swift │ ├── PrimitiveTests.swift │ ├── GrammarDefinitionTest.swift │ ├── Assert.swift │ ├── Tests.swift │ ├── TokenTest.swift │ ├── EqualityTest.swift │ ├── ExpressionBuilderTest.swift │ ├── ExamplesTest.swift │ ├── CharacterTest.swift │ └── ParsersTest.swift ├── .github └── workflows │ └── swift.yml ├── Package.swift ├── LICENSE ├── swift-petitparser.podspec └── README.md /lintPod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pod spec lint swift-petitparser.podspec -------------------------------------------------------------------------------- /pushPod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pod trunk push swift-petitparser.podspec -------------------------------------------------------------------------------- /releasePod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pod spec lint swift-petitparser.podspec -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 2 | Lukas Renggli (http://www.lukas-renggli.ch) 3 | Philipp Arndt (http://github.com/philipparndt/) -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/project.xcworkspace/xcuserdata/philipparndt.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipparndt/swift-petitparser/HEAD/src/swift-petitparser.xcodeproj/project.xcworkspace/xcuserdata/philipparndt.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macOS-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: | 14 | cd src 15 | xcodebuild clean test -project swift-petitparser.xcodeproj -scheme swift-petitparser CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO 16 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/StringPredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringPredicate.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class StringPredicate { 12 | var test: (String) -> Bool 13 | 14 | init(matcher: @escaping (String) -> Bool) { 15 | self.test = matcher 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/swift-petitparser/context/Failure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Failure.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Failure: AbstractResult, Result { 12 | 13 | public init(_ buffer: String, _ position: String.Index, _ message: String) { 14 | super.init(message, buffer, position) 15 | } 16 | 17 | override func isFailure() -> Bool { 18 | return true 19 | } 20 | 21 | public func get() -> T? { 22 | return nil 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/swift-petitparser/context/Success.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Success.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Success: AbstractResult, Result { 12 | 13 | let result: Any 14 | 15 | public init(_ buffer: String, _ position: String.Index, _ result: Any) { 16 | self.result = result 17 | super.init("", buffer, position) 18 | } 19 | 20 | override func isSuccess() -> Bool { 21 | return true 22 | } 23 | 24 | public func get() -> T? { 25 | return result as? T 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/swift-petitparser/tools/GrammarParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GrammarParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-22. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class GrammarParser: DelegateParser { 12 | public init(_ definition: GrammarDefinition) { 13 | super.init(definition.build()) 14 | } 15 | 16 | public init(_ definition: GrammarDefinition, _ name: String) { 17 | super.init(definition.build(name)) 18 | } 19 | 20 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 21 | return delegate.fastParseOn(buffer, position) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "swift-petitparser", 7 | products: [ 8 | .library(name: "swift-petitparser", targets: ["swift-petitparser"]), 9 | ], 10 | dependencies: [], 11 | targets: [ 12 | .target( 13 | name: "swift-petitparser", 14 | dependencies: [], 15 | path: "src/swift-petitparser/" 16 | ), 17 | .testTarget( 18 | name: "swift-petitparser-tests", 19 | dependencies: ["swift-petitparser"], 20 | path: "src/Tests/" 21 | ), 22 | ] 23 | ) -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/xcshareddata/xcbaselines/22872F6523AA077300713E7E.xcbaseline/1E6BBEE8-D55F-4BF0-B267-C4BBD1E161C2.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | Tests 8 | 9 | testPerformanceExample() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 9.16e-06 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/EpsilonParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EpsilonParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class EpsilonParser: Parser { 12 | public override init() { 13 | super.init() 14 | } 15 | 16 | public override func parseOn(_ context: Context) -> Result { 17 | return context.success(NSNull()) 18 | } 19 | 20 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 21 | return position 22 | } 23 | 24 | public override func copy() -> Parser { 25 | return EpsilonParser() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/xcshareddata/xcbaselines/22872F6523AA077300713E7E.xcbaseline/A5055CAB-26C4-4888-9CAB-744059C29025.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | Tests 8 | 9 | testPerformanceExample() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 1.5812e-05 15 | baselineIntegrationDisplayName 16 | 2019-12-20 at 15:03:37 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/swift-petitparser/context/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class AbstractResult: Context { 12 | 13 | let message: String? 14 | 15 | public init(_ message: String?, _ buffer: String, _ position: String.Index) { 16 | self.message = message 17 | 18 | super.init(buffer, position) 19 | } 20 | 21 | func isSuccess() -> Bool { 22 | return false 23 | } 24 | 25 | func isFailure() -> Bool { 26 | return false 27 | } 28 | } 29 | 30 | public protocol Result: AbstractResult { 31 | func get() -> T? 32 | } 33 | -------------------------------------------------------------------------------- /src/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - force_cast 4 | - identifier_name 5 | - statement_position 6 | opt_in_rules: 7 | - empty_count 8 | - empty_string 9 | excluded: 10 | - Carthage 11 | - Pods 12 | - SwiftLint/Common/3rdPartyLib 13 | line_length: 14 | warning: 150 15 | error: 200 16 | ignores_function_declarations: true 17 | ignores_comments: true 18 | ignores_urls: true 19 | function_body_length: 20 | warning: 300 21 | error: 500 22 | function_parameter_count: 23 | warning: 6 24 | error: 8 25 | type_body_length: 26 | warning: 300 27 | error: 500 28 | file_length: 29 | warning: 1000 30 | error: 1500 31 | ignore_comment_only_lines: true 32 | cyclomatic_complexity: 33 | warning: 15 34 | error: 25 35 | reporter: "xcode" 36 | -------------------------------------------------------------------------------- /src/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/repeating/LimitedRepeatingParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LimitedRepeatingParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class LimitedRepeatingParser: RepeatingParser { 12 | var limit: Parser 13 | 14 | init(_ delegate: Parser, _ limit: Parser, _ min: Int, _ max: Int) { 15 | self.limit = limit 16 | super.init(delegate, min, max) 17 | } 18 | 19 | override func getChildren() -> [Parser] { 20 | return [delegate, limit] 21 | } 22 | 23 | override func replace(_ source: Parser, _ target: Parser) { 24 | super.replace(source, target) 25 | if limit === source { 26 | self.limit = target 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/swift-petitparser/tools/ExpressionBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpressionBuilder.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-20. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ExpressionBuilder { 12 | let loopback = SettableParser.undefined() 13 | var groups: [ExpressionGroup] = [] 14 | 15 | public func group() -> ExpressionGroup { 16 | let group = ExpressionGroup(loopback) 17 | groups.append(group) 18 | return group 19 | } 20 | 21 | public func build() -> Parser { 22 | var parser = FailureParser.withMessage("Highest priority group should define a primitive parser.") 23 | for group in groups { 24 | parser = group.build(parser) 25 | } 26 | loopback.set(parser) 27 | return parser 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/xcuserdata/philipparndt.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Tests.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | swift-petitparser.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 22872F4823AA021C00713E7E 21 | 22 | primary 23 | 24 | 25 | 22872F6523AA077300713E7E 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/ParserOps.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParserOps.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2020-01-05. 6 | // Copyright © 2020 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | postfix operator <+> 12 | postfix operator <*> 13 | 14 | extension Parser { 15 | static func + (lhs: Parser, rhs: Parser) -> Parser { 16 | return lhs.seq(rhs) 17 | } 18 | 19 | static func & (lhs: Parser, rhs: Parser) -> Parser { 20 | return lhs.and().seq(rhs) 21 | } 22 | 23 | static func | (lhs: Parser, rhs: Parser) -> Parser { 24 | return lhs.or(rhs) 25 | } 26 | 27 | static postfix func <+> (parser: Parser) -> Parser { 28 | return parser.plus() 29 | } 30 | 31 | static postfix func <*> (parser: Parser) -> Parser { 32 | return parser.star() 33 | } 34 | 35 | static prefix func ! (parser: Parser) -> Parser { 36 | return parser.not() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/ListParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ListParser: Parser { 12 | 13 | var parsers: [Parser] 14 | 15 | public init(_ parsers: [Parser]) { 16 | self.parsers = parsers 17 | 18 | super.init() 19 | } 20 | 21 | public init(_ parsers: Parser...) { 22 | self.parsers = parsers 23 | 24 | super.init() 25 | } 26 | 27 | public override func replace(_ source: Parser, _ target: Parser) { 28 | super.replace(source, target) 29 | 30 | if let index = (parsers.firstIndex { $0 === source }) { 31 | parsers[index] = target 32 | } 33 | } 34 | 35 | public override func getChildren() -> [Parser] { 36 | return parsers 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/AndParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AndParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class AndParser: DelegateParser { 12 | 13 | public override func parseOn(_ context: Context) -> Result { 14 | let result = delegate.parseOn(context) 15 | if result.isSuccess() { 16 | return context.success(result.get()!) 17 | } 18 | else { 19 | return result 20 | } 21 | } 22 | 23 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 24 | let result = delegate.fastParseOn(buffer, position) 25 | return result == nil ? nil : position 26 | } 27 | 28 | public override func copy() -> Parser { 29 | return AndParser(delegate) 30 | } 31 | } 32 | 33 | extension Parser { 34 | public func and() -> Parser { 35 | return AndParser(self) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/actions/TokenParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class TokenParser: DelegateParser { 12 | 13 | public override func parseOn(_ context: Context) -> Result { 14 | let result = delegate.parseOn(context) 15 | if result.isSuccess() { 16 | let token = Token(context.buffer, context.position, result.position, result.get()!) 17 | return result.success(token) 18 | } 19 | else { 20 | return result 21 | } 22 | } 23 | 24 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 25 | return delegate.fastParseOn(buffer, position) 26 | } 27 | 28 | public override func copy() -> Parser { 29 | return TokenParser(delegate) 30 | } 31 | } 32 | 33 | extension Parser { 34 | public func token() -> Parser { 35 | return TokenParser(self) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2006-2019 Lukas Renggli. 4 | All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/swift-petitparser/parser/actions/ContinuationParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContinuationParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol ContinuationHandler { 12 | func apply(_ continuation: @escaping (Context) -> Result, _ context: Context) -> Result 13 | } 14 | 15 | public class ContinuationParser: DelegateParser { 16 | let handler: ContinuationHandler 17 | 18 | public init(_ delegate: Parser, _ handler: ContinuationHandler) { 19 | self.handler = handler 20 | super.init(delegate) 21 | } 22 | 23 | public override func parseOn(_ context: Context) -> Result { 24 | handler.apply(super.parseOn, context) 25 | } 26 | 27 | public override func hasEqualProperties(_ other: Parser) -> Bool { 28 | return ObjectIdentifier(self) == ObjectIdentifier(other) 29 | } 30 | 31 | public override func copy() -> Parser { 32 | return ContinuationParser(delegate, handler) 33 | } 34 | } 35 | 36 | extension Parser { 37 | public func callCC(_ handler: ContinuationHandler) -> Parser { 38 | return ContinuationParser(self, handler) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/FailureParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FailureParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class FailureParser: Parser { 12 | 13 | let message: String 14 | 15 | public init(_ message: String) { 16 | self.message = message 17 | super.init() 18 | } 19 | 20 | public class func withMessage(_ message: String) -> Parser { 21 | return FailureParser(message) 22 | } 23 | 24 | public override func parseOn(_ context: Context) -> Result { 25 | return context.failure(message) 26 | } 27 | 28 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 29 | return nil 30 | } 31 | 32 | public override func hasEqualProperties(_ other: Parser) -> Bool { 33 | if let oth = other as? FailureParser { 34 | return super.hasEqualProperties(other) 35 | && oth.message == message 36 | } 37 | 38 | return false 39 | } 40 | 41 | public override func copy() -> Parser { 42 | return FailureParser(message) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/SettableParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettableParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class SettableParser: DelegateParser { 12 | 13 | public class func undefined() -> SettableParser { 14 | return undefined("Undefined parser") 15 | } 16 | 17 | public class func undefined(_ message: String) -> SettableParser { 18 | return with(FailureParser.withMessage(message)) 19 | } 20 | 21 | public class func with(_ parser: Parser) -> SettableParser { 22 | return SettableParser(parser) 23 | } 24 | 25 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 26 | return delegate.fastParseOn(buffer, position) 27 | } 28 | 29 | public func get() -> Parser { 30 | return delegate 31 | } 32 | 33 | public func set(_ delegate: Parser) { 34 | self.delegate = delegate 35 | } 36 | 37 | public override func copy() -> Parser { 38 | return SettableParser(delegate) 39 | } 40 | 41 | } 42 | 43 | extension Parser { 44 | public func settable() -> SettableParser { 45 | return SettableParser.with(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Tests/Ex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ex.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-26. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class Example: XCTestCase { 14 | func testParseInt() { 15 | let id = CP.letter().seq(CP.letter().or(CP.digit()).star()) 16 | let id1 = id.parse("yeah") 17 | let id2 = id.parse("f12") 18 | print(id1.get()!) 19 | print(id2.get()!) 20 | 21 | let text = "123" 22 | let id3 = id.parse(text) 23 | print(id3.message!) 24 | print(id3.position.utf16Offset(in: text)) 25 | 26 | print(id.accept("foo")) // true 27 | print(id.accept("123")) // false 28 | 29 | let id_b = CP.letter().seq(CP.word().star()).flatten() 30 | print(id_b.parse("yeah").get()!) 31 | } 32 | 33 | func testMatchesSkipping() { 34 | let id = CP.letter().seq(CP.word().star()).flatten() 35 | let matches: [String] = id.matchesSkipping("foo 123 bar4") 36 | print(matches) // ["foo", "bar4"] 37 | } 38 | 39 | func testParseNumber() { 40 | let number = CP.digit().plus().flatten().trim() 41 | .map { (d: String) -> Int in Int(d)! } 42 | 43 | print(number.parse("123").get()!) // 123 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /swift-petitparser.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint swift-petitparser.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see https://guides.cocoapods.org/syntax/podspec.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |spec| 10 | spec.swift_version = '5.0' 11 | spec.name = "swift-petitparser" 12 | spec.version = "1.2.1" 13 | spec.summary = "PetitParser for Swift - Parser combinator using fluent API" 14 | spec.description = <<-DESC 15 | Parser combinator using fluent API written completely in Swift. This library is available for other languages as well. 16 | DESC 17 | spec.homepage = "https://github.com/philipparndt/swift-petitparser" 18 | # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 19 | spec.license = { :type => "MIT", :file => "LICENSE" } 20 | spec.author = { "Philipp Arndt" => "2f.mail@gmx.de" } 21 | 22 | # When using multiple platforms 23 | spec.ios.deployment_target = "13.0" 24 | spec.osx.deployment_target = "10.15" 25 | spec.watchos.deployment_target = "6.0" 26 | spec.tvos.deployment_target = "13.0" 27 | spec.source = { :git => "https://github.com/philipparndt/swift-petitparser.git", :tag => "#{spec.version}" } 28 | spec.source_files = "src/swift-petitparser/**/*.swift" 29 | end 30 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/DelegateParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DelegateParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class DelegateParser: Parser { 12 | // swiftlint:disable weak_delegate 13 | var delegate: Parser 14 | 15 | public init(_ delegate: Parser) { 16 | self.delegate = delegate 17 | super.init() 18 | } 19 | 20 | public override func parseOn(_ context: Context) -> Result { 21 | return delegate.parseOn(context) 22 | } 23 | 24 | public override func replace(_ source: Parser, _ target: Parser) { 25 | super.replace(source, target) 26 | if delegate === source { 27 | delegate = target 28 | } 29 | } 30 | 31 | public override func getChildren() -> [Parser] { 32 | return [delegate] 33 | } 34 | 35 | public override func copy() -> Parser { 36 | return DelegateParser(delegate) 37 | } 38 | } 39 | 40 | extension DelegateParser: CustomStringConvertible { 41 | public var description: String { 42 | let className = String(describing: type(of: self)) 43 | 44 | if self is RepeatingParser { 45 | let repeating = self as! RepeatingParser 46 | return "\(className)[\(repeating.getRange())]" 47 | } 48 | else { 49 | return "\(className)" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/EndOfInputParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EndOfInputParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class EndOfInputParser: Parser { 12 | let message: String 13 | 14 | init(_ message: String) { 15 | self.message = message 16 | 17 | super.init() 18 | } 19 | 20 | public override func parseOn(_ context: Context) -> Result { 21 | let range = context.position.. String.Index? { 26 | return position < buffer.endIndex ? nil : position 27 | } 28 | 29 | public override func hasEqualProperties(_ other: Parser) -> Bool { 30 | if let oth = other as? EndOfInputParser { 31 | return super.hasEqualProperties(other) 32 | && oth.message == message 33 | } 34 | 35 | return false 36 | } 37 | 38 | public override func copy() -> Parser { 39 | return EndOfInputParser(message) 40 | } 41 | } 42 | 43 | extension Parser { 44 | public func end(_ message: String = "end of input expected") -> Parser { 45 | let parser = EndOfInputParser(message) 46 | return SequenceParser(self, parser).pick(0) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/actions/ActionParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ActionParser: DelegateParser { 12 | let function: (T) -> R 13 | let hasSideEffects: Bool 14 | 15 | init(_ delegate: Parser, _ function: @escaping (T) -> R) { 16 | self.function = function 17 | self.hasSideEffects = false 18 | super.init(delegate) 19 | } 20 | 21 | init(_ delegate: Parser, _ function: @escaping (T) -> R, _ hasSideEffects: Bool) { 22 | self.function = function 23 | self.hasSideEffects = hasSideEffects 24 | super.init(delegate) 25 | } 26 | 27 | public override func parseOn(_ context: Context) -> Result { 28 | let result = delegate.parseOn(context) 29 | 30 | if result.isSuccess() { 31 | let value: T = result.get()! 32 | 33 | let mapped = function(value) 34 | return result.success(mapped) 35 | } 36 | return result 37 | } 38 | 39 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 40 | return hasSideEffects ? super.fastParseOn(buffer, position) : 41 | delegate.fastParseOn(buffer, position) 42 | } 43 | 44 | public override func copy() -> Parser { 45 | return ActionParser(delegate, function) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/NotParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class NotParser: DelegateParser { 12 | 13 | let message: String 14 | 15 | public init(_ delegate: Parser, _ message: String) { 16 | self.message = message 17 | super.init(delegate) 18 | } 19 | 20 | public override func parseOn(_ context: Context) -> Result { 21 | let result = delegate.parseOn(context) 22 | if result.isFailure() { 23 | return context.success(NSNull()) 24 | } 25 | else { 26 | return context.failure(message) 27 | } 28 | } 29 | 30 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 31 | let result = delegate.fastParseOn(buffer, position) 32 | return result == nil ? position : nil 33 | } 34 | 35 | public override func hasEqualProperties(_ other: Parser) -> Bool { 36 | if let oth = other as? NotParser { 37 | return super.hasEqualProperties(other) 38 | && oth.message == message 39 | } 40 | 41 | return false 42 | } 43 | 44 | public override func copy() -> Parser { 45 | return NotParser(delegate, message) 46 | } 47 | } 48 | 49 | extension Parser { 50 | public func not(_ message: String = "unexpected") -> Parser { 51 | return NotParser(self, message) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/NumbersParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Numbers.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-24. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class NumbersParser { 12 | public class func int() -> Parser { 13 | return CharacterParser.digit().plus() 14 | .flatten() 15 | .map { (int: String) -> Int in Int(int)! } 16 | } 17 | 18 | public class func int(from start: Int, to end: Int) -> Parser { 19 | let validator: (Context, Int) -> Result? = { 20 | if $1 >= start && $1 <= end { 21 | return nil 22 | } 23 | else { 24 | return $0.failure("Expected value in range \(start)..\(end)") 25 | } 26 | } 27 | 28 | return int().validate(validator) 29 | } 30 | 31 | public class func double() -> Parser { 32 | return CharacterParser.digit().plus().seq(CharacterParser.of(".") 33 | .seq(CharacterParser.digit().plus()).optional()) 34 | .flatten() 35 | .map { (double: String) -> Double in Double(double)! } 36 | } 37 | 38 | public class func double(from start: Double, to end: Double) -> Parser { 39 | let validator: (Context, Double) -> Result? = { 40 | if $1 >= start && $1 <= end { 41 | return nil 42 | } 43 | else { 44 | return $0.failure("Expected value in range \(start)..\(end)") 45 | } 46 | } 47 | 48 | return double().validate(validator) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/swift-petitparser/context/Context.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Context.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Context { 12 | let buffer: String 13 | let position: String.Index 14 | 15 | init(_ buffer: String, _ position: String.Index) { 16 | self.buffer = buffer 17 | self.position = position 18 | } 19 | 20 | public func success(_ value: Any) -> Success { 21 | return success(value, position) 22 | } 23 | 24 | public func success(_ value: Any, _ position: String.Index) -> Success { 25 | return Success(buffer, position, value) 26 | } 27 | 28 | public func failure(_ message: String) -> Failure { 29 | return failure(message, position) 30 | } 31 | 32 | public func failure(_ message: String, _ position: String.Index) -> Failure { 33 | return Failure(buffer, position, message) 34 | } 35 | } 36 | 37 | extension Context: CustomStringConvertible { 38 | public var description: String { 39 | let className = String(describing: type(of: self)) 40 | let tuple = Token.lineAndColumnOf(buffer, position) 41 | let string = "\(className)[\(tuple.0):\(tuple.1)]" 42 | 43 | if self is Failure { 44 | let failure = self as! Failure 45 | return "\(string): \(failure.message ?? "")" 46 | } 47 | else if self is Success { 48 | let success = self as! Success 49 | return "\(string): \(success.result)" 50 | } 51 | else { 52 | return string 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/ChoiceParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChoiceParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ChoiceParser: ListParser { 12 | 13 | public override init(_ parsers: [Parser]) { 14 | super.init(parsers) 15 | } 16 | 17 | public override func parseOn(_ context: Context) -> Result { 18 | var result: Result = Failure("", "".endIndex, "") 19 | 20 | for parser in parsers { 21 | result = parser.parseOn(context) 22 | if result.isSuccess() { 23 | return result 24 | } 25 | } 26 | 27 | return result 28 | } 29 | 30 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 31 | for parser in parsers { 32 | if let result = parser.fastParseOn(buffer, position) { 33 | return result 34 | } 35 | } 36 | 37 | return nil 38 | } 39 | 40 | public override func or(_ others: Parser...) -> ChoiceParser { 41 | var parsers = self.parsers 42 | for other in others { 43 | parsers.append(other) 44 | } 45 | return ChoiceParser(parsers) 46 | } 47 | 48 | public override func copy() -> Parser { 49 | return ChoiceParser(parsers) 50 | } 51 | } 52 | 53 | extension ChoiceParser: CustomStringConvertible { 54 | public var description: String { 55 | let className = String(describing: type(of: self)) 56 | 57 | return "\(className)" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/OptionalParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class OptionalParser: DelegateParser { 12 | let otherwise: NSObject? 13 | 14 | public init(_ delegate: Parser, _ otherwise: NSObject?) { 15 | self.otherwise = otherwise 16 | super.init(delegate) 17 | } 18 | 19 | public override func parseOn(_ context: Context) -> Result { 20 | let result = delegate.parseOn(context) 21 | if result.isSuccess() { 22 | return result 23 | } 24 | else { 25 | return context.success(otherwise ?? NSNull()) 26 | } 27 | } 28 | 29 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 30 | let result = delegate.fastParseOn(buffer, position) 31 | return result == nil ? position : result 32 | } 33 | 34 | public override func hasEqualProperties(_ other: Parser) -> Bool { 35 | if let oth = other as? OptionalParser { 36 | return super.hasEqualProperties(other) 37 | && 38 | (otherwise == nil && oth.otherwise == nil) || 39 | (otherwise != nil && oth.otherwise != nil && oth.otherwise! === otherwise!) 40 | } 41 | 42 | return false 43 | } 44 | 45 | public override func copy() -> Parser { 46 | return OptionalParser(delegate, otherwise) 47 | } 48 | } 49 | 50 | extension Parser { 51 | public func optional() -> Parser { 52 | return optional(nil) 53 | } 54 | 55 | public func optional(_ otherwise: NSObject?) -> Parser { 56 | return OptionalParser(self, otherwise) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/ValidationParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValidationParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-28. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ValidationParser: DelegateParser { 12 | let message: String 13 | let validator: (Context, T) -> Result? 14 | 15 | public init(_ delegate: Parser, _ validator: @escaping (Context, T) -> Result?, _ message: String) { 16 | self.message = message 17 | self.validator = validator 18 | super.init(delegate) 19 | } 20 | 21 | public override func parseOn(_ context: Context) -> Result { 22 | let result = delegate.parseOn(context) 23 | 24 | if result.isFailure() { 25 | return result 26 | } 27 | else { 28 | let value: T = result.get()! 29 | return validator(context, value) ?? result 30 | } 31 | } 32 | 33 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 34 | let result = delegate.fastParseOn(buffer, position) 35 | return result == nil ? position : nil 36 | } 37 | 38 | public override func hasEqualProperties(_ other: Parser) -> Bool { 39 | if let oth = other as? ValidationParser { 40 | return super.hasEqualProperties(other) 41 | && oth.message == message 42 | } 43 | 44 | return false 45 | } 46 | 47 | public override func copy() -> Parser { 48 | return ValidationParser(delegate, validator, message) 49 | } 50 | } 51 | 52 | extension Parser { 53 | public func validate(_ validator: @escaping (Context, T) -> Result?, _ message: String = "validation failed") -> Parser { 54 | return ValidationParser(self, validator, message) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/actions/FlattenParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlattenParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class FlattenParser: DelegateParser { 12 | 13 | let message: String? 14 | 15 | override init(_ delegate: Parser) { 16 | self.message = nil 17 | super.init(delegate) 18 | } 19 | 20 | init(_ delegate: Parser, _ message: String?) { 21 | self.message = message 22 | super.init(delegate) 23 | } 24 | 25 | public override func parseOn(_ context: Context) -> Result { 26 | if message == nil { 27 | let result = delegate.parseOn(context) 28 | if result.isSuccess() { 29 | let flattened = context.buffer[context.position.. Parser { 49 | return FlattenParser(delegate, message) 50 | } 51 | } 52 | 53 | extension Parser { 54 | public func flatten() -> Parser { 55 | return FlattenParser(self) 56 | } 57 | 58 | public func flatten(_ message: String) -> Parser { 59 | return FlattenParser(self, message) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/combinators/SequenceParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SequenceParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class SequenceParser: ListParser { 12 | 13 | public override init(_ parsers: [Parser]) { 14 | super.init(parsers) 15 | } 16 | 17 | public override init(_ parsers: Parser...) { 18 | super.init(parsers) 19 | } 20 | 21 | public override func parseOn(_ context: Context) -> Result { 22 | var current = context 23 | 24 | var elements: [Any] = [] 25 | for parser in parsers { 26 | let result = parser.parseOn(current) 27 | if result.isFailure() { 28 | return result 29 | } 30 | elements.append(result.get()!) 31 | current = result 32 | } 33 | 34 | return current.success(elements) 35 | } 36 | 37 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 38 | var pos = position 39 | for parser in parsers { 40 | if let nextpos = parser.fastParseOn(buffer, pos) { 41 | pos = nextpos 42 | } 43 | else { 44 | return nil 45 | } 46 | } 47 | return pos 48 | } 49 | 50 | public override func seq(_ others: Parser...) -> SequenceParser { 51 | var parsers = self.parsers 52 | for other in others { 53 | parsers.append(other) 54 | } 55 | return SequenceParser(parsers) 56 | } 57 | 58 | public override func copy() -> Parser { 59 | return SequenceParser(parsers) 60 | } 61 | } 62 | 63 | extension SequenceParser: CustomStringConvertible { 64 | public var description: String { 65 | let className = String(describing: type(of: self)) 66 | 67 | return "\(className)" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/swift-petitparser/utils/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Functions { 12 | public class func nthOfList(_ index: Int) -> ([T]) -> T { 13 | return { list in index < 0 ? list[list.count + index] : list[index]} 14 | } 15 | 16 | public class func permutationOfList(_ indexes: [Int]) -> ([Any]) -> [Any] { 17 | return { list in 18 | var result: [Any] = [] 19 | for index in indexes { 20 | result.append(list[index < 0 ? list.count + index : index]) 21 | } 22 | return result 23 | } 24 | } 25 | 26 | public class func separateByUnpack() -> ([Any]) -> [Any] { 27 | return { input in 28 | var result: [Any] = [] 29 | 30 | result.append(input[0]) 31 | 32 | if input.count > 1 { 33 | if let elements = input[1] as? [[Any]] { 34 | for child in elements { 35 | for inner in child { 36 | result.append(inner) 37 | } 38 | } 39 | } 40 | } 41 | 42 | return result 43 | } 44 | } 45 | 46 | public class func delimitedByUnpack() -> ([Any]) -> [Any] { 47 | return { input in 48 | var result: [Any] = [] 49 | 50 | if let elements = input[0] as? [Any] { 51 | for child in elements { 52 | result.append(child) 53 | } 54 | } 55 | 56 | if input.count > 1 { 57 | let other = input[1] 58 | if !(other is NSNull) { 59 | result.append(other) 60 | } 61 | } 62 | 63 | return result 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/Tests/TracerTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TracerTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-23. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class TracerTest: XCTestCase { 14 | static let IDENTIFIER: Parser = CP.letter().seq(CP.word().star()).flatten() 15 | 16 | func testSuccessfulTrace() { 17 | let actual = TraceArrayConsumer() 18 | 19 | let expected = [ 20 | "FlattenParser", 21 | " SequenceParser", 22 | " CharacterParser[letter expected]", 23 | " Success[1:2]: a", 24 | " PossessiveRepeatingParser[0..*]", 25 | " CharacterParser[letter or digit expected]", 26 | " Failure[1:2]: letter or digit expected", 27 | " Success[1:2]: []", 28 | " Success[1:2]: [\"a\", []]", 29 | "Success[1:2]: a" 30 | ] 31 | let result = Tracer.on(TracerTest.IDENTIFIER, consumer: actual).parse("a") 32 | let actualMapped = actual.events.map { "\($0)" } 33 | 34 | XCTAssertEqual(expected.count, actualMapped.count) 35 | for i in 0.. Mirror { 20 | return Mirror(parser) 21 | } 22 | 23 | public func iterate() -> [Parser] { 24 | var todo: [Parser] = [] 25 | todo.append(parser) 26 | 27 | var seen: Set = Set() 28 | seen.insert(parser) 29 | 30 | var result: [Parser] = [] 31 | while !todo.isEmpty { 32 | let current = todo.remove(at: todo.endIndex - 1) 33 | result.append(current) 34 | for parser in current.getChildren() { 35 | if !seen.contains(parser) { 36 | todo.append(parser) 37 | seen.insert(parser) 38 | } 39 | } 40 | } 41 | 42 | return result 43 | } 44 | 45 | public func transform(_ transformer: (Parser) -> Parser) -> Parser { 46 | var mapping: [Parser: Parser] = [:] 47 | 48 | for parser in iterate() { 49 | mapping[parser] = transformer(parser.copy()) 50 | } 51 | 52 | var seen: Set = Set(Array(mapping.values)) 53 | var todo = Array(mapping.values) 54 | 55 | while !todo.isEmpty { 56 | let parent = todo.remove(at: todo.endIndex - 1) 57 | for child in parent.getChildren() { 58 | if mapping[child] != nil { 59 | parent.replace(child, mapping[child]!) 60 | } 61 | else if !seen.contains(child) { 62 | seen.insert(child) 63 | todo.append(child) 64 | } 65 | } 66 | } 67 | 68 | return mapping[parser]! 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/repeating/PossessiveRepeatingParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PossessiveRepeatingParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class PossessiveRepeatingParser: RepeatingParser { 12 | public override func parseOn(_ context: Context) -> Result { 13 | var current = context 14 | var elements: [Any] = [] 15 | 16 | while elements.count < min { 17 | let result = delegate.parseOn(current) 18 | if result.isFailure() { 19 | return result 20 | } 21 | elements.append(result.get()!) 22 | current = result 23 | } 24 | 25 | while max == RepeatingParser.UNBOUNDED || elements.count < max { 26 | let result = delegate.parseOn(current) 27 | if result.isFailure() { 28 | return current.success(elements) 29 | } 30 | elements.append(result.get()!) 31 | current = result 32 | } 33 | 34 | return current.success(elements) 35 | } 36 | 37 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 38 | var count = 0 39 | var current = position 40 | 41 | while count < min { 42 | let result = delegate.fastParseOn(buffer, current) 43 | if result == nil { 44 | return result 45 | } 46 | current = result! 47 | count+=1 48 | } 49 | 50 | while max == RepeatingParser.UNBOUNDED || count < max { 51 | let result = delegate.fastParseOn(buffer, current) 52 | if result == nil { 53 | return current 54 | } 55 | current = result! 56 | count+=1 57 | } 58 | 59 | return current 60 | } 61 | 62 | public override func copy() -> Parser { 63 | return PossessiveRepeatingParser(delegate, min, max) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/xcshareddata/xcbaselines/22872F6523AA077300713E7E.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 1E6BBEE8-D55F-4BF0-B267-C4BBD1E161C2 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 400 13 | cpuCount 14 | 1 15 | cpuKind 16 | 6-Core Intel Core i7 17 | cpuSpeedInMHz 18 | 3200 19 | logicalCPUCoresPerPackage 20 | 12 21 | modelCode 22 | Macmini8,1 23 | physicalCPUCoresPerPackage 24 | 6 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone12,1 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | A5055CAB-26C4-4888-9CAB-744059C29025 39 | 40 | localComputer 41 | 42 | busSpeedInMHz 43 | 400 44 | cpuCount 45 | 1 46 | cpuKind 47 | 6-Core Intel Core i7 48 | cpuSpeedInMHz 49 | 3200 50 | logicalCPUCoresPerPackage 51 | 12 52 | modelCode 53 | Macmini8,1 54 | physicalCPUCoresPerPackage 55 | 6 56 | platformIdentifier 57 | com.apple.platform.macosx 58 | 59 | targetArchitecture 60 | x86_64 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/Tests/NumbersParserTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NumbersTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-24. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class NumbersTest: XCTestCase { 14 | func testParseInt() { 15 | XCTAssertEqual(NumbersParser.int().parse("0").get()!, 0) 16 | XCTAssertEqual(NumbersParser.int().parse("1").get()!, 1) 17 | XCTAssertEqual(NumbersParser.int().parse("10").get()!, 10) 18 | XCTAssertEqual(NumbersParser.int().parse("10000").get()!, 10000) 19 | } 20 | 21 | func testParserIntInRange() { 22 | let parser = NumbersParser.int(from: 10, to: 20).end() 23 | 24 | XCTAssertEqual(parser.parse("10").get()!, 10) 25 | XCTAssertEqual(parser.parse("15").get()!, 15) 26 | XCTAssertEqual(parser.parse("20").get()!, 20) 27 | 28 | XCTAssertTrue(parser.parse("21").isFailure()) 29 | XCTAssertEqual(parser.parse("21").message!, "Expected value in range 10..20") 30 | XCTAssertTrue(parser.parse("9").isFailure()) 31 | } 32 | 33 | func testParseDouble() { 34 | XCTAssertEqual(NumbersParser.double().parse("0").get()!, Double(0)) 35 | XCTAssertEqual(NumbersParser.double().parse("0.1").get()!, Double(0.1)) 36 | XCTAssertEqual(NumbersParser.double().parse("1").get()!, Double(1)) 37 | XCTAssertEqual(NumbersParser.double().parse("10").get()!, Double(10)) 38 | XCTAssertEqual(NumbersParser.double().parse("10000").get()!, Double(10000)) 39 | XCTAssertEqual(NumbersParser.double().parse("3.14159265").get()!, Double(3.14159265)) 40 | } 41 | 42 | func testParserDoubleInRange() { 43 | let parser = NumbersParser.double(from: 1.1, to: 1.2).end() 44 | 45 | XCTAssertEqual(parser.parse("1.1").get()!, 1.1) 46 | XCTAssertEqual(parser.parse("1.12").get()!, 1.12) 47 | XCTAssertEqual(parser.parse("1.2").get()!, 1.2) 48 | 49 | XCTAssertTrue(parser.parse("1").isFailure()) 50 | XCTAssertEqual(parser.parse("1").message!, "Expected value in range 1.1..1.2") 51 | XCTAssertTrue(parser.parse("1.21").isFailure()) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/CharacterRange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterRange.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class CharacterRange { 12 | let start: Character 13 | let stop: Character 14 | 15 | init(_ start: Character, _ stop: Character) { 16 | self.start = start 17 | self.stop = stop 18 | } 19 | 20 | class func toCharacterPredicate(_ ranges: [CharacterRange]) -> CharacterPredicate { 21 | // 1. sort the ranges 22 | var sortedRanges = ranges 23 | sortedRanges.sort { return $0.start < $1.start || $0.stop < $1.stop } 24 | 25 | // 2. merge adjacent or overlapping ranges 26 | var mergedRanges: [CharacterRange] = [] 27 | 28 | for thisRange in sortedRanges { 29 | if mergedRanges.isEmpty { 30 | mergedRanges.append(thisRange) 31 | } 32 | else { 33 | let lastRange = mergedRanges.last! 34 | if lastRange.stop.asciiValue! + 1 >= thisRange.start.asciiValue! { 35 | mergedRanges[mergedRanges.count - 1] = CharacterRange(lastRange.start, thisRange.stop) 36 | } 37 | else { 38 | mergedRanges.append(thisRange) 39 | } 40 | } 41 | 42 | } 43 | 44 | // 3. build the best resulting predicates 45 | if mergedRanges.isEmpty { 46 | return CharacterPredicates.none() 47 | } 48 | else if mergedRanges.count == 1 { 49 | let characterRange = mergedRanges[0] 50 | 51 | return characterRange.start == characterRange.stop ? 52 | CharacterPredicates.of(characterRange.start) : 53 | CharacterPredicates.range(characterRange.start, characterRange.stop) 54 | } 55 | else { 56 | var starts: [Character] = [] 57 | var stops: [Character] = [] 58 | for range in mergedRanges { 59 | starts.append(range.start) 60 | stops.append(range.stop) 61 | } 62 | return CharacterPredicates.ranges(starts, stops) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Tests/PrimitiveTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrimitiveTests.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import XCTest 12 | @testable import swift_petitparser 13 | 14 | class PrimitiveTests: XCTestCase { 15 | func testEpsilon() { 16 | let parser = EpsilonParser() 17 | Assert.assertSuccess(parser, "", NSNull()) 18 | Assert.assertSuccess(parser, "a", NSNull(), "a".startIndex) 19 | } 20 | 21 | func testFailure() { 22 | let parser = FailureParser.withMessage("wrong") 23 | Assert.assertFailure(parser, "", "wrong") 24 | Assert.assertFailure(parser, "a", "wrong") 25 | } 26 | 27 | func testString() { 28 | let parser = StringParser.of("foo").end() 29 | Assert.assertSuccess(parser, "foo", "foo") 30 | Assert.assertFailure(parser, "", "foo expected") 31 | Assert.assertFailure(parser, "f", "foo expected") 32 | Assert.assertFailure(parser, "fo", "foo expected") 33 | Assert.assertFailure(parser, "Foo", "foo expected") 34 | Assert.assertFailure(parser, "foobar", 3, "end of input expected") 35 | } 36 | 37 | func testStringWithMessage() { 38 | let parser = StringParser.of("foo", "wrong").end() 39 | Assert.assertSuccess(parser, "foo", "foo") 40 | Assert.assertFailure(parser, "", "wrong") 41 | Assert.assertFailure(parser, "f", "wrong") 42 | Assert.assertFailure(parser, "fo", "wrong") 43 | Assert.assertFailure(parser, "Foo", "wrong") 44 | } 45 | 46 | func testStringIgnoreCase() { 47 | let parser = StringParser.ofIgnoringCase("foo").end() 48 | Assert.assertSuccess(parser, "foo", "foo") 49 | Assert.assertSuccess(parser, "FOO", "FOO") 50 | Assert.assertSuccess(parser, "fOo", "fOo") 51 | Assert.assertFailure(parser, "", "foo expected") 52 | Assert.assertFailure(parser, "f", "foo expected") 53 | Assert.assertFailure(parser, "Fo", "foo expected") 54 | } 55 | 56 | func testStringIgnoreCaseWithMessage() { 57 | let parser = StringParser.ofIgnoringCase("foo", "wrong").end() 58 | Assert.assertSuccess(parser, "foo", "foo") 59 | Assert.assertSuccess(parser, "FOO", "FOO") 60 | Assert.assertSuccess(parser, "fOo", "fOo") 61 | Assert.assertFailure(parser, "", "wrong") 62 | Assert.assertFailure(parser, "f", "wrong") 63 | Assert.assertFailure(parser, "Fo", "wrong") 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/swift-petitparser/context/Token.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Token { 12 | let buffer: String 13 | let start: String.Index 14 | let stop: String.Index 15 | let value: AnyObject 16 | 17 | static let NEWLINE_PARSER: Parser = CharacterParser.of("\n").or(CharacterParser.of("\r").seq(CharacterParser.of("\n").optional())) 18 | 19 | public init(_ buffer: String, _ start: String.Index, _ stop: String.Index, _ value: AnyObject) { 20 | self.buffer = buffer 21 | self.start = start 22 | self.stop = stop 23 | self.value = value 24 | } 25 | 26 | public func getInput() -> String { 27 | return String(buffer[start.. String.IndexDistance { 31 | return buffer.distance(from: start, to: stop) 32 | } 33 | 34 | /** 35 | * The line number of the token. 36 | */ 37 | func getLine() -> Int { 38 | return Token.lineAndColumnOf(buffer, start).0 39 | } 40 | 41 | /** 42 | * The column number of this token. 43 | */ 44 | func getColumn() -> Int { 45 | return Token.lineAndColumnOf(buffer, start).1 46 | } 47 | 48 | public class func lineAndColumnOf(_ buffer: String, _ position: String.Index) -> (Int, Int) { 49 | let pos = position.utf16Offset(in: buffer) 50 | let tokens: [Token] = NEWLINE_PARSER.token().matchesSkipping(buffer) 51 | var line = 1 52 | var offset = 0 53 | 54 | for token in tokens { 55 | if pos < token.stop.utf16Offset(in: buffer) { 56 | return (line, pos - offset + 1) 57 | } 58 | 59 | line += 1 60 | offset = token.stop.utf16Offset(in: buffer) 61 | } 62 | 63 | return (line, pos - offset + 1) 64 | } 65 | } 66 | 67 | extension Token: Equatable { 68 | public static func == (lhs: Token, rhs: Token) -> Bool { 69 | return lhs.start == rhs.start && 70 | lhs.stop == rhs.stop && 71 | lhs.buffer == rhs.buffer && 72 | ObjectIdentifier(lhs.value) == ObjectIdentifier(rhs.value) 73 | } 74 | } 75 | 76 | extension Token: Hashable { 77 | public func hash(into hasher: inout Hasher) { 78 | hasher.combine(start) 79 | hasher.combine(stop) 80 | hasher.combine(buffer) 81 | hasher.combine(ObjectIdentifier(value)) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/repeating/LazyRepeatingParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LazyRepeatingParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class LazyRepeatingParser: LimitedRepeatingParser { 12 | override func parseOn(_ context: Context) -> Result { 13 | var current = context 14 | var elements: [Any] = [] 15 | 16 | while elements.count < min { 17 | let result = delegate.parseOn(current) 18 | if result.isFailure() { 19 | return result 20 | } 21 | elements.append(result.get()!) 22 | current = result 23 | } 24 | 25 | while true { 26 | let limiter = limit.parseOn(current) 27 | if limiter.isSuccess() { 28 | return current.success(elements) 29 | } 30 | else { 31 | if max != RepeatingParser.UNBOUNDED && elements.count >= max { 32 | return limiter 33 | } 34 | 35 | let result = delegate.parseOn(current) 36 | if result.isFailure() { 37 | return limiter 38 | } 39 | 40 | elements.append(result.get()!) 41 | current = result 42 | } 43 | } 44 | } 45 | 46 | override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 47 | var count = 0 48 | var current = position 49 | 50 | while count < min { 51 | let result = delegate.fastParseOn(buffer, current) 52 | if result == nil { 53 | return nil 54 | } 55 | current = result! 56 | count += 1 57 | } 58 | 59 | while true { 60 | let limiter = limit.fastParseOn(buffer, current) 61 | if limiter != nil { 62 | return current 63 | } 64 | else { 65 | if max != RepeatingParser.UNBOUNDED && count >= max { 66 | return nil 67 | } 68 | 69 | let result = delegate.fastParseOn(buffer, current) 70 | if result == nil { 71 | return nil 72 | } 73 | current = result! 74 | count += 1 75 | } 76 | } 77 | } 78 | 79 | override func copy() -> Parser { 80 | return LazyRepeatingParser(delegate, limit, min, max) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/actions/TrimmingParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrimmingParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class TrimmingParser: DelegateParser { 12 | var left: Parser 13 | var right: Parser 14 | 15 | public init(_ delegate: Parser, _ left: Parser, _ right: Parser) { 16 | self.left = left 17 | self.right = right 18 | super.init(delegate) 19 | } 20 | 21 | public override func parseOn(_ context: Context) -> Result { 22 | var current = context 23 | let buffer = current.buffer 24 | 25 | // Trim the left part: 26 | let before = consume(left, buffer, current.position) 27 | if before != context.position { 28 | current = Context(buffer, before) 29 | } 30 | 31 | // Consume the delegate: 32 | let result = delegate.parseOn(current) 33 | if result.isFailure() { 34 | return result 35 | } 36 | 37 | // Trim the right part: 38 | let after = consume(right, buffer, result.position) 39 | return after == result.position ? result : 40 | result.success(result.get()!, after) 41 | } 42 | 43 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 44 | let result = delegate.fastParseOn(buffer, consume(left, buffer, position)) 45 | return result == nil ? result : consume(right, buffer, result!) 46 | } 47 | 48 | func consume(_ parser: Parser, _ buffer: String, _ position: String.Index) -> String.Index { 49 | var current = position 50 | while true { 51 | let result = parser.fastParseOn(buffer, current) 52 | if result == nil { 53 | return current 54 | } 55 | current = result! 56 | } 57 | } 58 | 59 | public override func replace(_ source: Parser, _ target: Parser) { 60 | super.replace(source, target) 61 | 62 | if left === source { 63 | left = target 64 | } 65 | 66 | if right === source { 67 | right = target 68 | } 69 | } 70 | 71 | public override func getChildren() -> [Parser] { 72 | return [delegate, left, right] 73 | } 74 | 75 | public override func copy() -> Parser { 76 | return TrimmingParser(delegate, left, right) 77 | } 78 | } 79 | 80 | extension Parser { 81 | public func trim() -> Parser { 82 | return trim(CharacterParser.whitespace()) 83 | } 84 | 85 | public func trim(_ both: Parser) -> Parser { 86 | return trim(both, both) 87 | } 88 | 89 | public func trim(_ before: Parser, _ after: Parser) -> Parser { 90 | return TrimmingParser(self, before, after) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/repeating/RepeatingParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepeatingParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum InvalidArgumentError: Error { 12 | case runtimeError(String) 13 | } 14 | 15 | public class RepeatingParser: DelegateParser { 16 | public static let UNBOUNDED = -1 17 | 18 | let min: Int 19 | let max: Int 20 | 21 | public init(_ delegate: Parser, _ min: Int, _ max: Int) { 22 | self.min = min 23 | self.max = max 24 | 25 | super.init(delegate) 26 | 27 | // if (min < 0) { 28 | // throw InvalidArgumentError.runtimeError("Invalid min repetitions: \(getRange())") 29 | // } 30 | // if (max != RepeatingParser.UNBOUNDED && min > max) { 31 | // throw InvalidArgumentError.runtimeError("Invalid max repetitions: \(getRange())") 32 | // } 33 | } 34 | 35 | func getRange() -> String { 36 | let end = max == RepeatingParser.UNBOUNDED ? "*" : "\(max)" 37 | return "\(min)..\(end)" 38 | } 39 | 40 | public override func hasEqualProperties(_ other: Parser) -> Bool { 41 | if let oth = other as? RepeatingParser { 42 | return super.hasEqualProperties(other) 43 | && oth.min == min 44 | && oth.max == max 45 | } 46 | 47 | return false 48 | } 49 | } 50 | 51 | postfix operator + 52 | extension Parser { 53 | 54 | } 55 | 56 | extension Parser { 57 | public func star() -> Parser { 58 | return repeated(0, RepeatingParser.UNBOUNDED) 59 | } 60 | 61 | public func starGreedy(_ limit: Parser) -> Parser { 62 | return repeatGreedy(limit, 0, RepeatingParser.UNBOUNDED) 63 | } 64 | 65 | public func starLazy(_ limit: Parser) -> Parser { 66 | return repeatLazy(limit, 0, RepeatingParser.UNBOUNDED) 67 | } 68 | 69 | public func plus() -> Parser { 70 | return repeated(1, RepeatingParser.UNBOUNDED) 71 | } 72 | 73 | public func plusGreedy(_ limit: Parser) -> Parser { 74 | return repeatGreedy(limit, 1, RepeatingParser.UNBOUNDED) 75 | } 76 | 77 | public func plusLazy(_ limit: Parser) -> Parser { 78 | return repeatLazy(limit, 1, RepeatingParser.UNBOUNDED) 79 | } 80 | 81 | public func repeated(_ min: Int, _ max: Int) -> Parser { 82 | return PossessiveRepeatingParser(self, min, max) 83 | } 84 | 85 | public func repeatGreedy(_ limit: Parser, _ min: Int, _ max: Int) -> Parser { 86 | return GreedyRepeatingParser(self, limit, min, max) 87 | } 88 | 89 | public func repeatLazy(_ limit: Parser, _ min: Int, _ max: Int) -> Parser { 90 | return LazyRepeatingParser(self, limit, min, max) 91 | } 92 | 93 | public func times(_ count: Int) -> Parser { 94 | return repeated(count, count) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/StringParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class StringParser: Parser { 12 | let size: String.IndexDistance 13 | let message: String 14 | let matcher: StringPredicate 15 | 16 | init(_ size: String.IndexDistance, _ matcher: StringPredicate, _ message: String) { 17 | self.size = size 18 | self.matcher = matcher 19 | self.message = message 20 | 21 | super.init() 22 | } 23 | 24 | public class func of(_ value: String) -> Parser { 25 | return of(value, "\(value) expected") 26 | } 27 | 28 | public class func of(_ value: String, _ message: String) -> Parser { 29 | let matcher = StringPredicate(matcher: { s in s.compare(value) == .orderedSame }) 30 | let distance = value.distance(from: value.startIndex, to: value.endIndex) 31 | return StringParser(distance, matcher, message) 32 | } 33 | 34 | public class func ofIgnoringCase(_ value: String) -> Parser { 35 | return ofIgnoringCase(value, "\(value) expected") 36 | } 37 | 38 | public class func ofIgnoringCase(_ value: String, _ message: String) -> Parser { 39 | let matcher = StringPredicate(matcher: { s in s.caseInsensitiveCompare(value) == .orderedSame }) 40 | let distance = value.distance(from: value.startIndex, to: value.endIndex) 41 | return StringParser(distance, matcher, message) 42 | } 43 | 44 | public override func parseOn(_ context: Context) -> Result { 45 | let distance = context.buffer.distance(from: context.position, to: context.buffer.endIndex) 46 | if distance >= size { 47 | let end = context.buffer.index(context.position, offsetBy: size) 48 | let result = String(context.buffer[context.position.. String.Index? { 57 | let distance = buffer.distance(from: position, to: buffer.endIndex) 58 | if distance >= size { 59 | let end = buffer.index(position, offsetBy: size) 60 | let result = String(buffer[position.. Bool { 69 | if let oth = other as? StringParser { 70 | return super.hasEqualProperties(other) 71 | && oth.size == size 72 | && oth.message == message 73 | && ObjectIdentifier(oth.matcher) == ObjectIdentifier(matcher) 74 | } 75 | return false 76 | } 77 | 78 | public override func copy() -> Parser { 79 | return StringParser(size, matcher, message) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/xcshareddata/xcschemes/swift-petitparser.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/Tests/GrammarDefinitionTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GrammarDefinitionTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-22. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class ListGrammarDefinition: GrammarDefinition { 14 | override init() { 15 | super.init() 16 | 17 | def("start", ref("list").end()) 18 | def("list", ref("element").seq(CP.of(",").flatten()).seq(ref("list")).or(ref("element"))) 19 | def("element", CP.digit().plus().flatten()) 20 | } 21 | } 22 | 23 | class ListParserDefinition: ListGrammarDefinition { 24 | override init() { 25 | super.init() 26 | 27 | let function: (String) -> Int = { Int($0)! } 28 | action("element", function) 29 | } 30 | } 31 | 32 | class ExpressionGrammarDefinition: GrammarDefinition { 33 | override init() { 34 | super.init() 35 | 36 | def("start", ref("terms").end()) 37 | 38 | def("terms", ref("addition").or(ref("factors"))) 39 | def("addition", ref("factors").separatedBy(CP.pattern("+-").flatten().trim())) 40 | 41 | def("factors", ref("multiplication").or(ref("power"))) 42 | def("multiplication", ref("power").separatedBy(CP.pattern("*/").flatten().trim())) 43 | 44 | def("power", ref("primary").separatedBy(CP.of("^").flatten().trim())) 45 | def("primary", ref("number").or(ref("parentheses"))) 46 | 47 | def("number", CP.of("-").flatten().trim().optional() 48 | .seq(CP.digit().plus()) 49 | .seq(CP.of(".") 50 | .seq(CP.digit().plus()) 51 | .optional())) 52 | 53 | def("parentheses", CP.of("(").flatten().trim() 54 | .seq(ref("terms")) 55 | .seq(CP.of(")").flatten().trim())) 56 | } 57 | } 58 | 59 | class GrammarDefinitionTest: XCTestCase { 60 | 61 | func testGrammar() { 62 | let parser = ListGrammarDefinition().build() 63 | Assert.arrayEquals(["1", ",", "2"], parser.parse("1,2").get()!) 64 | } 65 | 66 | func testParser() { 67 | let parser = GrammarParser(ListGrammarDefinition(), "start") 68 | Assert.arrayEquals(["1", ",", "2"], parser.parse("1,2").get()!) 69 | } 70 | 71 | func testExpressionGrammar() { 72 | let parser = GrammarParser(ExpressionGrammarDefinition()) 73 | XCTAssertTrue(parser.accept("1")) 74 | XCTAssertTrue(parser.accept("12")) 75 | XCTAssertTrue(parser.accept("1.23")) 76 | XCTAssertTrue(parser.accept("-12.3")) 77 | XCTAssertTrue(parser.accept("1 + 2")) 78 | XCTAssertTrue(parser.accept("1 + 2 + 3")) 79 | XCTAssertTrue(parser.accept("1 - 2")) 80 | XCTAssertTrue(parser.accept("1 - 2 - 3")) 81 | XCTAssertTrue(parser.accept("1 * 2")) 82 | XCTAssertTrue(parser.accept("1 * 2 * 3")) 83 | XCTAssertTrue(parser.accept("1 / 2")) 84 | XCTAssertTrue(parser.accept("1 / 2 / 3")) 85 | XCTAssertTrue(parser.accept("1 ^ 2")) 86 | XCTAssertTrue(parser.accept("1 ^ 2 ^ 3")) 87 | XCTAssertTrue(parser.accept("1 + (2 * 3)")) 88 | XCTAssertTrue(parser.accept("(1 + 2) * 3")) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/repeating/GreedyRepeatingParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GreedyRepeatingParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GreedyRepeatingParser: LimitedRepeatingParser { 12 | override func parseOn(_ context: Context) -> Result { 13 | var current = context 14 | var elements: [Any] = [] 15 | 16 | while elements.count < min { 17 | let result = delegate.parseOn(current) 18 | if result.isFailure() { 19 | return result 20 | } 21 | elements.append(result.get()!) 22 | current = result 23 | } 24 | 25 | var contexts: [Context] = [] 26 | contexts.append(current) 27 | while max == RepeatingParser.UNBOUNDED || elements.count < max { 28 | let result = delegate.parseOn(current) 29 | if result.isFailure() { 30 | break 31 | } 32 | elements.append(result.get()!) 33 | current = result 34 | contexts.append(current) 35 | } 36 | 37 | while true { 38 | let limiter = limit.parseOn(contexts[contexts.count - 1]) 39 | if limiter.isSuccess() { 40 | return contexts[contexts.count - 1].success(elements) 41 | } 42 | if elements.isEmpty { 43 | return limiter 44 | } 45 | contexts.remove(at: contexts.count - 1) 46 | elements.remove(at: elements.count - 1) 47 | 48 | if contexts.isEmpty { 49 | return limiter 50 | } 51 | } 52 | } 53 | 54 | override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 55 | var count = 0 56 | var current = position 57 | 58 | while count < min { 59 | let result = delegate.fastParseOn(buffer, current) 60 | if result == nil { 61 | return nil 62 | } 63 | current = result! 64 | count += 1 65 | } 66 | 67 | var positions: [String.Index] = [] 68 | positions.append(current) 69 | while max == RepeatingParser.UNBOUNDED || count < max { 70 | let result = delegate.fastParseOn(buffer, current) 71 | if result == nil { 72 | break 73 | } 74 | current = result! 75 | positions.append(current) 76 | count+=1 77 | } 78 | 79 | while true { 80 | let limiter = limit.fastParseOn(buffer, positions[positions.count - 1]) 81 | if limiter != nil { 82 | return positions[positions.count - 1] 83 | } 84 | // swiftlint false positive 85 | // swiftlint:disable empty_count 86 | if count == 0 { 87 | return nil 88 | } 89 | positions.remove(at: positions.count - 1) 90 | count -= 1 91 | if positions.isEmpty { 92 | return nil 93 | } 94 | } 95 | } 96 | 97 | override func copy() -> Parser { 98 | return GreedyRepeatingParser(delegate, limit, min, max) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Tests/Assert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Assert.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | // swiftlint:disable type_name 14 | typealias CP = CharacterParser 15 | typealias SP = StringParser 16 | 17 | public class Assert { 18 | public static let NULL: String? = nil 19 | public static let EMPTY: [Character] = [] 20 | 21 | public class func assertSuccess(_ parser: Parser, _ input: String, _ expected: T?) { 22 | assertSuccess(parser, input, expected, input.endIndex) 23 | } 24 | 25 | public class func assertSuccess(_ parser: Parser, _ input: String, _ expected: T?, _ position: Int) { 26 | let positionIdx = input.index(input.startIndex, offsetBy: position) 27 | assertSuccess(parser, input, expected, positionIdx) 28 | } 29 | 30 | public class func assertSuccess(_ parser: Parser, _ input: String, _ expected: T?, _ position: String.Index) { 31 | let result = parser.parse(input) 32 | XCTAssertTrue(result.isSuccess(), "Expected parse success") 33 | XCTAssertFalse(result.isFailure(), "Expected parse success") 34 | 35 | if expected == nil { 36 | XCTAssertTrue(result.get() == nil, "Expected result to be nil") 37 | } 38 | else { 39 | let resultValue: T = result.get()! 40 | XCTAssertEqual(resultValue, expected, "Result") 41 | } 42 | 43 | XCTAssertEqual(result.position, position, "Position") 44 | 45 | let parsePosition = parser.fastParseOn(input, input.startIndex) 46 | XCTAssertEqual(parsePosition!, position, "Fast parse") 47 | XCTAssertTrue(parser.accept(input), "Accept") 48 | } 49 | 50 | public class func assertFailure(_ parser: Parser, _ input: String) { 51 | assertFailure(parser, input, 0, nil) 52 | } 53 | 54 | public class func assertFailure(_ parser: Parser, _ input: String, _ position: Int) { 55 | assertFailure(parser, input, position, nil) 56 | } 57 | 58 | public class func assertFailure(_ parser: Parser, _ input: String, _ message: String?) { 59 | assertFailure(parser, input, 0, message) 60 | } 61 | 62 | public class func assertFailure(_ parser: Parser, _ input: String, _ position: Int, _ message: String?) { 63 | let positionIdx = input.index(input.startIndex, offsetBy: position) 64 | assertFailure(parser, input, positionIdx, message) 65 | } 66 | 67 | public class func assertFailure(_ parser: Parser, _ input: String, _ position: String.Index, _ message: String?) { 68 | let result = parser.parse(input) 69 | // XCTAssertTrue(result.message != nil) 70 | XCTAssertFalse(result.isSuccess()) 71 | XCTAssertTrue(result.isFailure()) 72 | 73 | if message != nil { 74 | XCTAssertEqual(result.message!, message) 75 | } 76 | XCTAssertEqual(result.position, position) 77 | } 78 | 79 | public class func assertSuccess(_ parser: Parser, _ text: String, _ array: [Character]) { 80 | let result = parser.parse(text) 81 | XCTAssertEqual(result.isSuccess(), true) 82 | let resultValues: [Character] = parser.parse(text).get()! 83 | XCTAssertTrue(array.elementsEqual(resultValues)) 84 | } 85 | 86 | public class func arrayEquals(_ a: [String], _ b: [String]) { 87 | XCTAssertEqual(a.elementsEqual(b), true) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tests.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import swift_petitparser 11 | 12 | class Tests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testPerformanceExample() { 23 | // This is an example of a performance test case. 24 | measure { 25 | // Put the code you want to measure the time of here. 26 | } 27 | } 28 | 29 | func testFailure() { 30 | let buffer = "test" 31 | let failure = Failure(buffer, buffer.startIndex, "some error") 32 | XCTAssertEqual(buffer, failure.buffer) 33 | XCTAssertTrue(failure.get() == nil) 34 | } 35 | 36 | func testDelimitedBy() { 37 | let parser = CP.of("a").delimitedBy(CP.of("b")) 38 | 39 | assertSuccess(parser, "a", ["a"]) 40 | assertSuccess(parser, "ab", ["a", "b"]) 41 | assertSuccess(parser, "aba", ["a", "b", "a"]) 42 | assertSuccess(parser, "abab", ["a", "b", "a", "b"]) 43 | assertSuccess(parser, "ababa", ["a", "b", "a", "b", "a"]) 44 | assertSuccess(parser, "ababab", ["a", "b", "a", "b", "a", "b"]) 45 | } 46 | 47 | func assertSuccess(_ parser: Parser, _ text: String, _ array: [Character]) { 48 | let result = parser.parse(text) 49 | XCTAssertEqual(result.isSuccess(), true) 50 | let resultValues: [Character] = parser.parse(text).get()! 51 | XCTAssertEqual(array.elementsEqual(resultValues), true) 52 | } 53 | 54 | func testFlatten() { 55 | let parser = CP.digit().plus().flatten().end() 56 | XCTAssertEqual("12", parser.parse("12").get()!) 57 | 58 | XCTAssertEqual(parser.parse("1").isSuccess(), true) 59 | XCTAssertEqual(parser.parse("12").isSuccess(), true) 60 | XCTAssertEqual(parser.parse("123").isSuccess(), true) 61 | } 62 | 63 | func testTrim() { 64 | let parser = CP.digit().plus().flatten().trim().end() 65 | let result: String? = parser.parse(" 12 ").get() 66 | XCTAssertEqual("12", result!) 67 | 68 | XCTAssertEqual(parser.parse("1").isSuccess(), true) 69 | XCTAssertEqual(parser.parse("12").isSuccess(), true) 70 | XCTAssertEqual(parser.parse("123").isSuccess(), true) 71 | } 72 | 73 | func testParseNumber() { 74 | let parser = CP.digit().seq(CP.digit().optional()).end() 75 | XCTAssertEqual(parser.parse("1").isSuccess(), true) 76 | XCTAssertEqual(parser.parse("12").isSuccess(), true) 77 | XCTAssertEqual(parser.parse("123").isSuccess(), false) 78 | } 79 | 80 | func testParseNumberUnbounded() { 81 | let parser = CP.digit().star().end() 82 | XCTAssertEqual(parser.parse("1").isSuccess(), true) 83 | XCTAssertEqual(parser.parse("12").isSuccess(), true) 84 | XCTAssertEqual(parser.parse("123").isSuccess(), true) 85 | XCTAssertEqual(parser.parse("123a").isSuccess(), false) 86 | } 87 | 88 | func testParseNumber_Failing() { 89 | let parser = CP.digit() 90 | .seq(CP.digit().optional()) 91 | .end() 92 | let result = parser.parse("a") 93 | XCTAssertEqual(result.isSuccess(), false) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/swift-petitparser/tools/Tracer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tracer.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-23. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol TraceConsumer { 12 | func accept(_ event: TraceEvent) 13 | } 14 | 15 | public protocol ParentClosure { 16 | func setParentClosure(_ event: TraceEvent?) 17 | func getParentClosure() -> TraceEvent? 18 | } 19 | 20 | public class Tracer: ParentClosure { 21 | 22 | let consumer: TraceConsumer 23 | var parentClosure: TraceEvent? 24 | 25 | init(_ consumer: TraceConsumer) { 26 | self.consumer = consumer 27 | } 28 | 29 | public func setParentClosure(_ event: TraceEvent?) { 30 | parentClosure = event 31 | } 32 | 33 | public func getParentClosure() -> TraceEvent? { 34 | return parentClosure 35 | } 36 | 37 | public class func on(_ source: Parser, consumer: TraceConsumer) -> Parser { 38 | let tracer = Tracer(consumer) 39 | return Mirror.of(source) 40 | .transform(tracer.transform) 41 | } 42 | 43 | func transform(parser: Parser) -> Parser { 44 | return parser.callCC(TracerContinuationHandler(parser, consumer, self)) 45 | } 46 | } 47 | 48 | class TracerContinuationHandler: ContinuationHandler { 49 | let parser: Parser 50 | let consumer: TraceConsumer 51 | let parentClosure: ParentClosure 52 | 53 | init(_ parser: Parser, _ consumer: TraceConsumer, _ parentClosure: ParentClosure) { 54 | self.parser = parser 55 | self.consumer = consumer 56 | self.parentClosure = parentClosure 57 | } 58 | 59 | public func apply(_ continuation: @escaping (Context) -> Result, _ context: Context) -> Result { 60 | let parent = self.parentClosure.getParentClosure() 61 | let enter = TraceEvent(TraceEventType.enter, parent, parser, context) 62 | consumer.accept(enter) 63 | parentClosure.setParentClosure(enter) 64 | 65 | let result = continuation(context) 66 | parentClosure.setParentClosure(parent) 67 | consumer.accept(TraceEvent(TraceEventType.exit, parent, parser, result)) 68 | return result 69 | } 70 | } 71 | 72 | public class TraceArrayConsumer: TraceConsumer { 73 | var events: [TraceEvent] = [] 74 | 75 | public func accept(_ event: TraceEvent) { 76 | events.append(event) 77 | } 78 | } 79 | 80 | /** 81 | * The trace event type differentiating between activation and return. 82 | */ 83 | public enum TraceEventType { 84 | case enter 85 | case exit 86 | } 87 | 88 | /** 89 | * The trace event holding all relevant data. 90 | */ 91 | public class TraceEvent { 92 | 93 | let type: TraceEventType 94 | let parent: TraceEvent? 95 | let parser: Parser 96 | let context: Context 97 | 98 | init(_ type: TraceEventType, _ parent: TraceEvent?, _ parser: Parser, _ context: Context) { 99 | self.type = type 100 | self.parent = parent 101 | self.parser = parser 102 | self.context = context 103 | } 104 | 105 | func getLevel() -> Int { 106 | return parent.map { $0.getLevel() + 1 } ?? 0 107 | } 108 | } 109 | 110 | extension TraceEvent: CustomStringConvertible { 111 | public var description: String { 112 | let indent = String(repeating: " ", count: getLevel()) 113 | let type = TraceEventType.enter == self.type ? "\(parser)" : "\(context)" 114 | return "\(indent)\(type)" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/xcuserdata/philipparndt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 41 | 53 | 54 | 55 | 57 | 69 | 70 | 71 | 73 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/swift-petitparser.xcodeproj/project.xcworkspace/xcuserdata/philipparndt.xcuserdatad/xcdebugger/Expressions.xcexplist: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 10 | 11 | 13 | 14 | 16 | 17 | 19 | 20 | 22 | 23 | 25 | 26 | 27 | 28 | 30 | 31 | 33 | 34 | 36 | 37 | 39 | 40 | 42 | 43 | 45 | 46 | 48 | 49 | 51 | 52 | 53 | 54 | 56 | 57 | 59 | 60 | 62 | 63 | 64 | 65 | 67 | 68 | 70 | 71 | 73 | 74 | 75 | 76 | 78 | 79 | 81 | 82 | 84 | 85 | 86 | 87 | 89 | 90 | 92 | 93 | 95 | 96 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/Tests/TokenTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-23. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class TokenTest: XCTestCase { 14 | let parser = CP.any() 15 | .map { (any: Character) -> String in String(any) } 16 | .token().star() 17 | let buffer = "1\r12\r\n123\n1234" 18 | 19 | func testBuffer() { 20 | let result: [Token] = parser.parse(buffer).get()! 21 | let expected = [buffer, buffer, buffer, buffer, buffer, buffer, buffer, buffer, buffer, 22 | buffer, buffer, buffer, buffer] 23 | 24 | let actual = result.map { $0.buffer } 25 | XCTAssertTrue(expected.elementsEqual(actual)) 26 | } 27 | 28 | func testInput() { 29 | let result: [Token] = parser.parse(buffer).get()! 30 | let expected = ["1", "\r", "1", "2", "\r\n", "1", "2", "3", "\n", "1", "2", "3", 31 | "4"] 32 | 33 | let actual = result.map { $0.getInput() } 34 | XCTAssertTrue(expected.elementsEqual(actual)) 35 | } 36 | 37 | func testLength() { 38 | let result: [Token] = parser.parse(buffer).get()! 39 | let expected = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 40 | let actual = result.map { $0.getLength() } 41 | 42 | XCTAssertTrue(expected.elementsEqual(actual)) 43 | } 44 | 45 | func testStart() { 46 | let result: [Token] = parser.parse(buffer).get()! 47 | let expected = [ 0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13] 48 | let actual = result.map { $0.start.utf16Offset(in: buffer) } 49 | 50 | XCTAssertTrue(expected.elementsEqual(actual)) 51 | } 52 | 53 | func testStop() { 54 | let result: [Token] = parser.parse(buffer).get()! 55 | let expected = [ 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14] 56 | let actual = result.map { $0.stop.utf16Offset(in: buffer) } 57 | 58 | XCTAssertTrue(expected.elementsEqual(actual)) 59 | } 60 | 61 | func testValue() { 62 | let result: [Token] = parser.parse(buffer).get()! 63 | let expected = ["1", "\r", "1", "2", "\r\n", "1", "2", "3", "\n", "1", "2", "3", "4"] 64 | let actual: [String] = result.map { $0.value as! String } 65 | XCTAssertTrue(expected.elementsEqual(actual)) 66 | } 67 | 68 | func testLine() { 69 | let result: [Token] = parser.parse(buffer).get()! 70 | let expected = [1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3] 71 | 72 | let actual = result.map { $0.getLine() } 73 | XCTAssertTrue(expected.elementsEqual(actual)) 74 | } 75 | 76 | func testColumn() { 77 | let result: [Token] = parser.parse(buffer).get()! 78 | let expected = [1, 2, 1, 2, 3, 5, 6, 7, 8, 1, 2, 3, 4] 79 | 80 | let actual = result.map { $0.getColumn() } 81 | XCTAssertTrue(expected.elementsEqual(actual)) 82 | } 83 | 84 | func testHashCode() { 85 | let result: [Token] = parser.parse(buffer).get()! 86 | let uniques: Set = Set(result.map { $0.hashValue }) 87 | 88 | XCTAssertEqual(result.count, uniques.count) 89 | } 90 | 91 | func testEquals() { 92 | let result: [Token] = parser.parse(buffer).get()! 93 | 94 | for i in 0 ..< result.count { 95 | let first = result[i] 96 | for j in 0 ..< result.count { 97 | let second = result[j] 98 | 99 | if i == j { 100 | XCTAssertEqual(first, second) 101 | } 102 | else { 103 | XCTAssertNotEqual(first, second) 104 | } 105 | } 106 | 107 | XCTAssertEqual(first, Token(first.buffer, first.start, first.stop, first.value)) 108 | XCTAssertNotEqual(first, Token("", first.start, first.stop, first.value)) 109 | XCTAssertNotEqual(first, Token(first.buffer, first.buffer.index(after: first.start), first.stop, first.value)) 110 | XCTAssertNotEqual(first, Token(first.buffer, first.start, first.buffer.index(before: first.stop), first.value)) 111 | XCTAssertNotEqual(first, Token(first.buffer, first.start, first.stop, first)) 112 | } 113 | 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Tests/EqualityTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EqualityTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-23. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class EqualityTest: XCTestCase { 14 | func verify(_ parser: Parser) { 15 | let copy = parser.copy() 16 | XCTAssertNotEqual(ObjectIdentifier(copy), ObjectIdentifier(parser)) 17 | assertPairwiseSame(copy.getChildren(), parser.getChildren()) 18 | XCTAssertTrue(copy.hasEqualProperties(parser)) 19 | 20 | // check equality 21 | XCTAssertTrue(copy.isEqualTo(copy)) 22 | XCTAssertTrue(parser.isEqualTo(copy)) 23 | XCTAssertTrue(copy.isEqualTo(parser)) 24 | XCTAssertTrue(parser.isEqualTo(parser)) 25 | 26 | // check replacing 27 | var replaced: [Parser] = [] 28 | for i in 0 ..< copy.getChildren().count { 29 | let source = copy.getChildren()[i] 30 | let target = CharacterParser.any() 31 | copy.replace(source, target) 32 | XCTAssertEqual(target, copy.getChildren()[i]) 33 | replaced.append(target) 34 | } 35 | assertPairwiseSame(replaced, copy.getChildren()) 36 | } 37 | 38 | func assertPairwiseSame(_ expected: [Parser], _ actual: [Parser]) { 39 | XCTAssertEqual(expected.count, actual.count) 40 | for i in 0 ..< expected.count { 41 | XCTAssertEqual(expected[i], actual[i]) 42 | } 43 | } 44 | 45 | func testAny() { 46 | verify(CP.any()) 47 | } 48 | 49 | func testAnd() { 50 | verify(CP.digit().and()) 51 | } 52 | 53 | func testChar() { 54 | verify(CP.of("a")) 55 | } 56 | 57 | func testDigit() { 58 | verify(CP.digit()) 59 | } 60 | 61 | func testDelegate() { 62 | verify(DelegateParser(CP.any())) 63 | } 64 | // 65 | // func testContinuation() { 66 | // verify(CP.digit().callCC((continuation, context) -> null)) 67 | // } 68 | 69 | func testEnd() { 70 | verify(CP.digit().end()) 71 | } 72 | 73 | func testEpsilon() { 74 | verify(EpsilonParser()) 75 | } 76 | 77 | func testFailure() { 78 | verify(FailureParser.withMessage("failure")) 79 | } 80 | 81 | func testFlatten1() { 82 | verify(CP.digit().flatten()) 83 | } 84 | 85 | func testFlatten2() { 86 | verify(CP.digit().flatten("digit")) 87 | } 88 | 89 | func testMap() { 90 | verify(CP.digit().map { (any: Any) -> Any in any }) 91 | } 92 | 93 | func testNot() { 94 | verify(CP.digit().not()) 95 | } 96 | 97 | func testOptional() { 98 | verify(CP.digit().optional()) 99 | } 100 | 101 | func testOr() { 102 | verify(CP.digit().or(CP.word())) 103 | } 104 | 105 | func testPlus() { 106 | verify(CP.digit().plus()) 107 | } 108 | 109 | func testPlusGreedy() { 110 | verify(CP.digit().plusGreedy(CP.word())) 111 | } 112 | 113 | func testPlusLazy() { 114 | verify(CP.digit().plusLazy(CP.word())) 115 | } 116 | 117 | func testRepeat() { 118 | verify(CP.digit().repeated(2, 3)) 119 | } 120 | 121 | func testRepeatGreedy() { 122 | verify(CP.digit().repeatGreedy(CP.word(), 2, 3)) 123 | } 124 | 125 | func testRepeatLazy() { 126 | verify(CP.digit().repeatLazy(CP.word(), 2, 3)) 127 | } 128 | 129 | func testSeq() { 130 | verify(CP.digit().seq(CP.word())) 131 | } 132 | 133 | func testSettable() { 134 | verify(CP.digit().settable()) 135 | } 136 | 137 | func testStar() { 138 | verify(CP.digit().star()) 139 | } 140 | 141 | func testStarGreedy() { 142 | verify(CP.digit().starGreedy(CP.word())) 143 | } 144 | 145 | func testStarLazy() { 146 | verify(CP.digit().starLazy(CP.word())) 147 | } 148 | 149 | func testString() { 150 | verify(StringParser.of("ab")) 151 | } 152 | 153 | func testStringIgnoringCase() { 154 | verify(StringParser.ofIgnoringCase("ab")) 155 | } 156 | 157 | func testTimes() { 158 | verify(CP.digit().times(2)) 159 | } 160 | 161 | func testToken() { 162 | verify(CP.digit().token()) 163 | } 164 | 165 | func testTrim() { 166 | verify(CP.digit() 167 | .trim(CP.of("a"), CP.of("b"))) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/swift-petitparser/tools/GrammarDefinition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GrammarDefinition.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-22. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class GrammarDefinition { 12 | 13 | var parsers: [String: Parser] = [:] 14 | 15 | func resolve(_ name: String) -> Parser { 16 | return parsers[name]! 17 | } 18 | 19 | func ref(_ name: String) -> Parser { 20 | return Reference(name, resolve) 21 | } 22 | 23 | func def(_ name: String, _ parser: Parser) { 24 | if parsers.keys.contains(name) { 25 | fatalError("Duplicate production: \(name)") 26 | } 27 | else { 28 | parsers[name] = parser 29 | } 30 | } 31 | 32 | func redef(_ name: String, _ parser: Parser) { 33 | if !parsers.keys.contains(name) { 34 | fatalError("Undefined production: \(name)") 35 | } 36 | else { 37 | parsers[name] = parser 38 | } 39 | } 40 | 41 | func redef(_ name: String, _ function: (Parser) -> Parser) { 42 | if !parsers.keys.contains(name) { 43 | fatalError("Undefined production: \(name)") 44 | } 45 | else { 46 | parsers[name] = function(parsers[name]!) 47 | } 48 | } 49 | 50 | func action(_ name: String, _ function: @escaping (S) -> T) { 51 | redef(name, { parser in parser.map(function) }) 52 | } 53 | 54 | public func build() -> Parser { 55 | return build("start") 56 | } 57 | 58 | public func build(_ name: String) -> Parser { 59 | return resolve(Reference(name, resolve)) 60 | } 61 | 62 | func resolve(_ reference: Reference) -> Parser { 63 | var mapping: [Reference: Parser] = [:] 64 | var todo: [Parser] = [] 65 | todo.append(dereference(&mapping, reference)) 66 | var seen: Set = Set(todo) 67 | 68 | while !todo.isEmpty { 69 | let parent = todo.remove(at: todo.count - 1) 70 | 71 | for child in parent.getChildren() { 72 | var current = child 73 | if current is Reference { 74 | let referenced = dereference(&mapping, current as! Reference) 75 | parent.replace(current, referenced) 76 | current = referenced 77 | } 78 | if !seen.contains(current) { 79 | seen.insert(current) 80 | todo.append(current) 81 | } 82 | } 83 | } 84 | 85 | return mapping[reference]! 86 | } 87 | 88 | func dereference(_ mapping: inout [Reference: Parser], _ reference: Reference) -> Parser { 89 | var parser = mapping[reference] 90 | 91 | if parser == nil { 92 | var references: [Reference] = [] 93 | references.append(reference) 94 | parser = reference.resolve() 95 | 96 | while parser is Reference { 97 | let otherReference = parser as! Reference 98 | if references.contains(otherReference) { 99 | fatalError("Recursive references detected.") 100 | } 101 | references.append(otherReference) 102 | parser = otherReference.resolve() 103 | } 104 | 105 | for otherReference in references { 106 | mapping[otherReference] = parser 107 | } 108 | } 109 | 110 | return parser! 111 | } 112 | } 113 | 114 | class Reference: Parser { 115 | let name: String 116 | var resolver: (String) -> Parser 117 | 118 | init(_ name: String, _ resolver: @escaping (String) -> Parser) { 119 | self.name = name 120 | self.resolver = resolver 121 | 122 | super.init() 123 | } 124 | 125 | func resolve() -> Parser { 126 | return resolver(name) 127 | } 128 | } 129 | 130 | extension Parser: Hashable { 131 | public func hash(into hasher: inout Hasher) { 132 | if self is Reference { 133 | let ref = self as! Reference 134 | hasher.combine(ref.name) 135 | } 136 | else { 137 | hasher.combine(ObjectIdentifier(self)) 138 | } 139 | } 140 | } 141 | 142 | extension Parser: Equatable { 143 | public static func == (lhs: Parser, rhs: Parser) -> Bool { 144 | if lhs is Reference && rhs is Reference { 145 | let left = lhs as! Reference 146 | let right = rhs as! Reference 147 | return left.name == right.name 148 | } 149 | else { 150 | return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/CharacterParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterParser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class CharacterParser: Parser { 12 | let message: String 13 | let matcher: CharacterPredicate 14 | 15 | init(_ matcher: CharacterPredicate, _ message: String) { 16 | self.matcher = matcher 17 | self.message = message 18 | 19 | super.init() 20 | } 21 | 22 | public class func of(_ predicate: CharacterPredicate, _ message: String) -> CharacterParser { 23 | return CharacterParser(predicate, message) 24 | } 25 | 26 | public class func of(_ character: Character) -> CharacterParser { 27 | return of(character, "'\(character)' expected") 28 | } 29 | 30 | public class func of(_ character: Character, _ message: String) -> CharacterParser { 31 | return of(CharacterPredicates.of(character), message) 32 | } 33 | 34 | public class func any(_ message: String = "any character expected") -> CharacterParser { 35 | return CharacterParser(CharacterPredicates.any(), message) 36 | } 37 | 38 | public class func anyOf(_ chars: String) -> CharacterParser { 39 | return anyOf(chars, "any of '\(chars)' expected") 40 | } 41 | 42 | public class func anyOf(_ chars: String, _ message: String) -> CharacterParser { 43 | return of(CharacterPredicates.anyOf(chars), message) 44 | } 45 | 46 | public class func none(_ message: String = "no character expected") -> CharacterParser { 47 | return of(CharacterPredicates.none(), message) 48 | } 49 | 50 | public class func noneOf(_ chars: String) -> CharacterParser { 51 | return noneOf(chars, "none of '\(chars)' expected") 52 | } 53 | 54 | public class func noneOf(_ chars: String, _ message: String) -> CharacterParser { 55 | return of(CharacterPredicates.noneOf(chars), message) 56 | } 57 | 58 | public class func digit(_ message: String = "digit expected") -> CharacterParser { 59 | return CharacterParser(CharacterPredicates.digit(), message) 60 | } 61 | 62 | public class func letter(_ message: String = "letter expected") -> CharacterParser { 63 | return CharacterParser(CharacterPredicates.letter(), message) 64 | } 65 | 66 | public class func lowerCase(_ message: String = "lowercase letter expected") -> CharacterParser { 67 | return CharacterParser(CharacterPredicates.lowerCase(), message) 68 | } 69 | 70 | public class func pattern(_ charPattern: String) -> CharacterParser { 71 | return pattern(charPattern, "[\(charPattern)] expected") 72 | } 73 | 74 | public class func pattern(_ charPattern: String, _ message: String) -> CharacterParser { 75 | return of(CharacterPredicates.pattern(charPattern), message) 76 | } 77 | 78 | public class func range(_ start: Character, _ stop: Character) -> CharacterParser { 79 | return range(start, stop, "\(start)..\(stop) expected") 80 | } 81 | 82 | public class func range(_ start: Character, _ stop: Character, _ message: String) -> CharacterParser { 83 | return of(CharacterPredicates.range(start, stop), message) 84 | } 85 | 86 | public class func upperCase(_ message: String = "uppercase letter expected") -> CharacterParser { 87 | return CharacterParser(CharacterPredicates.upperCase(), message) 88 | } 89 | 90 | public class func whitespace(_ message: String = "whitespace expected") -> CharacterParser { 91 | return CharacterParser(CharacterPredicates.whitespace(), message) 92 | } 93 | 94 | public class func word(_ message: String = "letter or digit expected") -> CharacterParser { 95 | return CharacterParser(CharacterPredicates.word(), message) 96 | } 97 | 98 | public override func parseOn(_ context: Context) -> Result { 99 | let buffer = context.buffer 100 | let position = context.position 101 | 102 | if position < buffer.endIndex { 103 | let result = buffer[position] 104 | 105 | if matcher.test(result) { 106 | return context.success(result, buffer.index(after: position)) 107 | } 108 | } 109 | return context.failure(message) 110 | } 111 | 112 | public override func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 113 | if position < buffer.endIndex { 114 | let result = buffer[position] 115 | 116 | if matcher.test(result) { 117 | return buffer.index(after: position) 118 | } 119 | } 120 | 121 | return nil 122 | } 123 | 124 | public override func neg(_ message: String = "not expected") -> Parser { 125 | // Return an optimized version of the receiver. 126 | return CharacterParser.of(matcher.not(), message) 127 | } 128 | 129 | public override func copy() -> Parser { 130 | return CharacterParser(matcher, message) 131 | } 132 | } 133 | 134 | extension CharacterParser: CustomStringConvertible { 135 | public var description: String { 136 | return "CharacterParser[\(message)]" 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/primitive/CharacterPredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterPredicate.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct CharacterPredicate { 12 | var test: (Character) -> Bool 13 | 14 | init(matcher: @escaping (Character) -> Bool) { 15 | self.test = matcher 16 | } 17 | 18 | public func not() -> CharacterPredicate { 19 | return CharacterPredicate(matcher: { 20 | !self.test($0) 21 | }) 22 | } 23 | } 24 | 25 | public class CharacterPredicates { 26 | static func backward(_ s1: String, _ s2: String) -> Bool { 27 | return s1 > s2 28 | } 29 | 30 | static func any() -> CharacterPredicate { 31 | return CharacterPredicate(matcher: { _ in true }) 32 | } 33 | 34 | static func anyOf(_ string: String) -> CharacterPredicate { 35 | let ranges = Array(string).map { CharacterRange($0, $0) } 36 | return CharacterRange.toCharacterPredicate(ranges) 37 | } 38 | 39 | static func none() -> CharacterPredicate { 40 | return CharacterPredicate(matcher: { _ in false }) 41 | } 42 | 43 | static func noneOf(_ string: String) -> CharacterPredicate { 44 | let ranges = Array(string).map { CharacterRange($0, $0) } 45 | return CharacterRange.toCharacterPredicate(ranges).not() 46 | } 47 | 48 | static func of(_ character: Character) -> CharacterPredicate { 49 | return CharacterPredicate(matcher: { value in value == character }) 50 | } 51 | 52 | static func range(_ start: Character, _ stop: Character) -> CharacterPredicate { 53 | return CharacterPredicate(matcher: { value in start <= value && value <= stop }) 54 | } 55 | 56 | static func pattern(_ charPattern: String) -> CharacterPredicate { 57 | return PatternParser.pattern.parse(charPattern).get()! 58 | } 59 | 60 | static func ranges(_ starts: [Character], _ stops: [Character]) -> CharacterPredicate { 61 | if starts.count != starts.count { 62 | fatalError("Invalid range sizes.") 63 | } 64 | 65 | for i in 0...starts.count-1 { 66 | if starts[i].asciiValue! > stops[i].asciiValue! { 67 | fatalError("Invalid range: \(starts[i])-\(stops[i])") 68 | } else if i + 1 < starts.count && starts[i + 1] <= stops[i] { 69 | fatalError("Invalid sequence.") 70 | } 71 | } 72 | 73 | return CharacterPredicate(matcher: { value in 74 | let index = bisect(starts, value) 75 | 76 | if index == Int.min { 77 | return false 78 | } 79 | 80 | return index >= 0 || index < -1 && value <= stops[-index - 2] 81 | }) 82 | } 83 | 84 | class func bisect(_ values: [Character], _ value: Character) -> Int { 85 | var min = 0 86 | var max = values.count 87 | while min < max { 88 | let mid = min + ((max - min) >> 1) 89 | let midValue = values[mid] 90 | 91 | if midValue.asciiValue == nil { 92 | return Int.min 93 | } 94 | 95 | let midValueAscii = midValue.asciiValue! 96 | 97 | if value.asciiValue != nil { 98 | let valueAscii = value.asciiValue! 99 | 100 | if midValueAscii == valueAscii { 101 | return mid 102 | } 103 | else if midValueAscii < valueAscii { 104 | min = mid + 1 105 | } 106 | else { 107 | max = mid 108 | } 109 | } 110 | else { 111 | return Int.min 112 | } 113 | } 114 | return -(min + 1) 115 | } 116 | 117 | static func digit() -> CharacterPredicate { 118 | return CharacterPredicate(matcher: { $0.isNumber }) 119 | } 120 | 121 | static func letter() -> CharacterPredicate { 122 | return CharacterPredicate(matcher: { $0.isLetter }) 123 | } 124 | 125 | static func lowerCase() -> CharacterPredicate { 126 | return CharacterPredicate(matcher: { $0.isLowercase }) 127 | } 128 | 129 | static func upperCase() -> CharacterPredicate { 130 | return CharacterPredicate(matcher: { $0.isUppercase }) 131 | } 132 | 133 | static func hex() -> CharacterPredicate { 134 | return CharacterPredicate(matcher: { $0.isHexDigit }) 135 | } 136 | 137 | static func whitespace() -> CharacterPredicate { 138 | return CharacterPredicate(matcher: { $0.isWhitespace }) 139 | } 140 | 141 | static func word() -> CharacterPredicate { 142 | return CharacterPredicate(matcher: { $0.isLetter || $0.isNumber }) 143 | } 144 | 145 | } 146 | 147 | class PatternParser { 148 | static let patternSimple = CharacterParser.any() 149 | .map { CharacterRange($0, $0) } 150 | 151 | static let patternRange = CharacterParser.any() 152 | .seq(CharacterParser.of("-")) 153 | .seq(CharacterParser.any()) 154 | .map { (array: [Character]) -> CharacterRange in CharacterRange(array[0], array[2]) } 155 | 156 | static let patternPositive = patternRange 157 | .or(patternSimple).star() 158 | .map(CharacterRange.toCharacterPredicate) 159 | 160 | private static let unbox: ([Any]) -> CharacterPredicate = { 161 | let range = $0[1] as! CharacterPredicate 162 | if $0[0] is NSNull { 163 | return range 164 | } 165 | else { 166 | return range.not() 167 | } 168 | } 169 | public static let pattern = CharacterParser.of("^").optional().seq(patternPositive) 170 | .map(unbox) 171 | .end() 172 | } 173 | -------------------------------------------------------------------------------- /src/swift-petitparser/parser/Parser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-18. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class Parser { 12 | 13 | @discardableResult public func fastParseOn(_ buffer: String, _ position: String.Index) -> String.Index? { 14 | let result = parseOn(Context(buffer, position)) 15 | return result.isSuccess() ? result.position : nil 16 | } 17 | 18 | public func parseOn(_ context: Context) -> Result { 19 | let className = String(describing: type(of: self)) 20 | fatalError("\(className) must overwrite parseOn") 21 | } 22 | 23 | public func parse(_ input: String) -> Result { 24 | return parseOn(Context(input, input.startIndex)) 25 | } 26 | 27 | public func accept(_ input: String) -> Bool { 28 | return fastParseOn(input, input.startIndex) != nil 29 | } 30 | 31 | public func matches(_ input: String) -> [T] { 32 | var result: [T] = [] 33 | let function: (T) -> Void = {result.append($0)} 34 | 35 | and().mapWithSideEffects(function) 36 | .seq(CharacterParser.any()) 37 | .or(CharacterParser.any()) 38 | .star() 39 | .fastParseOn(input, input.startIndex) 40 | 41 | return result 42 | } 43 | 44 | public func matchesSkipping(_ input: String) -> [T] { 45 | var result: [T] = [] 46 | let function: (T) -> Void = {result.append($0)} 47 | 48 | mapWithSideEffects(function) 49 | .or(CharacterParser.any()) 50 | .star() 51 | .fastParseOn(input, input.startIndex) 52 | 53 | return result 54 | } 55 | 56 | public func seq(_ others: Parser...) -> SequenceParser { 57 | var parsers = others 58 | parsers.insert(self, at: 0) 59 | return SequenceParser(parsers) 60 | } 61 | 62 | public func or(_ others: Parser...) -> ChoiceParser { 63 | var parsers = others 64 | parsers.insert(self, at: 0) 65 | return ChoiceParser(parsers) 66 | } 67 | 68 | public func neg(_ message: String = "not expected") -> Parser { 69 | return not(message).seq(CharacterParser.any()).pick(1) 70 | } 71 | 72 | public func map(_ function: @escaping (A) -> B) -> Parser { 73 | return ActionParser(self, function) 74 | } 75 | 76 | public func mapWithSideEffects(_ function: @escaping (A) -> B) -> Parser { 77 | return ActionParser(self, function, true) 78 | } 79 | 80 | public func pick(_ index: Int) -> Parser { 81 | let function: ([Any]) -> Any = Functions.nthOfList(index) 82 | return map(function) 83 | } 84 | 85 | public func permute(_ indexes: Int...) -> Parser { 86 | return map(Functions.permutationOfList(indexes)) 87 | } 88 | 89 | public func separatedBy(_ separator: Parser) -> Parser { 90 | let function: ([Any]) -> [Any] = Functions.separateByUnpack() 91 | 92 | return SequenceParser(self, SequenceParser(separator, self).star()) 93 | .map(function) 94 | } 95 | 96 | public func delimitedBy(_ separator: Parser) -> Parser { 97 | let function: ([Any]) -> [Any] = Functions.delimitedByUnpack() 98 | 99 | return separatedBy(separator).seq(separator.optional()) 100 | .map(function) 101 | } 102 | 103 | public func replace(_ source: Parser, _ target: Parser) { 104 | // no referring parsers 105 | } 106 | 107 | public func hasEqualProperties(_ other: Parser) -> Bool { 108 | return true 109 | } 110 | 111 | public func getChildren() -> [Parser] { 112 | return [] 113 | } 114 | 115 | public func copy() -> Parser { 116 | return Parser() 117 | } 118 | } 119 | 120 | extension Parser { 121 | /** 122 | * Recursively tests for structural similarity of two parsers. 123 | * 124 | *

The code can automatically deals with recursive parsers and parsers 125 | * that refer to other parsers. This code is supposed to be overridden by 126 | * parsers that add other state. 127 | */ 128 | public func isEqualTo(_ other: Parser) -> Bool { 129 | var seen: Set = Set() 130 | return isEqualTo(other, &seen) 131 | } 132 | 133 | /** 134 | * Recursively tests for structural similarity of two parsers. 135 | */ 136 | func isEqualTo(_ other: Parser, _ seen: inout Set) -> Bool { 137 | if seen.contains(self) { 138 | return true 139 | } 140 | seen.insert(self) 141 | 142 | return isSameClass(other) 143 | && hasEqualProperties(other) && hasEqualChildren(other, &seen) 144 | } 145 | 146 | func isSameClass(_ other: Parser) -> Bool { 147 | return object_getClassName(self) == object_getClassName(other) 148 | } 149 | 150 | /** 151 | * Compares the children of two parsers. 152 | * 153 | *

Normally subclasses should not override this method, but instead {@link 154 | * #getChildren()}. 155 | */ 156 | func hasEqualChildren(_ other: Parser, _ seen: inout Set) -> Bool { 157 | let selfChildren = getChildren() 158 | let otherChildren = other.getChildren() 159 | 160 | if selfChildren.count != otherChildren.count { 161 | return false 162 | } 163 | 164 | for i in 0 ..< selfChildren.count { 165 | if !selfChildren[i].isEqualTo(otherChildren[i], &seen) { 166 | return false 167 | } 168 | } 169 | 170 | return true 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/swift-petitparser/tools/ExpressionGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpressionGroup.swift 3 | // swift-petitparser 4 | // 5 | // Created by Philipp Arndt on 2019-12-20. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ExpressionResult { 12 | let op: Any 13 | let action: (Any) -> Any 14 | } 15 | 16 | public class ExpressionGroup { 17 | let loopback: Parser 18 | 19 | var primitives: [Parser] = [] 20 | var wrappers: [Parser] = [] 21 | var prefix: [Parser] = [] 22 | var postfix: [Parser] = [] 23 | var right: [Parser] = [] 24 | var left: [Parser] = [] 25 | 26 | init(_ loopback: Parser) { 27 | self.loopback = loopback 28 | } 29 | 30 | func buildChoice(_ parsers: [Parser]) -> Parser { 31 | buildChoice(parsers, FailureParser("otherwise")) 32 | } 33 | 34 | func buildChoice(_ parsers: [Parser], _ otherwise: Parser) -> Parser { 35 | if parsers.isEmpty { 36 | return otherwise 37 | } 38 | else if parsers.count == 1 { 39 | return parsers[0] 40 | } 41 | else { 42 | return ChoiceParser(parsers) 43 | } 44 | } 45 | 46 | public func build(_ inner: Parser) -> Parser { 47 | return buildLeft(buildRight(buildPostfix(buildPrefix(buildWrapper(buildPrimitive(inner)))))) 48 | } 49 | } 50 | 51 | extension ExpressionGroup { 52 | @discardableResult public func primitive(_ parser: Parser) -> ExpressionGroup { 53 | primitives.append(parser) 54 | return self 55 | } 56 | 57 | @discardableResult public func primitive(_ parser: Parser, _ action: @escaping (A) -> B) -> ExpressionGroup { 58 | primitives.append(parser.map(action)) 59 | return self 60 | } 61 | 62 | func buildPrimitive(_ inner: Parser) -> Parser { 63 | return buildChoice(primitives, inner) 64 | } 65 | } 66 | 67 | extension ExpressionGroup { 68 | @discardableResult public func wrapper(_ left: Parser, _ right: Parser) -> ExpressionGroup { 69 | wrappers.append(SequenceParser(left, loopback, right)) 70 | return self 71 | } 72 | 73 | @discardableResult public func wrapper(_ left: Parser, _ right: Parser, _ action: @escaping (A) -> B) -> ExpressionGroup { 74 | wrappers.append(SequenceParser(left, loopback, right).map(action)) 75 | return self 76 | } 77 | 78 | func buildWrapper(_ inner: Parser) -> Parser { 79 | var choices = wrappers 80 | choices.append(inner) 81 | return buildChoice(choices, inner) 82 | } 83 | } 84 | 85 | extension ExpressionGroup { 86 | @discardableResult public func prefix(_ parser: Parser) -> ExpressionGroup { 87 | prefix.append(parser.map { ExpressionResult(op: $0, action: { $0 }) }) 88 | return self 89 | } 90 | 91 | @discardableResult public func prefix(_ parser: Parser, _ action: @escaping (A) -> B) -> ExpressionGroup { 92 | prefix.append(parser.map { ExpressionResult(op: $0, action: { action($0 as! A) }) }) 93 | return self 94 | } 95 | 96 | func buildPrefix(_ inner: Parser) -> Parser { 97 | if prefix.isEmpty { 98 | return inner 99 | } 100 | else { 101 | return SequenceParser(buildChoice(prefix).star(), inner) 102 | .map(mapPrefix) 103 | } 104 | } 105 | 106 | private func mapPrefix(_ tuple: [Any]) -> Any { 107 | var value = tuple[1] 108 | var tuples = tuple[0] as! [ExpressionResult] 109 | tuples.reverse() 110 | for result in tuples { 111 | value = result.action([result.op, value]) 112 | } 113 | return value 114 | } 115 | } 116 | 117 | extension ExpressionGroup { 118 | @discardableResult public func postfix(_ parser: Parser) -> ExpressionGroup { 119 | postfix.append(parser.map { ExpressionResult(op: $0, action: { $0 }) }) 120 | return self 121 | } 122 | 123 | @discardableResult public func postfix(_ parser: Parser, _ action: @escaping (A) -> B) -> ExpressionGroup { 124 | postfix.append(parser.map { ExpressionResult(op: $0, action: { action($0 as! A) }) }) 125 | return self 126 | } 127 | 128 | func buildPostfix(_ inner: Parser) -> Parser { 129 | if postfix.isEmpty { 130 | return inner 131 | } 132 | else { 133 | return SequenceParser(inner, buildChoice(postfix).star()) 134 | .map(mapPostfix) 135 | } 136 | } 137 | 138 | private func mapPostfix(_ tuple: [Any]) -> Any { 139 | var value = tuple[0] 140 | let tuples = tuple[1] as! [ExpressionResult] 141 | for result in tuples { 142 | value = result.action([value, result.op]) 143 | } 144 | return value 145 | } 146 | } 147 | 148 | extension ExpressionGroup { 149 | @discardableResult public func right(_ parser: Parser) -> ExpressionGroup { 150 | right.append(parser.map { ExpressionResult(op: $0, action: { $0 }) }) 151 | return self 152 | } 153 | 154 | @discardableResult public func right(_ parser: Parser, _ action: @escaping (A) -> B) -> ExpressionGroup { 155 | right.append(parser.map { ExpressionResult(op: $0, action: { action($0 as! A) }) }) 156 | return self 157 | } 158 | 159 | func buildRight(_ inner: Parser) -> Parser { 160 | if right.isEmpty { 161 | return inner 162 | } 163 | else { 164 | return inner.separatedBy(buildChoice(right)) 165 | .map(mapRight) 166 | } 167 | } 168 | 169 | private func mapRight(_ innerSequence: [Any]) -> Any { 170 | var result = innerSequence.last! 171 | 172 | for i in stride(from: innerSequence.count - 2, to: 0, by: -2) { 173 | let expressionResult = innerSequence[i] as! ExpressionResult 174 | result = expressionResult.action([innerSequence[i - 1], expressionResult.op, result]) 175 | } 176 | 177 | return result 178 | } 179 | } 180 | 181 | extension ExpressionGroup { 182 | @discardableResult public func left(_ parser: Parser) -> ExpressionGroup { 183 | left.append(parser.map { ExpressionResult(op: $0, action: { $0 }) }) 184 | return self 185 | } 186 | 187 | @discardableResult public func left(_ parser: Parser, _ action: @escaping (A) -> B) -> ExpressionGroup { 188 | left.append(parser.map { ExpressionResult(op: $0, action: { action($0 as! A) }) }) 189 | return self 190 | } 191 | 192 | func buildLeft(_ inner: Parser) -> Parser { 193 | if left.isEmpty { 194 | return inner 195 | } 196 | else { 197 | return inner.separatedBy(buildChoice(left)) 198 | .map(mapLeft) 199 | } 200 | } 201 | 202 | private func mapLeft(_ innerSequence: [Any]) -> Any { 203 | var result = innerSequence[0] 204 | 205 | for i in stride(from: 1, to: innerSequence.count, by: 2) { 206 | let expressionResult = innerSequence[i] as! ExpressionResult 207 | result = expressionResult.action([result, expressionResult.op, innerSequence[i + 1]]) 208 | } 209 | 210 | return result 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/Tests/ExpressionBuilderTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpressionBuilderTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-24. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class ExpressionBuilderTest: XCTestCase { 14 | 15 | var parser: Parser = CP.any() 16 | var evaluator: Parser = CP.any() 17 | 18 | override func setUp() { 19 | setUpParser() 20 | setUpEvaluator() 21 | } 22 | 23 | fileprivate func setUpParser() { 24 | let builder = ExpressionBuilder() 25 | builder.group() 26 | .primitive(CP.digit().plus().seq(CP.of(".") 27 | .seq(CP.digit().plus()).optional()) 28 | .flatten() 29 | .trim()) 30 | .wrapper(CP.of("(").trim(), CP.of(")").trim()) 31 | builder.group() 32 | .prefix(CP.of("-").trim()) 33 | builder.group() 34 | .postfix(SP.of("++").trim()) 35 | .postfix(SP.of("--").trim()) 36 | builder.group() 37 | .right(CP.of("^").trim()) 38 | builder.group() 39 | .left(CP.of("*").trim()) 40 | .left(CP.of("/").trim()) 41 | builder.group() 42 | .left(CP.of("+").trim()) 43 | .left(CP.of("-").trim()) 44 | parser = builder.build().end() 45 | } 46 | 47 | fileprivate func setUpEvaluator() { 48 | let builder = ExpressionBuilder() 49 | builder.group() 50 | .primitive(NumbersParser.double()) 51 | .wrapper( 52 | CP.of("(").trim(), 53 | CP.of(")").trim(), { (nums: [Any]) -> Any in nums[1] }) 54 | builder.group() 55 | .prefix(CP.of("-").trim(), { (nums: [AnyObject]) -> Double in 56 | -(nums[1] as! Double) 57 | }) 58 | builder.group() 59 | .postfix(SP.of("++").trim(), { (nums: [AnyObject]) -> Double in 60 | (nums[0] as! Double + 1) 61 | }) 62 | .postfix(SP.of("--").trim(), { (nums: [AnyObject]) -> Double in 63 | (nums[0] as! Double - 1) 64 | }) 65 | builder.group() 66 | .right(CP.of("^").trim(), { (nums: [AnyObject]) -> Double in 67 | pow((nums[0] as! Double), (nums[2] as! Double)) 68 | }) 69 | builder.group() 70 | .left(CP.of("*").trim(), { (nums: [AnyObject]) -> Double in 71 | (nums[0] as! Double) * (nums[2] as! Double) 72 | }) 73 | .left(CP.of("/").trim(), { (nums: [AnyObject]) -> Double in 74 | (nums[0] as! Double) / (nums[2] as! Double) 75 | }) 76 | builder.group() 77 | .left(CP.of("+").trim(), { (nums: [AnyObject]) -> Double in 78 | (nums[0] as! Double) + (nums[2] as! Double) 79 | }) 80 | .left(CP.of("-").trim(), { (nums: [AnyObject]) -> Double in 81 | (nums[0] as! Double) - (nums[2] as! Double) 82 | }) 83 | evaluator = builder.build().end() 84 | } 85 | 86 | func assertParse(_ input: String, _ expected: T) { 87 | let actual: T = parser.parse(input).get()! 88 | XCTAssertEqual(actual, expected) 89 | } 90 | 91 | func assertEvaluation(_ input: String, _ expected: T) { 92 | let actual: T = evaluator.parse(input).get()! 93 | XCTAssertEqual(actual, expected) 94 | } 95 | 96 | func testParseNumber() { 97 | assertParse("0", "0") 98 | assertParse("1.2", "1.2") 99 | assertParse("34.78", "34.78") 100 | } 101 | 102 | func testEvaluateNumber() { 103 | assertEvaluation("0", 0.0) 104 | assertEvaluation("0.0", 0.0) 105 | assertEvaluation("1", 1.0) 106 | assertEvaluation("1.2", 1.2) 107 | assertEvaluation("34", 34.0) 108 | assertEvaluation("34.7", 34.7) 109 | assertEvaluation("56.78", 56.78) 110 | } 111 | // 112 | // func testParseNegativeNumber() { 113 | // assertParse("-1", ["-", "1"]) 114 | // assertParse("-1.2", ["-", "1.2"]) 115 | // } 116 | 117 | func testEvaluateAdd() { 118 | assertEvaluation("1 + 2", 3.0) 119 | assertEvaluation("2 + 1", 3.0) 120 | assertEvaluation("1 + 2.3", 3.3) 121 | assertEvaluation("2.3 + 1", 3.3) 122 | assertEvaluation("1 + -2", -1.0) 123 | assertEvaluation("-2 + 1", -1.0) 124 | } 125 | 126 | func testEvaluateAddMany() { 127 | assertEvaluation("1", 1.0) 128 | assertEvaluation("1 + 2", 3.0) 129 | assertEvaluation("1 + 2 + 3", 6.0) 130 | assertEvaluation("1 + 2 + 3 + 4", 10.0) 131 | assertEvaluation("1 + 2 + 3 + 4 + 5", 15.0) 132 | } 133 | 134 | func testEvaluateSub() { 135 | assertEvaluation("1 - 2", -1.0) 136 | assertEvaluation("1.2 - 1.2", 0.0) 137 | assertEvaluation("1 - -2", 3.0) 138 | assertEvaluation("-1 - -2", 1.0) 139 | } 140 | 141 | func testEvaluateSubMany() { 142 | assertEvaluation("1", 1.0) 143 | assertEvaluation("1 - 2", -1.0) 144 | assertEvaluation("1 - 2 - 3", -4.0) 145 | assertEvaluation("1 - 2 - 3 - 4", -8.0) 146 | assertEvaluation("1 - 2 - 3 - 4 - 5", -13.0) 147 | } 148 | 149 | func testEvaluateMul() { 150 | assertEvaluation("2 * 3", 6.0) 151 | assertEvaluation("2 * -4", -8.0) 152 | } 153 | 154 | func testEvaluateMulMany() { 155 | assertEvaluation("1 * 2", 2.0) 156 | assertEvaluation("1 * 2 * 3", 6.0) 157 | assertEvaluation("1 * 2 * 3 * 4", 24.0) 158 | assertEvaluation("1 * 2 * 3 * 4 * 5", 120.0) 159 | } 160 | 161 | func testEvaluateDiv() { 162 | assertEvaluation("12 / 3", 4.0) 163 | assertEvaluation("-16 / -4", 4.0) 164 | } 165 | 166 | func testEvaluateDivMany() { 167 | assertEvaluation("100 / 2", 50.0) 168 | assertEvaluation("100 / 2 / 2", 25.0) 169 | assertEvaluation("100 / 2 / 2 / 5", 5.0) 170 | assertEvaluation("100 / 2 / 2 / 5 / 5", 1.0) 171 | } 172 | 173 | func testEvaluatePow() { 174 | assertEvaluation("2 ^ 3", 8.0) 175 | assertEvaluation("-2 ^ 3", -8.0) 176 | assertEvaluation("-2 ^ -3", -0.125) 177 | } 178 | 179 | func testEvaluatePowMany() { 180 | assertEvaluation("4 ^ 3", 64.0) 181 | assertEvaluation("4 ^ 3 ^ 2", 262144.0) 182 | assertEvaluation("4 ^ 3 ^ 2 ^ 1", 262144.0) 183 | assertEvaluation("4 ^ 3 ^ 2 ^ 1 ^ 0", 262144.0) 184 | } 185 | 186 | func testEvaluateParenthesis() { 187 | assertEvaluation("(1)", 1.0) 188 | assertEvaluation("(1 + 2)", 3.0) 189 | assertEvaluation("((1))", 1.0) 190 | assertEvaluation("((1 + 2))", 3.0) 191 | assertEvaluation("2 * (3 + 4)", 14.0) 192 | assertEvaluation("(2 + 3) * 4", 20.0) 193 | assertEvaluation("6 / (2 + 4)", 1.0) 194 | assertEvaluation("(2 + 6) / 2", 4.0) 195 | } 196 | 197 | func testEvaluatePriority() { 198 | assertEvaluation("2 * 3 + 4", 10.0) 199 | assertEvaluation("2 + 3 * 4", 14.0) 200 | assertEvaluation("6 / 3 + 4", 6.0) 201 | assertEvaluation("2 + 6 / 2", 5.0) 202 | } 203 | 204 | func testEvaluatePostfixAdd() { 205 | assertEvaluation("0++", 1.0) 206 | assertEvaluation("0++++", 2.0) 207 | assertEvaluation("0++++++", 3.0) 208 | assertEvaluation("0+++1", 2.0) 209 | assertEvaluation("0+++++1", 3.0) 210 | assertEvaluation("0+++++++1", 4.0) 211 | } 212 | 213 | func testEvaluatePostfixSub() { 214 | assertEvaluation("1--", 0.0) 215 | assertEvaluation("2----", 0.0) 216 | assertEvaluation("3------", 0.0) 217 | assertEvaluation("2---1", 0.0) 218 | assertEvaluation("3-----1", 0.0) 219 | assertEvaluation("4-------1", 0.0) 220 | } 221 | 222 | func testEvaluatePrefixNegate() { 223 | assertEvaluation("1", 1.0) 224 | assertEvaluation("-1", -1.0) 225 | assertEvaluation("--1", 1.0) 226 | assertEvaluation("---1", -1.0) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/Tests/ExamplesTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExamplesTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-20. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | class ExampleTypes { 14 | static let identifier = (CP.letter() + CP.word()<*>).flatten() 15 | 16 | static let FRACTION = CP.of(".") + CP.digit()<+> 17 | 18 | static let NUMBER = (CP.of("-").optional() 19 | + CP.digit()<+> + FRACTION.optional()) 20 | .flatten() 21 | 22 | static let STRING = (CP.of("\"") 23 | + CP.any().starLazy(CP.of("\"")) 24 | + CP.of("\"")) 25 | .flatten() 26 | 27 | static let RETURN = (SP.of("return") 28 | + CP.whitespace()<+>.flatten() 29 | + (identifier | NUMBER | STRING)) 30 | .pick(-1) 31 | 32 | static let JAVADOC = (SP.of("/**") 33 | + CP.any().starLazy(SP.of("*/")) 34 | + SP.of("*/")) 35 | .flatten() 36 | 37 | static let DOUBLE = (CP.digit()<+> + FRACTION.optional()) 38 | .flatten().trim() 39 | .map { (d: String) -> Double in Double(d)! } 40 | } 41 | 42 | // swiftlint:disable type_name 43 | typealias T = ExampleTypes 44 | 45 | class ExamplesTests: XCTestCase { 46 | 47 | func testIdentifierSuccess() { 48 | Assert.assertSuccess(T.identifier, "a", "a") 49 | Assert.assertSuccess(T.identifier, "a1", "a1") 50 | Assert.assertSuccess(T.identifier, "a12", "a12") 51 | Assert.assertSuccess(T.identifier, "ab", "ab") 52 | Assert.assertSuccess(T.identifier, "a1b", "a1b") 53 | } 54 | 55 | func testIdentifierIncomplete() { 56 | Assert.assertSuccess(T.identifier, "a_", "a", 1) 57 | Assert.assertSuccess(T.identifier, "a1-", "a1", 2) 58 | Assert.assertSuccess(T.identifier, "a12+", "a12", 3) 59 | Assert.assertSuccess(T.identifier, "ab ", "ab", 2) 60 | } 61 | 62 | func testIdentifierFailure() { 63 | Assert.assertFailure(T.identifier, "", "letter expected") 64 | Assert.assertFailure(T.identifier, "1", "letter expected") 65 | Assert.assertFailure(T.identifier, "1a", "letter expected") 66 | } 67 | 68 | func testNumberPositiveSuccess() { 69 | Assert.assertSuccess(T.NUMBER, "1", "1") 70 | Assert.assertSuccess(T.NUMBER, "12", "12") 71 | Assert.assertSuccess(T.NUMBER, "12.3", "12.3") 72 | Assert.assertSuccess(T.NUMBER, "12.34", "12.34") 73 | } 74 | 75 | func testNumberNegativeSuccess() { 76 | Assert.assertSuccess(T.NUMBER, "-1", "-1") 77 | Assert.assertSuccess(T.NUMBER, "-12", "-12") 78 | Assert.assertSuccess(T.NUMBER, "-12.3", "-12.3") 79 | Assert.assertSuccess(T.NUMBER, "-12.34", "-12.34") 80 | } 81 | 82 | func testNumberIncomplete() { 83 | Assert.assertSuccess(T.NUMBER, "1..", "1", 1) 84 | Assert.assertSuccess(T.NUMBER, "12-", "12", 2) 85 | Assert.assertSuccess(T.NUMBER, "12.3.", "12.3", 4) 86 | Assert.assertSuccess(T.NUMBER, "12.34.", "12.34", 5) 87 | } 88 | 89 | func testNumberFailure() { 90 | Assert.assertFailure(T.NUMBER, "", "digit expected") 91 | Assert.assertFailure(T.NUMBER, "-", 1, "digit expected") 92 | Assert.assertFailure(T.NUMBER, "-x", 1, "digit expected") 93 | Assert.assertFailure(T.NUMBER, ".", "digit expected") 94 | Assert.assertFailure(T.NUMBER, ".1", "digit expected") 95 | } 96 | 97 | func testStringSuccess() { 98 | Assert.assertSuccess(T.STRING, "\"\"", "\"\"") 99 | Assert.assertSuccess(T.STRING, "\"a\"", "\"a\"") 100 | Assert.assertSuccess(T.STRING, "\"ab\"", "\"ab\"") 101 | Assert.assertSuccess(T.STRING, "\"abc\"", "\"abc\"") 102 | } 103 | 104 | func testStringIncomplete() { 105 | Assert.assertSuccess(T.STRING, "\"\"x", "\"\"", 2) 106 | Assert.assertSuccess(T.STRING, "\"a\"x", "\"a\"", 3) 107 | Assert.assertSuccess(T.STRING, "\"ab\"x", "\"ab\"", 4) 108 | Assert.assertSuccess(T.STRING, "\"abc\"x", "\"abc\"", 5) 109 | } 110 | 111 | func testStringFailure() { 112 | Assert.assertFailure(T.STRING, "\"", 1, "'\"' expected") 113 | Assert.assertFailure(T.STRING, "\"a", 2, "'\"' expected") 114 | Assert.assertFailure(T.STRING, "\"ab", 3, "'\"' expected") 115 | Assert.assertFailure(T.STRING, "a\"", "'\"' expected") 116 | Assert.assertFailure(T.STRING, "ab\"", "'\"' expected") 117 | } 118 | 119 | func testReturnSuccess() { 120 | Assert.assertSuccess(T.RETURN, "return f", "f") 121 | Assert.assertSuccess(T.RETURN, "return f", "f") 122 | Assert.assertSuccess(T.RETURN, "return foo", "foo") 123 | Assert.assertSuccess(T.RETURN, "return foo", "foo") 124 | Assert.assertSuccess(T.RETURN, "return 1", "1") 125 | Assert.assertSuccess(T.RETURN, "return 1", "1") 126 | Assert.assertSuccess(T.RETURN, "return -2.3", "-2.3") 127 | Assert.assertSuccess(T.RETURN, "return -2.3", "-2.3") 128 | Assert.assertSuccess(T.RETURN, "return \"a\"", "\"a\"") 129 | Assert.assertSuccess(T.RETURN, "return \"a\"", "\"a\"") 130 | } 131 | 132 | func testReturnFailure() { 133 | Assert.assertFailure(T.RETURN, "retur f", 0, "return expected") 134 | Assert.assertFailure(T.RETURN, "return1", 6, "whitespace expected") 135 | Assert.assertFailure(T.RETURN, "return $", 8, "'\"' expected") 136 | } 137 | 138 | func testJavaDoc() { 139 | Assert.assertSuccess(T.JAVADOC, "/** foo */", "/** foo */") 140 | Assert.assertSuccess(T.JAVADOC, "/** * * */", "/** * * */") 141 | } 142 | 143 | func testExpression() { 144 | let term = SettableParser.undefined() 145 | let prod = SettableParser.undefined() 146 | let prim = SettableParser.undefined() 147 | 148 | term.set(prod.seq(CP.of("+").trim()).seq(term) 149 | .map { (nums: [Any]) -> Int in (nums[0] as! Int) + (nums[2] as! Int) } 150 | .or(prod)) 151 | 152 | prod.set(prim.seq(CP.of("*").trim()).seq(prod) 153 | .map { (nums: [Any]) -> Int in (nums[0] as! Int) * (nums[2] as! Int) } 154 | .or(prim)) 155 | 156 | prim.set((CP.of("(").trim().seq(term).seq(CP.of(")").trim())) 157 | .map { (nums: [Any]) -> Int in nums[1] as! Int } 158 | .or(NumbersParser.int())) 159 | 160 | let start = term.end() 161 | Assert.assertSuccess(start, "1 + 1", 2) 162 | Assert.assertSuccess(start, "1 + 2 * 3", 7) 163 | Assert.assertSuccess(start, "(1 + 2) * 3", 9) 164 | } 165 | 166 | func testExpressionBuilderWithSettableExample() { 167 | let recursion = SettableParser.undefined() 168 | 169 | let bracket = CP.of("(") 170 | .seq(recursion) 171 | .seq(CP.of(")")) 172 | .map { (nums: [Any]) -> Double in nums[1] as! Double } 173 | 174 | let builder = ExpressionBuilder() 175 | builder.group() 176 | .primitive(bracket.or(T.DOUBLE)) 177 | 178 | initOps(builder) 179 | 180 | recursion.set(builder.build()) 181 | let parser = recursion.end() 182 | assertCalculatorExample(parser) 183 | } 184 | 185 | func testExpressionBuilderWithWrapperExample() { 186 | let builder = ExpressionBuilder() 187 | builder.group() 188 | .primitive(NumbersParser.double()) 189 | .wrapper(CP.of("(").trim(), CP.of(")").trim(), { (nums: [Any]) -> Any in nums[1] }) 190 | 191 | initOps(builder) 192 | 193 | let parser = builder.build().end() 194 | assertCalculatorExample(parser) 195 | } 196 | 197 | private func initOps(_ builder: ExpressionBuilder) { 198 | // negation is a prefix operator 199 | builder.group() 200 | .prefix(CP.of("-").trim(), { (nums: [Any]) -> Double in 201 | -(nums[1] as! Double) 202 | }) 203 | 204 | // power is right-associative 205 | builder.group() 206 | .right(CP.of("^").trim(), { (nums: [Any]) -> Double in 207 | pow((nums[0] as! Double), (nums[2] as! Double)) 208 | }) 209 | 210 | // multiplication and addition are left-associative 211 | builder.group() 212 | .left(CP.of("*").trim(), { (nums: [Any]) -> Double in 213 | (nums[0] as! Double) * (nums[2] as! Double) 214 | }) 215 | .left(CP.of("/").trim(), { (nums: [Any]) -> Double in 216 | (nums[0] as! Double) / (nums[2] as! Double) 217 | }) 218 | 219 | builder.group() 220 | .left(CP.of("+").trim(), { (nums: [Any]) -> Double in 221 | (nums[0] as! Double) + (nums[2] as! Double) 222 | }) 223 | .left(CP.of("-").trim(), { (nums: [Any]) -> Double in 224 | (nums[0] as! Double) - (nums[2] as! Double) 225 | }) 226 | } 227 | 228 | func assertCalculatorExample(_ parser: Parser) { 229 | let intCalculator = parser.map { (value: Double) -> Int in 230 | Int(value) 231 | } 232 | 233 | Assert.assertSuccess(intCalculator, "9 - 4 - 2", 3) 234 | Assert.assertSuccess(intCalculator, "9 - (4 - 2)", 7) 235 | Assert.assertSuccess(intCalculator, "-8", -8) 236 | Assert.assertSuccess(intCalculator, "1+2*3", 7) 237 | Assert.assertSuccess(intCalculator, "1*2+3", 5) 238 | Assert.assertSuccess(intCalculator, "8/4/2", 1) 239 | Assert.assertSuccess(intCalculator, "2^2^3", 256) 240 | } 241 | 242 | func testIPAddress() { 243 | let ip = (NumbersParser.int(from: 0, to: 255) 244 | + (CP.of(".") + NumbersParser.int(from: 0, to: 255)).times(3)) 245 | .flatten().trim() 246 | 247 | Assert.assertSuccess(ip, "10.0.0.1", "10.0.0.1") 248 | Assert.assertSuccess(ip, " 10.0.0.1", "10.0.0.1") 249 | } 250 | 251 | func testHostName() { 252 | let host = (CP.word() | CP.of("."))<+>.flatten().trim() 253 | 254 | Assert.assertSuccess(host, " some.example.com ", "some.example.com") 255 | } 256 | 257 | func testIPAddressOrHostName() { 258 | let ip = (NumbersParser.int(from: 0, to: 255) 259 | + (CP.of(".") + NumbersParser.int(from: 0, to: 255)).times(3)) 260 | 261 | let host = (CP.word() | CP.of("."))<+> 262 | let parser = (ip | host).flatten().trim() 263 | Assert.assertSuccess(parser, "10.0.0.1", "10.0.0.1") 264 | Assert.assertSuccess(parser, " 10.0.0.1", "10.0.0.1") 265 | Assert.assertSuccess(parser, " some.example.com ", "some.example.com") 266 | } 267 | 268 | func testHostname() { 269 | XCTAssertEqual(validateHostname(name: "pisvr"), "pisvr") 270 | } 271 | 272 | func testHostnameNonAscii() { 273 | XCTAssertNil(validateHostname(name: "pisvr💖")) 274 | } 275 | 276 | private func validateHostname(name hostname: String) -> String? { 277 | let ip = NumbersParser.int(from: 0, to: 255) 278 | .seq(CharacterParser.of(".").seq(NumbersParser.int(from: 0, to: 255)).times(3)) 279 | 280 | let host = CharacterParser.pattern("a-zA-Z0-9./-").plus() 281 | let parser = ip.or(host).flatten().trim().end() 282 | return parser.parse(hostname).get() 283 | } 284 | 285 | } 286 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PetitParser for Swift 2 | ===================== 3 | 4 | [![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 5 | 6 | Grammars for programming languages are traditionally specified statically. They are hard to compose and reuse due to ambiguities that inevitably arise. PetitParser combines ideas from scannnerless parsing, parser combinators, parsing expression grammars and packrat parsers to model grammars and parsers as objects that can be reconfigured dynamically. 7 | 8 | This library is based on [java-petitparser](https://github.com/petitparser/java-petitparser) from Lukas Renggli. 9 | It has been adapted to Swift mainly as coding kata. 10 | 11 | 12 | Installation 13 | ------------ 14 | 15 | ### Swift package 16 | 17 | Use `https://github.com/philipparndt/swift-petitparser.git` as Swift package 18 | 19 | ### CocoaPod 20 | 21 | ``` 22 | target 'MyApp' do 23 | pod 'swift-petitparser', '~> 1.0' 24 | end 25 | ``` 26 | 27 | Tutorial 28 | -------- 29 | 30 | ### Writing a Simple Grammar 31 | 32 | Writing grammars with PetitParser is simple as writing Swift code. For example, to write a grammar that can parse identifiers that start with a letter followed by zero or more letter or digits is defined as follows: 33 | 34 | We use the following import and type alias for all examples: 35 | ```swift 36 | import swift_petitparser 37 | typealias CP = CharacterParser 38 | typealias SP = StringParser 39 | ``` 40 | 41 | ```swift 42 | class Example { 43 | init() { 44 | let id = CP.letter().seq(CP.letter().or(CP.digit()).star()) 45 | ... 46 | } 47 | } 48 | ``` 49 | 50 | Or using operator overloads: 51 | ```swift 52 | class Example { 53 | init() { 54 | let id = CP.letter() + (CP.letter() | CP.digit())<*> 55 | ... 56 | } 57 | } 58 | ``` 59 | 60 | If you look at the object `id` in the debugger, you'll notice that the code above builds a tree of parser objects: 61 | 62 | - `SequenceParser`: This parser accepts a sequence of parsers. 63 | - `CharacterParser`: This parser accepts a single letter. 64 | - `PossessiveRepeatingParser`: This parser accepts zero or more times another parser. 65 | - `ChoiceParser`: This parser accepts a single word character. 66 | - `CharacterParser`: This parser accepts a single letter. 67 | - `CharacterParser`: This parser accepts a single digit. 68 | 69 | ### Parsing Some Input 70 | 71 | To actually parse a `String` we can use the method `Parser#parse(String)`: 72 | 73 | ```swift 74 | let id1 = id.parse("yeah") 75 | let id2 = id.parse("f12") 76 | ``` 77 | 78 | The method `String` returns `Result`, which is either an instance of `Success` or `Failure`. In both examples above we are successful and can retrieve the parse result using `Success#get()`: 79 | 80 | ```swift 81 | print(id1.get()!) // ["y", ["e", "a", "h"]] 82 | print(id2.get()!) // ["f", ["1", "2"]] 83 | ``` 84 | 85 | While it seems odd to get these nested arrays with characters as a return value, this is the default decomposition of the input into a parse tree. We'll see in a while how that can be customized. 86 | 87 | If we try to parse something invalid we get an instance of `Failure` as an answer and we can retrieve a descriptive error message using `Failure#getMessage()`: 88 | 89 | ```swift 90 | let text = "123" 91 | let id3 = id.parse(text) 92 | print(id3.message!) // "letter expected" 93 | print(id3.position.utf16Offset(in: text)) // 0 94 | ``` 95 | 96 | Trying to retrieve the parse result by calling `Failure#get()` will return an `nil` optional. `Result#isSuccess()` and `Result#isFailure()` can be used to decide if the parse was successful. 97 | 98 | If you are only interested if a given string matches or not you can use the helper method `Parser#accept(String)`: 99 | 100 | ```swift 101 | print(id.accept("foo")) // true 102 | print(id.accept("123")) // false 103 | ``` 104 | 105 | ### Different Kinds of Parsers 106 | 107 | PetitParser provide a large set of ready-made parser that you can compose to consume and transform arbitrarily complex languages. The terminal parsers are the most simple ones. We've already seen a few of those: 108 | 109 | - `CharacterParser.of("a")` parses the character _a_. 110 | - `StringParser.of("abc")` parses the string _abc_. 111 | - `CharacterParser.any()` parses any character. 112 | - `CharacterParser.digit()` parses any digit from _0_ to _9_. 113 | - `CharacterParser.letter()` parses any letter from _a_ to _z_ and _A_ to _Z_. 114 | - `CharacterParser.word()` parses any letter or digit. 115 | 116 | Many other parsers are available in `CharacterParser` and `StringParser`. 117 | 118 | So instead of using the letter and digit predicate, we could have written our identifier parser like this: 119 | 120 | ```swift 121 | let id = CP.letter().seq(CP.word().star()) 122 | ``` 123 | 124 | or even: 125 | ```swift 126 | let id = CP.letter() + CP.word()<*> 127 | ``` 128 | 129 | The next set of parsers are used to combine other parsers together: 130 | 131 | - `p1.seq(p2)` parses `p1` followed by `p2` (sequence). 132 | - `p1.or(p2)` parses `p1`, if that doesn't work parses `p2` (ordered choice). 133 | - `p.star()` parses `p` zero or more times. 134 | - `p.plus()` parses `p` one or more times. 135 | - `p.optional()` parses `p`, if possible. 136 | - `p.and()` parses `p`, but does not consume its input. 137 | - `p.not()` parses `p` and succeed when p fails, but does not consume its input. 138 | - `p.end()` parses `p` and succeed at the end of the input. 139 | 140 | To attach an action or transformation to a parser we can use the following methods: 141 | 142 | - `p.map { somthing_with_$0 }` performs the transformation given the function. 143 | - `p.pick(n)` returns the `n`-th element of the list `p` returns. 144 | - `p.flatten()` creates a string from the result of `p`. 145 | - `p.token()` creates a token from the result of `p`. 146 | - `p.trim()` trims whitespaces before and after `p`. 147 | 148 | To return a string of the parsed identifier, we can modify our parser like this: 149 | 150 | ```swift 151 | let id_b = CP.letter().seq(CP.word().star()).flatten() 152 | print(id_b.parse("yeah").get()!) // yeah 153 | ``` 154 | 155 | or: 156 | 157 | ```swift 158 | let id_b = (CP.letter() + CP.word()<*>).flatten() 159 | print(id_b.parse("yeah").get()!) // yeah 160 | ``` 161 | 162 | To conveniently find all matches in a given input string you can use `Parser#matchesSkipping(String)`: 163 | 164 | ```swift 165 | let id = CP.letter().seq(CP.word().star()).flatten() 166 | let matches: [String] = id.matchesSkipping("foo 123 bar4") 167 | print(matches) // ["foo", "bar4"] 168 | ``` 169 | 170 | These are the basic elements to build parsers. There are a few more well documented and tested factory methods in the `Parser` class. If you want, browse their documentation and tests. 171 | 172 | ### Writing a More Complicated Grammar 173 | 174 | Now we are able to write a more complicated grammar for evaluating simple arithmetic expressions. Within a file we start with the grammar for a number (actually an integer): 175 | 176 | ```swift 177 | let number = CP.digit().plus().flatten().trim() 178 | .map { (d: String) -> Int in Int(d)! } 179 | 180 | // let number = CP.digit()<+>.flatten().trim() 181 | // .map { (d: String) -> Int in Int(d)! } 182 | 183 | print(number.parse("123").get()!) // 123 184 | ``` 185 | 186 | Then we define the productions for addition and multiplication in order of precedence. Note that we instantiate the productions with undefined parsers upfront, because they recursively refer to each other. Later on we can resolve this recursion by setting their reference: 187 | 188 | ```swift 189 | let term = SettableParser.undefined() 190 | let prod = SettableParser.undefined() 191 | let prim = SettableParser.undefined() 192 | 193 | term.set(prod.seq(CP.of("+").trim()).seq(term) 194 | .map { (nums: [Any]) -> Int in (nums[0] as! Int) + (nums[2] as! Int) } 195 | .or(prod)) 196 | 197 | prod.set(prim.seq(CP.of("*").trim()).seq(prod) 198 | .map { (nums: [Any]) -> Int in (nums[0] as! Int) * (nums[2] as! Int) } 199 | .or(prim)) 200 | 201 | prim.set((CP.of("(").trim().seq(term).seq(CP.of(")").trim())) 202 | .map { (nums: [Any]) -> Int in nums[1] as! Int } 203 | .or(NumbersParser.int())) 204 | ``` 205 | 206 | To make sure that our parser consumes all input we wrap it with the `end()` parser into the start production: 207 | 208 | ```swift 209 | let start = term.end() 210 | ``` 211 | 212 | That's it, now we can test our parser and evaluator: 213 | 214 | ```swift 215 | print(start.parse("1 + 2 * 3").get()!) // 7 216 | print(start.parse("(1 + 2) * 3").get()!) // 9 217 | ``` 218 | 219 | As an exercise we could extend the parser to also accept negative numbers and floating point numbers, not only integers. Furthermore it would be useful to support subtraction and division as well. All these features 220 | can be added with a few lines of PetitParser code. 221 | 222 | ### Using the Expression Builder 223 | 224 | Writing such expression parsers is pretty common and can be quite tricky to get right. To simplify things, PetitParser comes with a builder that can help you to define such grammars easily. It supports the definition of operator precedence; and prefix, postfix, left- and right-associative operators. 225 | 226 | The following code creates the empty expression builder: 227 | 228 | ```swift 229 | let builder = ExpressionBuilder() 230 | ``` 231 | 232 | Then we define the operator-groups in descending precedence. The highest precedence are the literal numbers themselves. This time we accept floating point numbers, not just integers. In the same group we add support for parenthesis: 233 | 234 | ```swift 235 | builder.group() 236 | .primitive(NumbersParser.double()) 237 | .wrapper(CP.of("(").trim(), CP.of(")").trim(), { (nums: [Any]) -> Any in nums[1] }) 238 | ``` 239 | 240 | Then come the normal arithmetic operators. Note, that the action blocks receive both, the terms and the parsed operator in the order they appear in the parsed input: 241 | 242 | ```swift 243 | // negation is a prefix operator 244 | builder.group() 245 | .prefix(CP.of("-").trim(), { (nums: [Any]) -> Double in 246 | -(nums[1] as! Double) 247 | }) 248 | 249 | // power is right-associative 250 | builder.group() 251 | .right(CP.of("^").trim(), { (nums: [Any]) -> Double in 252 | pow((nums[0] as! Double), (nums[2] as! Double)) 253 | }) 254 | 255 | // multiplication and addition are left-associative 256 | builder.group() 257 | .left(CP.of("*").trim(), { (nums: [Any]) -> Double in 258 | (nums[0] as! Double) * (nums[2] as! Double) 259 | }) 260 | .left(CP.of("/").trim(), { (nums: [Any]) -> Double in 261 | (nums[0] as! Double) / (nums[2] as! Double) 262 | }) 263 | 264 | builder.group() 265 | .left(CP.of("+").trim(), { (nums: [Any]) -> Double in 266 | (nums[0] as! Double) + (nums[2] as! Double) 267 | }) 268 | .left(CP.of("-").trim(), { (nums: [Any]) -> Double in 269 | (nums[0] as! Double) - (nums[2] as! Double) 270 | }) 271 | ``` 272 | 273 | Finally we can build the parser: 274 | 275 | ```swift 276 | let parser = builder.build().end() 277 | ``` 278 | 279 | After executing the above code we get an efficient parser that correctly 280 | evaluates expressions like: 281 | 282 | ```java 283 | parser.parse("-8").get()! // -8 284 | parser.parse("1+2*3").get()! // 7 285 | parser.parse("1*2+3").get()! // 5 286 | parser.parse("8/4/2").get()! // 1 287 | parser.parse("2^2^3").get()! // 256 288 | ``` 289 | 290 | You can find this example as test case here: [ExamplesTest.java](src/Tests/ExamplesTest.swift) 291 | 292 | Misc 293 | ---- 294 | 295 | ### History 296 | 297 | PetitParser was originally implemented by Lukas Renggli in 298 | - [Smalltalk](http://scg.unibe.ch/research/helvetia/petitparser). 299 | - [Java](https://github.com/petitparser/java-petitparser) and 300 | - [Dart](https://github.com/petitparser/dart-petitparser). 301 | 302 | and adapted to Swift by Philipp Arndt 303 | - [Swift](https://github.com/philipparndt/swift-petitparser) 304 | 305 | The implementations are very similar in their API and the supported features. If possible, the implementations adopt best practises of the target language. 306 | 307 | ### Implementations 308 | 309 | - [Dart](https://github.com/petitparser/dart-petitparser) 310 | - [Java](https://github.com/petitparser/java-petitparser) 311 | - [PHP](https://github.com/mindplay-dk/petitparserphp) 312 | - [Smalltalk](http://scg.unibe.ch/research/helvetia/petitparser) 313 | - [Swift](https://github.com/philipparndt/swift-petitparser) 314 | - [TypeScript](https://github.com/mindplay-dk/petitparser-ts) 315 | 316 | ### License 317 | 318 | The MIT License, see [LICENSE](LICENSE). 319 | -------------------------------------------------------------------------------- /src/Tests/CharacterTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-19. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import XCTest 12 | @testable import swift_petitparser 13 | 14 | class CharacterTest: XCTestCase { 15 | let _0: Character = "0" 16 | let _1: Character = "1" 17 | let _9: Character = "9" 18 | let minus: Character = "-" 19 | let space: Character = " " 20 | let a: Character = "a" 21 | let b: Character = "b" 22 | let c: Character = "c" 23 | let d: Character = "d" 24 | let e: Character = "e" 25 | let f: Character = "f" 26 | let g: Character = "g" 27 | let h: Character = "h" 28 | let i: Character = "i" 29 | let o: Character = "o" 30 | let p: Character = "p" 31 | let r: Character = "r" 32 | let t: Character = "t" 33 | let x: Character = "x" 34 | let X: Character = "X" 35 | let y: Character = "y" 36 | 37 | func testAny() { 38 | let parser = CP.any() 39 | Assert.assertSuccess(parser, "a", a) 40 | Assert.assertSuccess(parser, "b", b) 41 | Assert.assertFailure(parser, "", "any character expected") 42 | } 43 | 44 | func testAnyWithMessage() { 45 | let parser = CP.any("wrong") 46 | Assert.assertSuccess(parser, "a", a) 47 | Assert.assertSuccess(parser, "b", b) 48 | Assert.assertFailure(parser, "", "wrong") 49 | } 50 | 51 | func testAnyOf() { 52 | let parser = CP.anyOf("uncopyrightable") 53 | Assert.assertSuccess(parser, "c", c) 54 | Assert.assertSuccess(parser, "g", g) 55 | Assert.assertSuccess(parser, "h", h) 56 | Assert.assertSuccess(parser, "i", i) 57 | Assert.assertSuccess(parser, "o", o) 58 | Assert.assertSuccess(parser, "p", p) 59 | Assert.assertSuccess(parser, "r", r) 60 | Assert.assertSuccess(parser, "t", t) 61 | Assert.assertSuccess(parser, "y", y) 62 | Assert.assertFailure(parser, "x", "any of 'uncopyrightable' expected") 63 | } 64 | 65 | func testAnyOfWithMessage() { 66 | let parser = CP.anyOf("uncopyrightable", "wrong") 67 | Assert.assertSuccess(parser, "c", c) 68 | Assert.assertSuccess(parser, "g", g) 69 | Assert.assertSuccess(parser, "h", h) 70 | Assert.assertSuccess(parser, "i", i) 71 | Assert.assertSuccess(parser, "o", o) 72 | Assert.assertSuccess(parser, "p", p) 73 | Assert.assertSuccess(parser, "r", r) 74 | Assert.assertSuccess(parser, "t", t) 75 | Assert.assertSuccess(parser, "y", y) 76 | Assert.assertFailure(parser, "x", "wrong") 77 | } 78 | 79 | func testAnyOfEmpty() { 80 | let parser = CP.anyOf("") 81 | Assert.assertFailure(parser, "a", "any of '' expected") 82 | Assert.assertFailure(parser, "b", "any of '' expected") 83 | Assert.assertFailure(parser, "", "any of '' expected") 84 | } 85 | 86 | func testNone() { 87 | let parser = CP.none() 88 | Assert.assertFailure(parser, "a", "no character expected") 89 | Assert.assertFailure(parser, "b", "no character expected") 90 | Assert.assertFailure(parser, "", "no character expected") 91 | } 92 | 93 | func testNoneWithMessage() { 94 | let parser = CP.none("wrong") 95 | Assert.assertFailure(parser, "a", "wrong") 96 | Assert.assertFailure(parser, "b", "wrong") 97 | Assert.assertFailure(parser, "", "wrong") 98 | } 99 | 100 | func testNoneOf() { 101 | let parser = CP.noneOf("uncopyrightable") 102 | Assert.assertSuccess(parser, "x", x) 103 | Assert.assertFailure(parser, "c", "none of 'uncopyrightable' expected") 104 | Assert.assertFailure(parser, "g", "none of 'uncopyrightable' expected") 105 | Assert.assertFailure(parser, "h", "none of 'uncopyrightable' expected") 106 | Assert.assertFailure(parser, "i", "none of 'uncopyrightable' expected") 107 | Assert.assertFailure(parser, "o", "none of 'uncopyrightable' expected") 108 | Assert.assertFailure(parser, "p", "none of 'uncopyrightable' expected") 109 | Assert.assertFailure(parser, "r", "none of 'uncopyrightable' expected") 110 | Assert.assertFailure(parser, "t", "none of 'uncopyrightable' expected") 111 | Assert.assertFailure(parser, "y", "none of 'uncopyrightable' expected") 112 | } 113 | 114 | func testNoneOfWithMessage() { 115 | let parser = CP.noneOf("uncopyrightable", "wrong") 116 | Assert.assertSuccess(parser, "x", x) 117 | Assert.assertFailure(parser, "c", "wrong") 118 | Assert.assertFailure(parser, "g", "wrong") 119 | Assert.assertFailure(parser, "h", "wrong") 120 | Assert.assertFailure(parser, "i", "wrong") 121 | Assert.assertFailure(parser, "o", "wrong") 122 | Assert.assertFailure(parser, "p", "wrong") 123 | Assert.assertFailure(parser, "r", "wrong") 124 | Assert.assertFailure(parser, "t", "wrong") 125 | Assert.assertFailure(parser, "y", "wrong") 126 | } 127 | 128 | func testNoneOfEmpty() { 129 | let parser = CP.noneOf("") 130 | Assert.assertSuccess(parser, "a", a) 131 | Assert.assertSuccess(parser, "b", b) 132 | Assert.assertFailure(parser, "", "none of '' expected") 133 | } 134 | 135 | func testIs() { 136 | let parser = CP.of("a") 137 | Assert.assertSuccess(parser, "a", a) 138 | Assert.assertFailure(parser, "b", "'a' expected") 139 | Assert.assertFailure(parser, "", "'a' expected") 140 | } 141 | 142 | func testIsWithMessage() { 143 | let parser = CP.of("a", "wrong") 144 | Assert.assertSuccess(parser, "a", a) 145 | Assert.assertFailure(parser, "b", "wrong") 146 | Assert.assertFailure(parser, "", "wrong") 147 | } 148 | 149 | func testDigit() { 150 | let parser = CP.digit() 151 | Assert.assertSuccess(parser, "1", _1) 152 | Assert.assertSuccess(parser, "9", _9) 153 | Assert.assertFailure(parser, "a", "digit expected") 154 | Assert.assertFailure(parser, "", "digit expected") 155 | } 156 | 157 | func testDigitWithMessage() { 158 | let parser = CP.digit("wrong") 159 | Assert.assertSuccess(parser, "1", _1) 160 | Assert.assertSuccess(parser, "9", _9) 161 | Assert.assertFailure(parser, "a", "wrong") 162 | Assert.assertFailure(parser, "", "wrong") 163 | } 164 | 165 | func testLetter() { 166 | let parser = CP.letter() 167 | Assert.assertSuccess(parser, "a", a) 168 | Assert.assertSuccess(parser, "X", X) 169 | Assert.assertFailure(parser, "0", "letter expected") 170 | Assert.assertFailure(parser, "", "letter expected") 171 | } 172 | 173 | func testLetterWithMessage() { 174 | let parser = CP.letter("wrong") 175 | Assert.assertSuccess(parser, "a", a) 176 | Assert.assertSuccess(parser, "X", X) 177 | Assert.assertFailure(parser, "0", "wrong") 178 | Assert.assertFailure(parser, "", "wrong") 179 | } 180 | 181 | func testLowerCase() { 182 | let parser = CP.lowerCase() 183 | Assert.assertSuccess(parser, "a", a) 184 | Assert.assertFailure(parser, "A", "lowercase letter expected") 185 | Assert.assertFailure(parser, "0", "lowercase letter expected") 186 | Assert.assertFailure(parser, "", "lowercase letter expected") 187 | } 188 | 189 | func testLowerCaseWithMessage() { 190 | let parser = CP.lowerCase("wrong") 191 | Assert.assertSuccess(parser, "a", a) 192 | Assert.assertFailure(parser, "A", "wrong") 193 | Assert.assertFailure(parser, "0", "wrong") 194 | Assert.assertFailure(parser, "", "wrong") 195 | } 196 | 197 | func testPatternWithSingle() { 198 | let parser = CP.pattern("abc") 199 | Assert.assertSuccess(parser, "a", a) 200 | Assert.assertSuccess(parser, "b", b) 201 | Assert.assertSuccess(parser, "c", c) 202 | Assert.assertFailure(parser, "d", "[abc] expected") 203 | Assert.assertFailure(parser, "", "[abc] expected") 204 | } 205 | 206 | func testPatternWithMessage() { 207 | let parser = CP.pattern("abc", "wrong") 208 | Assert.assertSuccess(parser, "a", a) 209 | Assert.assertFailure(parser, "d", "wrong") 210 | Assert.assertFailure(parser, "", "wrong") 211 | } 212 | 213 | func testPatternWithRange() { 214 | let parser = CP.pattern("a-c") 215 | Assert.assertSuccess(parser, "a", a) 216 | Assert.assertSuccess(parser, "b", b) 217 | Assert.assertSuccess(parser, "c", c) 218 | Assert.assertFailure(parser, "d", "[a-c] expected") 219 | Assert.assertFailure(parser, "", "[a-c] expected") 220 | } 221 | 222 | func testPatternWithOverlappingRange() { 223 | let parser = CP.pattern("b-da-c") 224 | Assert.assertSuccess(parser, "a", a) 225 | Assert.assertSuccess(parser, "b", b) 226 | Assert.assertSuccess(parser, "c", c) 227 | Assert.assertSuccess(parser, "d", d) 228 | Assert.assertFailure(parser, "e", "[b-da-c] expected") 229 | Assert.assertFailure(parser, "", "[b-da-c] expected") 230 | } 231 | 232 | func testPatternWithAdjacentRange() { 233 | let parser = CP.pattern("c-ea-c") 234 | Assert.assertSuccess(parser, "a", a) 235 | Assert.assertSuccess(parser, "b", b) 236 | Assert.assertSuccess(parser, "c", c) 237 | Assert.assertSuccess(parser, "d", d) 238 | Assert.assertSuccess(parser, "e", e) 239 | Assert.assertFailure(parser, "f", "[c-ea-c] expected") 240 | Assert.assertFailure(parser, "", "[c-ea-c] expected") 241 | } 242 | 243 | func testPatternWithPrefixRange() { 244 | let parser = CP.pattern("a-ea-c") 245 | Assert.assertSuccess(parser, "a", a) 246 | Assert.assertSuccess(parser, "b", b) 247 | Assert.assertSuccess(parser, "c", c) 248 | Assert.assertSuccess(parser, "d", d) 249 | Assert.assertSuccess(parser, "e", e) 250 | Assert.assertFailure(parser, "f", "[a-ea-c] expected") 251 | Assert.assertFailure(parser, "", "[a-ea-c] expected") 252 | } 253 | 254 | func testPatternWithPostfixRange() { 255 | let parser = CP.pattern("a-ec-e") 256 | Assert.assertSuccess(parser, "a", a) 257 | Assert.assertSuccess(parser, "b", b) 258 | Assert.assertSuccess(parser, "c", c) 259 | Assert.assertSuccess(parser, "d", d) 260 | Assert.assertSuccess(parser, "e", e) 261 | Assert.assertFailure(parser, "f", "[a-ec-e] expected") 262 | Assert.assertFailure(parser, "", "[a-ec-e] expected") 263 | } 264 | 265 | func testPatternWithRepeatedRange() { 266 | let parser = CP.pattern("a-ea-e") 267 | Assert.assertSuccess(parser, "a", a) 268 | Assert.assertSuccess(parser, "b", b) 269 | Assert.assertSuccess(parser, "c", c) 270 | Assert.assertSuccess(parser, "d", d) 271 | Assert.assertSuccess(parser, "e", e) 272 | Assert.assertFailure(parser, "f", "[a-ea-e] expected") 273 | Assert.assertFailure(parser, "", "[a-ea-e] expected") 274 | } 275 | 276 | func testPatternWithComposed() { 277 | let parser = CP.pattern("ac-df-") 278 | Assert.assertSuccess(parser, "a", a) 279 | Assert.assertSuccess(parser, "c", c) 280 | Assert.assertSuccess(parser, "d", d) 281 | Assert.assertSuccess(parser, "f", f) 282 | Assert.assertSuccess(parser, "-", minus) 283 | Assert.assertFailure(parser, "b", "[ac-df-] expected") 284 | Assert.assertFailure(parser, "e", "[ac-df-] expected") 285 | Assert.assertFailure(parser, "g", "[ac-df-] expected") 286 | Assert.assertFailure(parser, "", "[ac-df-] expected") 287 | } 288 | 289 | func testPatternWithNegatedSingle() { 290 | let parser = CP.pattern("^a") 291 | Assert.assertSuccess(parser, "b", b) 292 | Assert.assertFailure(parser, "a", "[^a] expected") 293 | Assert.assertFailure(parser, "", "[^a] expected") 294 | } 295 | 296 | func testPatternWithNegatedRange() { 297 | let parser = CP.pattern("^a-c") 298 | Assert.assertSuccess(parser, "d", d) 299 | Assert.assertFailure(parser, "a", "[^a-c] expected") 300 | Assert.assertFailure(parser, "b", "[^a-c] expected") 301 | Assert.assertFailure(parser, "c", "[^a-c] expected") 302 | Assert.assertFailure(parser, "", "[^a-c] expected") 303 | } 304 | 305 | func testRange() { 306 | let parser = CP.range("e", "o") 307 | Assert.assertFailure(parser, "d", "e..o expected") 308 | Assert.assertSuccess(parser, "e", e) 309 | Assert.assertSuccess(parser, "i", i) 310 | Assert.assertSuccess(parser, "o", o) 311 | Assert.assertFailure(parser, "p", "e..o expected") 312 | Assert.assertFailure(parser, "", "e..o expected") 313 | } 314 | 315 | func testRangeWithMessage() { 316 | let parser = CP.range("e", "o", "wrong") 317 | Assert.assertFailure(parser, "d", "wrong") 318 | Assert.assertSuccess(parser, "e", e) 319 | Assert.assertSuccess(parser, "i", i) 320 | Assert.assertSuccess(parser, "o", o) 321 | Assert.assertFailure(parser, "p", "wrong") 322 | Assert.assertFailure(parser, "", "wrong") 323 | } 324 | 325 | func testUpperCase() { 326 | let parser = CP.upperCase() 327 | Assert.assertSuccess(parser, "X", X) 328 | Assert.assertFailure(parser, "x", "uppercase letter expected") 329 | Assert.assertFailure(parser, "0", "uppercase letter expected") 330 | Assert.assertFailure(parser, "", "uppercase letter expected") 331 | } 332 | 333 | func testUpperCaseWithMessage() { 334 | let parser = CP.upperCase("wrong") 335 | Assert.assertSuccess(parser, "X", X) 336 | Assert.assertFailure(parser, "x", "wrong") 337 | Assert.assertFailure(parser, "0", "wrong") 338 | Assert.assertFailure(parser, "", "wrong") 339 | } 340 | 341 | func testWhitespace() { 342 | let parser = CP.whitespace() 343 | Assert.assertSuccess(parser, " ", space) 344 | Assert.assertFailure(parser, "z", "whitespace expected") 345 | Assert.assertFailure(parser, "-", "whitespace expected") 346 | Assert.assertFailure(parser, "", "whitespace expected") 347 | } 348 | 349 | func testWhitespaceWithMessage() { 350 | let parser = CP.whitespace("wrong") 351 | Assert.assertSuccess(parser, " ", space) 352 | Assert.assertFailure(parser, "z", "wrong") 353 | Assert.assertFailure(parser, "-", "wrong") 354 | Assert.assertFailure(parser, "", "wrong") 355 | } 356 | 357 | func testWord() { 358 | let parser = CP.word() 359 | Assert.assertSuccess(parser, "a", a) 360 | Assert.assertSuccess(parser, "0", _0) 361 | Assert.assertFailure(parser, "-", "letter or digit expected") 362 | Assert.assertFailure(parser, "", "letter or digit expected") 363 | } 364 | 365 | func testWordWithMessage() { 366 | let parser = CP.word("wrong") 367 | Assert.assertSuccess(parser, "a", a) 368 | Assert.assertSuccess(parser, "0", _0) 369 | Assert.assertFailure(parser, "-", "wrong") 370 | Assert.assertFailure(parser, "", "wrong") 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/Tests/ParsersTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParsersTest.swift 3 | // Tests 4 | // 5 | // Created by Philipp Arndt on 2019-12-20. 6 | // Copyright © 2019 petitparser. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | @testable import swift_petitparser 12 | 13 | // swiftlint:disable type_body_length nesting 14 | class ParsersTests: XCTestCase { 15 | 16 | func char(_ c: Character) -> Character { 17 | return c 18 | } 19 | 20 | func testAnd() { 21 | let parser = CP.of("a").and() 22 | Assert.assertSuccess(parser, "a", char("a"), 0) 23 | Assert.assertFailure(parser, "b", "'a' expected") 24 | Assert.assertFailure(parser, "") 25 | } 26 | 27 | func testChoice2() { 28 | let parser1 = CP.of("a").or(CP.of("b")) 29 | Assert.assertSuccess(parser1, "a", char("a")) 30 | Assert.assertSuccess(parser1, "b", char("b")) 31 | Assert.assertFailure(parser1, "c") 32 | Assert.assertFailure(parser1, "") 33 | 34 | let parser2 = CP.of("a") | CP.of("b") 35 | Assert.assertSuccess(parser2, "a", char("a")) 36 | Assert.assertSuccess(parser2, "b", char("b")) 37 | Assert.assertFailure(parser2, "c") 38 | Assert.assertFailure(parser2, "") 39 | } 40 | 41 | func testChoice3() { 42 | let parser1 = CP.of("a").or(CP.of("b")).or(CP.of("c")) 43 | Assert.assertSuccess(parser1, "a", char("a")) 44 | Assert.assertSuccess(parser1, "b", char("b")) 45 | Assert.assertSuccess(parser1, "c", char("c")) 46 | Assert.assertFailure(parser1, "d") 47 | Assert.assertFailure(parser1, "") 48 | 49 | let parser2 = CP.of("a") | CP.of("b") | CP.of("c") 50 | Assert.assertSuccess(parser2, "a", char("a")) 51 | Assert.assertSuccess(parser2, "b", char("b")) 52 | Assert.assertSuccess(parser2, "c", char("c")) 53 | Assert.assertFailure(parser2, "d") 54 | Assert.assertFailure(parser2, "") 55 | } 56 | 57 | func testEndOfInput() { 58 | let parser = CP.of("a").end() 59 | Assert.assertFailure(parser, "", "'a' expected") 60 | Assert.assertSuccess(parser, "a", char("a")) 61 | Assert.assertFailure(parser, "aa", 1, "end of input expected") 62 | } 63 | 64 | func testSettable() { 65 | let parser = CP.of("a").settable() 66 | Assert.assertSuccess(parser, "a", char("a")) 67 | Assert.assertFailure(parser, "b", 0, "'a' expected") 68 | 69 | parser.set(CP.of("b")) 70 | Assert.assertSuccess(parser, "b", char("b")) 71 | Assert.assertFailure(parser, "a", 0, "'b' expected") 72 | } 73 | 74 | func testFlatten1() { 75 | let parser = CP.digit().repeated(2, RepeatingParser.UNBOUNDED).flatten() 76 | Assert.assertFailure(parser, "", 0, "digit expected") 77 | Assert.assertFailure(parser, "a", 0, "digit expected") 78 | Assert.assertFailure(parser, "1", 1, "digit expected") 79 | Assert.assertFailure(parser, "1a", 1, "digit expected") 80 | Assert.assertSuccess(parser, "12", "12") 81 | Assert.assertSuccess(parser, "123", "123") 82 | Assert.assertSuccess(parser, "1234", "1234") 83 | } 84 | 85 | func testFlatten2() { 86 | let parser = CP.digit().repeated(2, RepeatingParser.UNBOUNDED) 87 | .flatten("gimme a number") 88 | Assert.assertFailure(parser, "", 0, "gimme a number") 89 | Assert.assertFailure(parser, "a", 0, "gimme a number") 90 | Assert.assertFailure(parser, "1", 0, "gimme a number") 91 | Assert.assertFailure(parser, "1a", 0, "gimme a number") 92 | Assert.assertSuccess(parser, "12", "12") 93 | Assert.assertSuccess(parser, "123", "123") 94 | Assert.assertSuccess(parser, "1234", "1234") 95 | } 96 | 97 | func testMap() { 98 | let parser = CP.digit() 99 | .map { (c: Character) -> Int in Int("\(c)") ?? -1 } 100 | Assert.assertSuccess(parser, "1", 1) 101 | Assert.assertSuccess(parser, "4", 4) 102 | Assert.assertSuccess(parser, "9", 9) 103 | Assert.assertFailure(parser, "") 104 | Assert.assertFailure(parser, "a") 105 | } 106 | 107 | func testPick() { 108 | let parser = CP.digit().seq(CP.letter()).pick(1) 109 | Assert.assertSuccess(parser, "1a", char("a")) 110 | Assert.assertSuccess(parser, "2b", char("b")) 111 | Assert.assertFailure(parser, "") 112 | Assert.assertFailure(parser, "1", 1, "letter expected") 113 | Assert.assertFailure(parser, "12", 1, "letter expected") 114 | 115 | let parser2 = (CP.digit() + CP.letter()).pick(1) 116 | Assert.assertSuccess(parser2, "1a", char("a")) 117 | Assert.assertSuccess(parser2, "2b", char("b")) 118 | Assert.assertFailure(parser2, "") 119 | Assert.assertFailure(parser2, "1", 1, "letter expected") 120 | Assert.assertFailure(parser2, "12", 1, "letter expected") 121 | } 122 | 123 | func testPickLast() { 124 | let parser = CP.digit().seq(CP.letter()).pick(-1) 125 | Assert.assertSuccess(parser, "1a", char("a")) 126 | Assert.assertSuccess(parser, "2b", char("b")) 127 | Assert.assertFailure(parser, "") 128 | Assert.assertFailure(parser, "1", 1, "letter expected") 129 | Assert.assertFailure(parser, "12", 1, "letter expected") 130 | } 131 | 132 | func testPermute() { 133 | let parser = CP.digit().seq(CP.letter()).permute(1, 0) 134 | Assert.assertSuccess(parser, "1a", [char("a"), char("1")]) 135 | Assert.assertSuccess(parser, "2b", [char("b"), char("2")]) 136 | Assert.assertFailure(parser, "") 137 | Assert.assertFailure(parser, "1", 1, "letter expected") 138 | Assert.assertFailure(parser, "12", 1, "letter expected") 139 | } 140 | 141 | func testPermuteLast() { 142 | let parser = CP.digit().seq(CP.letter()).permute(-1, 0) 143 | Assert.assertSuccess(parser, "1a", [char("a"), char("1")]) 144 | Assert.assertSuccess(parser, "2b", [char("b"), char("2")]) 145 | Assert.assertFailure(parser, "") 146 | Assert.assertFailure(parser, "1", 1, "letter expected") 147 | Assert.assertFailure(parser, "12", 1, "letter expected") 148 | } 149 | 150 | func testNeg1() { 151 | let parser = CP.digit().neg() 152 | Assert.assertFailure(parser, "1", 0) 153 | Assert.assertFailure(parser, "9", 0) 154 | Assert.assertSuccess(parser, "a", char("a")) 155 | Assert.assertSuccess(parser, " ", char(" ")) 156 | Assert.assertFailure(parser, "", 0) 157 | } 158 | 159 | func testNeg2() { 160 | let parser = CP.digit().neg("no digit expected") 161 | Assert.assertFailure(parser, "1", 0, "no digit expected") 162 | Assert.assertFailure(parser, "9", 0, "no digit expected") 163 | Assert.assertSuccess(parser, "a", char("a")) 164 | Assert.assertSuccess(parser, " ", char(" ")) 165 | Assert.assertFailure(parser, "", 0, "no digit expected") 166 | } 167 | 168 | func testNeg3() { 169 | let parser = SP.of("foo").neg("no foo expected") 170 | Assert.assertFailure(parser, "foo", 0, "no foo expected") 171 | Assert.assertFailure(parser, "foobar", 0, "no foo expected") 172 | Assert.assertSuccess(parser, "f", char("f")) 173 | Assert.assertSuccess(parser, " ", char(" ")) 174 | } 175 | 176 | func testNot() { 177 | let parser = CP.of("a").not("not a expected") 178 | Assert.assertFailure(parser, "a", "not a expected") 179 | Assert.assertSuccess(parser, "b", Assert.NULL, 0) 180 | Assert.assertSuccess(parser, "", Assert.NULL) 181 | } 182 | 183 | func testNotAlternative() { 184 | let parser = !CP.of("a") 185 | Assert.assertFailure(parser, "a", "unexpected") 186 | Assert.assertSuccess(parser, "b", Assert.NULL, 0) 187 | Assert.assertSuccess(parser, "", Assert.NULL) 188 | } 189 | 190 | func testOptional() { 191 | let parser = CP.of("a").optional() 192 | Assert.assertSuccess(parser, "a", char("a")) 193 | Assert.assertSuccess(parser, "b", Assert.NULL, 0) 194 | Assert.assertSuccess(parser, "", Assert.NULL) 195 | } 196 | 197 | func testPlus() { 198 | let parser = CP.of("a").plus() 199 | Assert.assertFailure(parser, "", "'a' expected") 200 | Assert.assertSuccess(parser, "a", [char("a")]) 201 | Assert.assertSuccess(parser, "aa", [char("a"), char("a")]) 202 | Assert.assertSuccess(parser, "aaa", [char("a"), char("a"), char("a")]) 203 | } 204 | 205 | func testPlusGreedy() { 206 | let parser = CP.word().plusGreedy(CharacterParser.digit()) 207 | Assert.assertFailure(parser, "", 0, "letter or digit expected") 208 | Assert.assertFailure(parser, "a", 1, "digit expected") 209 | Assert.assertFailure(parser, "ab", 1, "digit expected") 210 | Assert.assertFailure(parser, "1", 1, "digit expected") 211 | Assert.assertSuccess(parser, "a1", [char("a")], 1) 212 | Assert.assertSuccess(parser, "ab1", [char("a"), char("b")], 2) 213 | Assert.assertSuccess(parser, "abc1", [char("a"), char("b"), char("c")], 3) 214 | Assert.assertSuccess(parser, "12", [char("1")], 1) 215 | Assert.assertSuccess(parser, "a12", [char("a"), char("1")], 2) 216 | Assert.assertSuccess(parser, "ab12", [char("a"), char("b"), char("1")], 3) 217 | Assert.assertSuccess(parser, "abc12", [char("a"), char("b"), char("c"), char("1")], 4) 218 | Assert.assertSuccess(parser, "123", [char("1"), char("2")], 2) 219 | Assert.assertSuccess(parser, "a123", [char("a"), char("1"), char("2")], 3) 220 | Assert.assertSuccess(parser, "ab123", [char("a"), char("b"), char("1"), char("2")], 4) 221 | Assert.assertSuccess(parser, "abc123", [char("a"), char("b"), char("c"), char("1"), char("2")], 5) 222 | } 223 | 224 | func testPlusLazy() { 225 | let parser = CP.word().plusLazy(CharacterParser.digit()) 226 | Assert.assertFailure(parser, "") 227 | Assert.assertFailure(parser, "a", 1, "digit expected") 228 | Assert.assertFailure(parser, "ab", 2, "digit expected") 229 | Assert.assertFailure(parser, "1", 1, "digit expected") 230 | Assert.assertSuccess(parser, "a1", [char("a")], 1) 231 | Assert.assertSuccess(parser, "ab1", [char("a"), char("b")], 2) 232 | Assert.assertSuccess(parser, "abc1", [char("a"), char("b"), char("c")], 3) 233 | Assert.assertSuccess(parser, "12", [char("1")], 1) 234 | Assert.assertSuccess(parser, "a12", [char("a")], 1) 235 | Assert.assertSuccess(parser, "ab12", [char("a"), char("b")], 2) 236 | Assert.assertSuccess(parser, "abc12", [char("a"), char("b"), char("c")], 3) 237 | Assert.assertSuccess(parser, "123", [char("1")], 1) 238 | Assert.assertSuccess(parser, "a123", [char("a")], 1) 239 | Assert.assertSuccess(parser, "ab123", [char("a"), char("b")], 2) 240 | Assert.assertSuccess(parser, "abc123", [char("a"), char("b"), char("c")], 3) 241 | } 242 | 243 | func testTimes() { 244 | let parser = CP.of("a").times(2) 245 | Assert.assertFailure(parser, "", 0, "'a' expected") 246 | Assert.assertFailure(parser, "a", 1, "'a' expected") 247 | Assert.assertSuccess(parser, "aa", [char("a"), char("a")]) 248 | Assert.assertSuccess(parser, "aaa", [char("a"), char("a")], 2) 249 | } 250 | 251 | func testRepeat() { 252 | let parser = CP.of("a").repeated(2, 3) 253 | Assert.assertFailure(parser, "", "'a' expected") 254 | Assert.assertFailure(parser, "a", 1, "'a' expected") 255 | Assert.assertSuccess(parser, "aa", [char("a"), char("a")]) 256 | Assert.assertSuccess(parser, "aaa", [char("a"), char("a"), char("a")]) 257 | Assert.assertSuccess(parser, "aaaa", [char("a"), char("a"), char("a")], 3) 258 | } 259 | 260 | // func testRepeatMinError1() { 261 | // let _ = CP.of("a").repeated(-2, 5) 262 | // } 263 | // 264 | // func testRepeatMinError2() { 265 | // let _ = CP.of("a").repeated(3, 5) 266 | // } 267 | 268 | func testRepeatUnbounded() { 269 | var list: [Character] = [] 270 | var string = "" 271 | for _ in 0..<100000 { 272 | list.append("a") 273 | string += "a" 274 | } 275 | 276 | let parser = CP.of("a").repeated(2, RepeatingParser.UNBOUNDED) 277 | Assert.assertSuccess(parser, string, list) 278 | } 279 | 280 | func testRepeatGreedy() { 281 | let parser = CP.word().repeatGreedy(CP.digit(), 2, 4) 282 | Assert.assertFailure(parser, "", 0, "letter or digit expected") 283 | Assert.assertFailure(parser, "a", 1, "letter or digit expected") 284 | Assert.assertFailure(parser, "ab", 2, "digit expected") 285 | Assert.assertFailure(parser, "abc", 2, "digit expected") 286 | Assert.assertFailure(parser, "abcd", 2, "digit expected") 287 | Assert.assertFailure(parser, "abcde", 2, "digit expected") 288 | Assert.assertFailure(parser, "1", 1, "letter or digit expected") 289 | Assert.assertFailure(parser, "a1", 2, "digit expected") 290 | Assert.assertSuccess(parser, "ab1", [char("a"), char("b")], 2) 291 | Assert.assertSuccess(parser, "abc1", [char("a"), char("b"), char("c")], 3) 292 | Assert.assertSuccess(parser, "abcd1", [char("a"), char("b"), char("c"), char("d")], 4) 293 | Assert.assertFailure(parser, "abcde1", 2, "digit expected") 294 | Assert.assertFailure(parser, "12", 2, "digit expected") 295 | Assert.assertSuccess(parser, "a12", [char("a"), char("1")], 2) 296 | Assert.assertSuccess(parser, "ab12", [char("a"), char("b"), char("1")], 3) 297 | Assert.assertSuccess(parser, "abc12", [char("a"), char("b"), char("c"), char("1")], 4) 298 | Assert.assertSuccess(parser, "abcd12", [char("a"), char("b"), char("c"), char("d")], 4) 299 | Assert.assertFailure(parser, "abcde12", 2, "digit expected") 300 | Assert.assertSuccess(parser, "123", [char("1"), char("2")], 2) 301 | Assert.assertSuccess(parser, "a123", [char("a"), char("1"), char("2")], 3) 302 | Assert.assertSuccess(parser, "ab123", [char("a"), char("b"), char("1"), char("2")], 4) 303 | Assert.assertSuccess(parser, "abc123", [char("a"), char("b"), char("c"), char("1")], 4) 304 | Assert.assertSuccess(parser, "abcd123", [char("a"), char("b"), char("c"), char("d")], 4) 305 | Assert.assertFailure(parser, "abcde123", 2, "digit expected") 306 | } 307 | 308 | // func testRepeatGreedyUnbounded() { 309 | // var letter = "" 310 | // var listLetter: [Character] = [] 311 | // 312 | // var digit = "" 313 | // var listDigit: [Character] = [] 314 | // 315 | // for _ in 0..<100000 { 316 | // letter += "a" 317 | // listLetter += "a" 318 | // 319 | // digit += "1" 320 | // listDigit += "1" 321 | // } 322 | // 323 | // letter += "a" 324 | // digit += "1" 325 | // 326 | // let parser = CP.word() 327 | // .repeatGreedy(CP.digit(), 2, RepeatingParser.UNBOUNDED); 328 | // Assert.assertSuccess(parser, letter, listLetter, listLetter.count) 329 | // Assert.assertSuccess(parser, digit, listDigit, listDigit.count) 330 | // } 331 | 332 | func testRepeatLazy() { 333 | let parser = CP.word().repeatLazy(CP.digit(), 2, 4) 334 | Assert.assertFailure(parser, "", 0, "letter or digit expected") 335 | Assert.assertFailure(parser, "a", 1, "letter or digit expected") 336 | Assert.assertFailure(parser, "ab", 2, "digit expected") 337 | Assert.assertFailure(parser, "abc", 3, "digit expected") 338 | Assert.assertFailure(parser, "abcd", 4, "digit expected") 339 | Assert.assertFailure(parser, "abcde", 4, "digit expected") 340 | Assert.assertFailure(parser, "1", 1, "letter or digit expected") 341 | Assert.assertFailure(parser, "a1", 2, "digit expected") 342 | Assert.assertSuccess(parser, "ab1", [char("a"), char("b")], 2) 343 | Assert.assertSuccess(parser, "abc1", [char("a"), char("b"), char("c")], 3) 344 | Assert.assertSuccess(parser, "abcd1", [char("a"), char("b"), char("c"), char("d")], 4) 345 | Assert.assertFailure(parser, "abcde1", 4, "digit expected") 346 | Assert.assertFailure(parser, "12", 2, "digit expected") 347 | Assert.assertSuccess(parser, "a12", [char("a"), char("1")], 2) 348 | Assert.assertSuccess(parser, "ab12", [char("a"), char("b")], 2) 349 | Assert.assertSuccess(parser, "abc12", [char("a"), char("b"), char("c")], 3) 350 | Assert.assertSuccess(parser, "abcd12", [char("a"), char("b"), char("c"), char("d")], 4) 351 | Assert.assertFailure(parser, "abcde12", 4, "digit expected") 352 | Assert.assertSuccess(parser, "123", [char("1"), char("2")], 2) 353 | Assert.assertSuccess(parser, "a123", [char("a"), char("1")], 2) 354 | Assert.assertSuccess(parser, "ab123", [char("a"), char("b")], 2) 355 | Assert.assertSuccess(parser, "abc123", [char("a"), char("b"), char("c")], 3) 356 | Assert.assertSuccess(parser, "abcd123", [char("a"), char("b"), char("c"), char("d")], 4) 357 | Assert.assertFailure(parser, "abcde123", 4, "digit expected") 358 | } 359 | 360 | // @Test 361 | // public void testRepeatLazyUnbounded() { 362 | // StringBuilder builder = new StringBuilder(); 363 | // List list = new ArrayList<>(); 364 | // for (int i = 0; i < 100000; i++) { 365 | // builder.append('a'); 366 | // list.add('a'); 367 | // } 368 | // builder.append("1111"); 369 | // Parser parser = CharacterParser.word() 370 | // .repeatLazy(CharacterParser.digit(), 2, RepeatingParser.UNBOUNDED); 371 | // assertSuccess(parser, builder.toString(), list, list.size()); 372 | // } 373 | // 374 | // @Test 375 | // public void testSequence2() { 376 | // Parser parser = of('a').seq(of('b')); 377 | // assertSuccess(parser, "ab", Arrays.asList('a', 'b')); 378 | // assertFailure(parser, ""); 379 | // assertFailure(parser, "x"); 380 | // assertFailure(parser, "a", 1); 381 | // assertFailure(parser, "ax", 1); 382 | // } 383 | // 384 | // @Test 385 | // public void testSequence3() { 386 | // Parser parser = of('a').seq(of('b')).seq(of('c')); 387 | // assertSuccess(parser, "abc", Arrays.asList('a', 'b', 'c')); 388 | // assertFailure(parser, ""); 389 | // assertFailure(parser, "x"); 390 | // assertFailure(parser, "a", 1); 391 | // assertFailure(parser, "ax", 1); 392 | // assertFailure(parser, "ab", 2); 393 | // assertFailure(parser, "abx", 2); 394 | // } 395 | // 396 | 397 | func testStar() { 398 | let parser = CP.of("a").star() 399 | Assert.assertSuccess(parser, "", []) 400 | Assert.assertSuccess(parser, "a", [char("a")]) 401 | Assert.assertSuccess(parser, "aa", [char("a"), char("a")]) 402 | Assert.assertSuccess(parser, "aaa", [char("a"), char("a"), char("a")]) 403 | } 404 | 405 | func testStarGreedy() { 406 | let parser = CP.word().starGreedy(CP.digit()) 407 | Assert.assertFailure(parser, "", 0, "digit expected") 408 | Assert.assertFailure(parser, "a", 0, "digit expected") 409 | Assert.assertFailure(parser, "ab", 0, "digit expected") 410 | Assert.assertSuccess(parser, "1", Assert.EMPTY, 0) 411 | Assert.assertSuccess(parser, "a1", [char("a")], 1) 412 | Assert.assertSuccess(parser, "ab1", [char("a"), char("b")], 2) 413 | Assert.assertSuccess(parser, "abc1", [char("a"), char("b"), char("c")], 3) 414 | Assert.assertSuccess(parser, "12", [char("1")], 1) 415 | Assert.assertSuccess(parser, "a12", [char("a"), char("1")], 2) 416 | Assert.assertSuccess(parser, "ab12", [char("a"), char("b"), char("1")], 3) 417 | Assert.assertSuccess(parser, "abc12", [char("a"), char("b"), char("c"), char("1")], 4) 418 | Assert.assertSuccess(parser, "123", [char("1"), char("2")], 2) 419 | Assert.assertSuccess(parser, "a123", [char("a"), char("1"), char("2")], 3) 420 | Assert.assertSuccess(parser, "ab123", [char("a"), char("b"), char("1"), char("2")], 4) 421 | Assert.assertSuccess(parser, "abc123", [char("a"), char("b"), char("c"), char("1"), char("2")], 5) 422 | } 423 | 424 | func testStarLazy() { 425 | let parser = CP.word().starLazy(CP.digit()) 426 | Assert.assertFailure(parser, "") 427 | Assert.assertFailure(parser, "a", 1, "digit expected") 428 | Assert.assertFailure(parser, "ab", 2, "digit expected") 429 | Assert.assertSuccess(parser, "1", Assert.EMPTY, 0) 430 | Assert.assertSuccess(parser, "a1", [char("a")], 1) 431 | Assert.assertSuccess(parser, "ab1", [char("a"), char("b")], 2) 432 | Assert.assertSuccess(parser, "abc1", [char("a"), char("b"), char("c")], 3) 433 | Assert.assertSuccess(parser, "12", Assert.EMPTY, 0) 434 | Assert.assertSuccess(parser, "a12", [char("a")], 1) 435 | Assert.assertSuccess(parser, "ab12", [char("a"), char("b")], 2) 436 | Assert.assertSuccess(parser, "abc12", [char("a"), char("b"), char("c")], 3) 437 | Assert.assertSuccess(parser, "123", Assert.EMPTY, 0) 438 | Assert.assertSuccess(parser, "a123", [char("a")], 1) 439 | Assert.assertSuccess(parser, "ab123", [char("a"), char("b")], 2) 440 | Assert.assertSuccess(parser, "abc123", [char("a"), char("b"), char("c")], 3) 441 | } 442 | 443 | func testToken() { 444 | let parser = CP.of("a").star().token().trim() 445 | let token: Token = parser.parse(" aa ").get()! 446 | XCTAssertEqual(1, token.start.utf16Offset(in: " aa ")) 447 | XCTAssertEqual(3, token.stop.utf16Offset(in: " aa ")) 448 | // Assert.assertEquals([char("a"), char("a")], token.>getValue()] 449 | } 450 | 451 | func testTrim() { 452 | let parser = CP.of("a").trim() 453 | Assert.assertSuccess(parser, "a", char("a")) 454 | Assert.assertSuccess(parser, " a", char("a")) 455 | Assert.assertSuccess(parser, "a ", char("a")) 456 | Assert.assertSuccess(parser, " a ", char("a")) 457 | Assert.assertSuccess(parser, " a", char("a")) 458 | Assert.assertSuccess(parser, "a ", char("a")) 459 | Assert.assertSuccess(parser, " a ", char("a")) 460 | Assert.assertFailure(parser, "", "'a' expected") 461 | Assert.assertFailure(parser, "b", "'a' expected") 462 | Assert.assertFailure(parser, " b", 1, "'a' expected") 463 | Assert.assertFailure(parser, " b", 2, "'a' expected") 464 | } 465 | 466 | func testTrimCustom() { 467 | let parser = CP.of("a").trim(CP.of("*")) 468 | Assert.assertSuccess(parser, "a", char("a")) 469 | Assert.assertSuccess(parser, "*a", char("a")) 470 | Assert.assertSuccess(parser, "a*", char("a")) 471 | Assert.assertSuccess(parser, "*a*", char("a")) 472 | Assert.assertSuccess(parser, "**a", char("a")) 473 | Assert.assertSuccess(parser, "a**", char("a")) 474 | Assert.assertSuccess(parser, "**a**", char("a")) 475 | Assert.assertFailure(parser, "", "'a' expected") 476 | Assert.assertFailure(parser, "b", "'a' expected") 477 | Assert.assertFailure(parser, "*b", 1, "'a' expected") 478 | Assert.assertFailure(parser, "**b", 2, "'a' expected") 479 | } 480 | 481 | func testSeparatedBy() { 482 | let parser = CP.of("a").separatedBy(CP.of("b")) 483 | Assert.assertFailure(parser, "", "'a' expected") 484 | Assert.assertSuccess(parser, "a", [char("a")]) 485 | Assert.assertSuccess(parser, "ab", [char("a")], 1) 486 | Assert.assertSuccess(parser, "aba", [char("a"), char("b"), char("a")]) 487 | Assert.assertSuccess(parser, "abab", [char("a"), char("b"), char("a")], 3) 488 | Assert.assertSuccess(parser, "ababa", [char("a"), char("b"), char("a"), char("b"), char("a")]) 489 | Assert.assertSuccess(parser, "ababab", [char("a"), char("b"), char("a"), char("b"), char("a")], 5) 490 | } 491 | 492 | func testDelimitedBy() { 493 | let parser = CP.of("a").delimitedBy(CP.of("b")) 494 | Assert.assertFailure(parser, "", "'a' expected") 495 | Assert.assertSuccess(parser, "a", [char("a")]) 496 | Assert.assertSuccess(parser, "ab", [char("a"), char("b")]) 497 | Assert.assertSuccess(parser, "aba", [char("a"), char("b"), char("a")]) 498 | Assert.assertSuccess(parser, "abab", [char("a"), char("b"), char("a"), char("b")]) 499 | Assert.assertSuccess(parser, "ababa", [char("a"), char("b"), char("a"), char("b"), char("a")]) 500 | Assert.assertSuccess(parser, "ababab", 501 | [char("a"), char("b"), char("a"), char("b"), char("a"), char("b")]) 502 | } 503 | 504 | func testContinuationDelegating() { 505 | struct Continuation: ContinuationHandler { 506 | func apply(_ continuation: (Context) -> Result, _ context: Context) -> Result { 507 | return continuation(context) 508 | } 509 | } 510 | 511 | let parser = CharacterParser.digit().callCC(Continuation()) 512 | XCTAssertTrue(parser.parse("1").isSuccess()) 513 | XCTAssertFalse(parser.parse("a").isSuccess()) 514 | } 515 | 516 | func testContinuationRedirecting() { 517 | struct Continuation: ContinuationHandler { 518 | func apply(_ continuation: (Context) -> Result, _ context: Context) -> Result { 519 | return CP.letter().parseOn(context) 520 | } 521 | } 522 | 523 | let parser = CharacterParser.digit().callCC(Continuation()) 524 | XCTAssertFalse(parser.parse("1").isSuccess()) 525 | XCTAssertTrue(parser.parse("a").isSuccess()) 526 | } 527 | 528 | func testContinuationResuming() { 529 | class Continuation: ContinuationHandler { 530 | var continuations: [(Context) -> Result] = [] 531 | var contexts: [Context] = [] 532 | 533 | func apply(_ continuation: @escaping (Context) -> Result, _ context: Context) -> Result { 534 | self.continuations.append(continuation) 535 | self.contexts.append(context) 536 | 537 | // we have to return something for now 538 | return context.failure("Abort") 539 | } 540 | } 541 | let continuation = Continuation() 542 | let parser = CP.digit().callCC(continuation) 543 | 544 | // execute the parser twice to collect the continuations 545 | XCTAssertFalse(parser.parse("1").isSuccess()) 546 | XCTAssertFalse(parser.parse("a").isSuccess()) 547 | 548 | // later we can execute the captured continuations 549 | XCTAssertTrue(continuation.continuations[0](continuation.contexts[0]).isSuccess()) 550 | XCTAssertFalse(continuation.continuations[1](continuation.contexts[1]).isSuccess()) 551 | 552 | // of course the continuations can be resumed multiple times 553 | XCTAssertTrue(continuation.continuations[0](continuation.contexts[0]).isSuccess()) 554 | XCTAssertFalse(continuation.continuations[1](continuation.contexts[1]).isSuccess()) 555 | } 556 | 557 | func testContinuationSuccessful() { 558 | struct Continuation: ContinuationHandler { 559 | func apply(_ continuation: (Context) -> Result, _ context: Context) -> Result { 560 | return context.success("Always succeed") 561 | } 562 | } 563 | 564 | let parser = CharacterParser.digit() 565 | .callCC(Continuation()) 566 | 567 | XCTAssertTrue(parser.parse("1").isSuccess()) 568 | XCTAssertTrue(parser.parse("a").isSuccess()) 569 | } 570 | 571 | func testContinuationFailing() { 572 | struct Continuation: ContinuationHandler { 573 | func apply(_ continuation: (Context) -> Result, _ context: Context) -> Result { 574 | return context.failure("Always fail") 575 | } 576 | } 577 | 578 | let parser = CharacterParser.digit() 579 | .callCC(Continuation()) 580 | 581 | XCTAssertFalse(parser.parse("1").isSuccess()) 582 | XCTAssertFalse(parser.parse("a").isSuccess()) 583 | } 584 | 585 | func testSequence() { 586 | let parser = SequenceParser(CP.digit(), CP.word()) 587 | XCTAssertTrue(parser.parse("1a").isSuccess()) 588 | XCTAssertFalse(parser.parse("a1").isSuccess()) 589 | } 590 | } 591 | --------------------------------------------------------------------------------