├── .gitignore ├── .swift-version ├── .travis.yml ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── Sparse │ ├── CSVParser.swift │ ├── CharacterParsers.swift │ ├── ContextDescribableError.swift │ ├── ContextualizedError.swift │ ├── DotStringsEntry.swift │ ├── DotStringsParser.swift │ ├── IgnoreError.swift │ ├── InfiniteLoopError.swift │ ├── Parser.swift │ ├── ParserError.swift │ ├── Parser_combinators.swift │ ├── Parser_creators.swift │ ├── Parser_name.swift │ ├── Parser_run.swift │ ├── ParsingContext.swift │ ├── PositionedInput.swift │ ├── Stream.swift │ ├── StringParsers.swift │ ├── Transforms.swift │ └── UnexpectedInputError.swift └── SparseTests │ ├── CSV │ ├── CSVExample.swift │ ├── CSVPerformance.swift │ └── CSVTests.swift │ ├── Core │ ├── CharacterParsersTests.swift │ ├── ParserCombinatorsTests.swift │ ├── ParserErrorTests.swift │ └── Quick+Throw.swift │ └── DotStrings │ ├── DotStringsExample.swift │ ├── DotStringsParserPerformance.swift │ └── DotStringsParserTests.swift └── Sparse.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | ### SwiftPM ### 26 | Packages 27 | .build/ 28 | xcuserdata 29 | DerivedData/ 30 | *.xcodeproj 31 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -workspace Example/Sparse.xcworkspace -scheme Sparse-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 johnmorgan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CwlCatchException", 6 | "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "7cd2f8cacc4d22f21bc0b2309c3b18acf7957b66", 10 | "version": "1.2.0" 11 | } 12 | }, 13 | { 14 | "package": "CwlPreconditionTesting", 15 | "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "c228db5d2ad1b01ebc84435e823e6cca4e3db98b", 19 | "version": "1.2.0" 20 | } 21 | }, 22 | { 23 | "package": "Nimble", 24 | "repositoryURL": "https://github.com/Quick/Nimble.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "6abeb3f5c03beba2b9e4dbe20886e773b5b629b6", 28 | "version": "8.0.4" 29 | } 30 | }, 31 | { 32 | "package": "Quick", 33 | "repositoryURL": "https://github.com/Quick/Quick.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "33682c2f6230c60614861dfc61df267e11a1602f", 37 | "version": "2.2.0" 38 | } 39 | } 40 | ] 41 | }, 42 | "version": 1 43 | } 44 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Sparse", 7 | products: [ 8 | .library(name: "Sparse", targets: ["Sparse"]), 9 | ], 10 | dependencies: [ 11 | // Test dependencies 12 | .package(url: "https://github.com/Quick/Quick.git", from: "2.2.0"), 13 | .package(url: "https://github.com/Quick/Nimble.git", from: "8.0.4") 14 | ], 15 | targets: [ 16 | .target( 17 | name: "Sparse", 18 | dependencies: []), 19 | .testTarget( 20 | name: "SparseTests", 21 | dependencies: ["Sparse", "Quick", "Nimble"]), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ✂️ Sparse ✂️ 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/Sparse.svg?style=flat)](http://cocoapods.org/pods/Sparse) 4 | [![License](https://img.shields.io/cocoapods/l/Sparse.svg?style=flat)](http://cocoapods.org/pods/Sparse) 5 | ![Swift](https://img.shields.io/badge/Swift-5.1-orange.svg) 6 | 7 | Sparse is a simple parsing library, written in Swift. It is based on the parser-combinator approach used by Haskell's [Parsec](https://github.com/aslatter/parsec). Its focus is on natural language parser creation and descriptive error messages. 8 | 9 | ## Example 10 | 11 | Here is a CSV parser: 12 | 13 | ```swift 14 | let quote = character("\"") 15 | 16 | let illegalCellCharacters = CharacterSet.newlines.union(CharacterSet(charactersIn: ",")) 17 | let unquotedCellCharacter = characterNot(in: illegalCellCharacters) 18 | .named("cell character") 19 | let unquotedCell = many(unquotedCellCharacter).asString() 20 | .map { $0.trimmingCharacters(in: .whitespaces) } 21 | 22 | let escapedQuote = quote.skipThen(quote) 23 | .named("escaped quote") 24 | let quotedCellCharacter = characterNot("\"") 25 | .named("quoted cell character") 26 | .otherwise(escapedQuote) 27 | 28 | let quotedCell = many(quotedCellCharacter, boundedBy: quote).asString() 29 | 30 | let cell = quotedCell.otherwise(unquotedCell) 31 | 32 | let cellSeparator = whitespaces().then(character(",")).then(whitespaces()) 33 | .named("cell separator") 34 | let line = many(cell, separator: cellSeparator) 35 | 36 | let ending = optional(newline()).then(end()) 37 | let csv = many(line, separator: newline()).thenSkip(ending) 38 | 39 | public func parseCSV(input: String) throws -> [[String]] { 40 | return try csv.parse(input) 41 | } 42 | ``` 43 | 44 | Naming the component parsers allows for more descriptive error messages, e.g.: 45 | 46 | Line 8, Column 12 47 | r8c1 , "r8"c2" , r8c3 ,r8c4,r8c5,r8c6,r8c7 , r8c8 48 | ~~~~~~~~~~~^ 49 | Expected: '"' in escaped quote 50 | Expected: whitespace in cell separator 51 | Expected: ',' in cell separator 52 | Expected: newline 53 | Expected: EOF: file 54 | 55 | ## Installation 56 | 57 | ### Swift Package Manager 58 | 59 | Add Sparse as a dependency in Package.swift: 60 | 61 | ```swift 62 | .package(url: "https://github.com/johnpatrickmorgan/sparse.git", from: "0.3.0"), 63 | ``` 64 | 65 | ### CocoaPods 66 | 67 | Add the following line to your Podfile: 68 | 69 | ```ruby 70 | pod "Sparse" 71 | ``` 72 | 73 | ## Credit 74 | 75 | Sparse is based on Haskell's [Parsec](https://github.com/aslatter/parsec). 76 | 77 | ## License 78 | 79 | Sparse is available under the MIT license. See the LICENSE file for more info. 80 | -------------------------------------------------------------------------------- /Sources/Sparse/CSVParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CSVParser.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 14/12/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public enum CSVParser { 12 | 13 | static let quote = character("\"") 14 | 15 | static let illegalCellCharacters = CharacterSet.newlines.union(CharacterSet(charactersIn: ",")) 16 | static let unquotedCellCharacter = characterNot(in: illegalCellCharacters) 17 | .named("cell character") 18 | static let unquotedCell = many(unquotedCellCharacter).asString() 19 | .map { $0.trimmingCharacters(in: .whitespaces) } 20 | 21 | static let escapedQuote = quote.skipThen(quote) 22 | .named("escaped quote") 23 | static let quotedCellCharacter = characterNot("\"") 24 | .named("quoted cell character") 25 | .otherwise(escapedQuote) 26 | 27 | static let quotedCell = many(quotedCellCharacter, boundedBy: quote).asString() 28 | 29 | static let cell = quotedCell.otherwise(unquotedCell) 30 | 31 | static let cellSeparator = whitespaces().then(character(",")).then(whitespaces()) 32 | .named("cell separator") 33 | static let line = many(cell, separator: cellSeparator) 34 | 35 | static let ending = optional(newline()).then(end()) 36 | static let csv = many(line, separator: newline()).thenSkip(ending) 37 | 38 | public static func parseCSV(input: String) throws -> [[String]] { 39 | return try csv.parse(input) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Sparse/CharacterParsers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacterParsers.swift 3 | // StringsToCSV 4 | // 5 | // Created by John Morgan on 02/12/2016. 6 | // Copyright © 2016 John Morgan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Character { 12 | 13 | func unicodeScalar() -> UnicodeScalar { 14 | 15 | let characterString = String(self) 16 | let scalars = characterString.unicodeScalars 17 | 18 | return scalars[scalars.startIndex] 19 | } 20 | } 21 | 22 | public func character(condition: @escaping (Character) -> Bool) -> Parser { 23 | 24 | return Parser { stream in 25 | 26 | guard let char = stream.peekNext(), condition(char) else { 27 | 28 | throw UnexpectedInputError() 29 | } 30 | stream.consumeNext() 31 | 32 | return char 33 | } 34 | } 35 | 36 | public func anyCharacter() -> Parser { 37 | 38 | return character(condition: { _ in true }).named("any character") 39 | } 40 | 41 | public func character(in set: CharacterSet) -> Parser { 42 | 43 | return character(condition: { set.contains($0.unicodeScalar()) }) 44 | } 45 | 46 | public func character(in string: String) -> Parser { 47 | 48 | return character(condition: { string.contains($0) }) 49 | } 50 | 51 | public func character(_ char: Character) -> Parser { 52 | 53 | return character(condition: { $0 == char }).named("'\(char)'") 54 | } 55 | 56 | public func characterNot(in set: CharacterSet) -> Parser { 57 | 58 | return character(condition: { !set.contains($0.unicodeScalar()) }) 59 | } 60 | 61 | public func characterNot(in string: String) -> Parser { 62 | 63 | return character(condition: { !string.contains($0) }) 64 | } 65 | 66 | public func characterNot(_ char: Character) -> Parser { 67 | 68 | return character(condition: { $0 != char }).named("not \(String(char))") 69 | } 70 | 71 | public func characterNot(condition: @escaping (Character) -> Bool) -> Parser { 72 | 73 | return character(condition: { !condition($0) }) 74 | } 75 | 76 | public func whitespace() -> Parser { 77 | 78 | return character(condition: { $0.isWhitespace }).named("whitespace") 79 | } 80 | 81 | public func whitespaces() -> Parser<[Character]> { 82 | 83 | return many(whitespace()) 84 | } 85 | 86 | public func whitespaceOrNewline() -> Parser { 87 | 88 | return character(condition: { $0.isWhitespace || $0.isNewline }).named("whitespace or newline") 89 | } 90 | 91 | public func whitespacesOrNewlines() -> Parser<[Character]> { 92 | 93 | return many(whitespaceOrNewline()) 94 | } 95 | 96 | public func newline() -> Parser { 97 | 98 | return character(condition: { $0.isNewline }).named("newline") 99 | } 100 | 101 | public func newlines() -> Parser<[Character]> { 102 | 103 | return many(newline()) 104 | } 105 | -------------------------------------------------------------------------------- /Sources/Sparse/ContextDescribableError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextualizedError.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 02/11/2018. 6 | // 7 | 8 | import Foundation 9 | 10 | /// An error that can provide a ParsingContext-aware description. 11 | public protocol ContextDescribableError: Error { 12 | 13 | func description(in context: () -> ParsingContext) -> String 14 | } 15 | 16 | public extension ContextDescribableError where Self: CustomStringConvertible { 17 | 18 | func description(in context: () -> ParsingContext) -> String { 19 | return description 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Sparse/ContextualizedError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextualizedError.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 02/11/2018. 6 | // 7 | 8 | import Foundation 9 | 10 | /// An error decorated with a lazily evaluated parsing context. 11 | public struct ContextualizedError: CustomStringConvertible { 12 | 13 | let context: () -> ParsingContext 14 | let error: Error 15 | 16 | public var description: String { 17 | return (error as? ContextDescribableError)?.description(in: context) ?? error.localizedDescription 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Sparse/DotStringsEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringsEntry.swift 3 | // StringsToCSV 4 | // 5 | // Created by John Morgan on 02/12/2016. 6 | // Copyright © 2016 John Morgan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct DotStringsEntry { 12 | 13 | public var comments: [String] 14 | public var key: String 15 | public var translation: String 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Sparse/DotStringsParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringsParser.swift 3 | // StringsToCSV 4 | // 5 | // Created by John Morgan on 02/12/2016. 6 | // Copyright © 2016 John Morgan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum DotStringsParser { 12 | 13 | // Primitives 14 | static let wss = whitespaces() 15 | static let wsnl = whitespaceOrNewline() 16 | static let wsnls = whitespacesOrNewlines() 17 | static let wss1nl = wss.then(atMost(1, nl)) 18 | static let nl = newline() 19 | static let qm = character("\"") 20 | static let sc = character(";") 21 | 22 | // Escapes 23 | static let escapableCharacter = characterNot("\\").otherwise(escapedSpecial) 24 | static let escapedSpecial: Parser = character("\\").skipThen(oneOf([ 25 | character("\"").skipThen(pure("\"")), 26 | character("'").skipThen(pure("'")), 27 | character("n").skipThen(pure("\n")), 28 | character("r").skipThen(pure("\r")), 29 | character("0").skipThen(pure("\0")), 30 | character("\\").skipThen(pure("\\")), 31 | character("t").skipThen(pure("\t")) 32 | ])).named("escaped character") 33 | 34 | // Comments 35 | static let ilcStart = atLeast(2, character("/")).thenSkip(wss) 36 | static let ilcStop = nl 37 | 38 | static let mlcStart = string("/*").thenSkip(many(character("*"))) 39 | static let mlcStop = string("*/") 40 | 41 | static let mlc = many(anyCharacter().butNot(mlcStop), prefix: mlcStart, suffix: mlcStop).asString() 42 | static let ilc = many(anyCharacter().butNot(ilcStop), prefix: ilcStart, suffix: ilcStop).asString() 43 | 44 | static let comment = mlc.otherwise(ilc).thenSkip(wss1nl).named("comment") 45 | static let comments = many(comment) 46 | static let inlineComment = ilc.thenSkip(wss1nl).named("in-line comment") 47 | 48 | static let orphanedComment = atLeastOne(comment).then(wss1nl) 49 | 50 | // Key-value pairs 51 | static let text = many(escapableCharacter.butNot(qm), boundedBy: qm).asString().named("text") 52 | static let key = text.named("key") 53 | static let translation = text.named("translation") 54 | static let separator = wss.skipThen(character("=")).thenSkip(wss).named("separator") 55 | static let kvp = key.thenSkip(separator).then(translation).thenSkip(sc).named("pair") 56 | 57 | // Entries 58 | static let entryTuple = comments.then(kvp).thenSkip(wss).then(optional(inlineComment)).thenSkip(wsnls).map(flatten) 59 | static let entryObject = entryTuple.map(createEntry).named("entry") 60 | static let entries = many(entryObject.otherwiseSkip(orphanedComment)).map { $0.compactMap{$0} } 61 | 62 | // Strings Parser 63 | static let stringsParser = wsnls.skipThen(entries).thenSkip(end()) 64 | 65 | public static func parseStrings(input: String) throws -> [DotStringsEntry] { 66 | 67 | let stream = Stream(input) 68 | let parsed = try stringsParser.parse(stream) 69 | return parsed 70 | } 71 | 72 | static func createEntry(preComments: [String], pair: (String, String), postComment: String?) -> DotStringsEntry { 73 | 74 | var comments = preComments.map { pc in 75 | return String(pc).components(separatedBy: .newlines).map { 76 | $0.trimmingCharacters(in: .whitespaces) 77 | }.joined(separator: "\n") 78 | } 79 | 80 | if let postComment = postComment { comments.append(postComment) } 81 | comments = comments.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } 82 | let (key, translation) = pair 83 | return DotStringsEntry(comments: comments, key: key, translation: translation) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/Sparse/IgnoreError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextualizedError.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 02/11/2018. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Replaces an error that has already been captured in the stream, to avoid 11 | /// capturing the same error at every level of the parser tree. 12 | class IgnoreError: Error { } 13 | 14 | let ignoreError = IgnoreError() 15 | -------------------------------------------------------------------------------- /Sources/Sparse/InfiniteLoopError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfiniteLoopError.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 05/01/2017. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// An error signifying that the parser is stuck in an infinite loop. 12 | public struct InfiniteLoopError: ContextDescribableError { 13 | 14 | public func description(in context: () -> ParsingContext) -> String { 15 | let contextDescription = context().description 16 | guard !contextDescription.isEmpty else { 17 | return "Parser stuck in infinite loop" 18 | } 19 | return "Parser stuck in infinite loop parsing: \(context().description)" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Sparse/Parser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser.swift 3 | // StringsToCSV 4 | // 5 | // Created by John Morgan on 02/12/2016. 6 | // Copyright © 2016 John Morgan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Parser { 12 | 13 | public let name: String? 14 | 15 | let __parse: (Stream) throws -> Output 16 | 17 | public init(_ name: String? = nil, parse: @escaping (Stream) throws -> Output) { 18 | 19 | self.name = name 20 | self.__parse = parse 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Sparse/ParserError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContextualizedError.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 02/11/2018. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Capture a number of contextualized errors thrown at a particular index in an input stream. 11 | public struct ParserError: Error, PositionedInput, CustomStringConvertible { 12 | 13 | public let input: String 14 | public var index: String.Index 15 | var errors: [ContextualizedError] 16 | 17 | mutating func incorporate(error: ContextualizedError, at index: String.Index) { 18 | if index > self.index { 19 | self.index = index 20 | self.errors = [error] 21 | } else if index == self.index { 22 | errors.append(error) 23 | } 24 | } 25 | 26 | public var description: String { 27 | return [ 28 | [positionDescription], 29 | errors.map { $0.description }.removingDuplicates() 30 | ].flatMap { $0 }.joined(separator: "\n") 31 | } 32 | } 33 | 34 | extension ParserError { 35 | 36 | init(input: String) { 37 | self.init( 38 | input: input, 39 | index: input.startIndex, 40 | errors: [] 41 | ) 42 | } 43 | } 44 | 45 | private extension Collection where Element: Hashable { 46 | 47 | func removingDuplicates() -> [Element] { 48 | var seen: Set = [] 49 | var uniques: [Element] = [] 50 | for element in self { 51 | guard !seen.contains(element) else { continue } 52 | seen.insert(element) 53 | uniques.append(element) 54 | } 55 | return uniques 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Sparse/Parser_combinators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser_combinations.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 07/12/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Parser { 12 | 13 | func map(_ transform: @escaping (Output) throws -> T) -> Parser { 14 | 15 | return Parser { stream in 16 | 17 | let result = try self._run(stream) 18 | return try transform(result) 19 | } 20 | } 21 | 22 | func then(_ other: Parser) -> Parser<(Output,T)> { 23 | 24 | return Parser<(Output,T)> { stream in 25 | 26 | let first = try self._runOrRestoreState(stream) 27 | let second = try other._runOrRestoreState(stream) 28 | return (first, second) 29 | } 30 | } 31 | 32 | func skipThen(_ other: Parser) -> Parser { 33 | 34 | return self.then(other).map { $0.1 } 35 | } 36 | 37 | func thenSkip(_ other: Parser) -> Parser { 38 | 39 | return self.then(other).map { $0.0 } 40 | } 41 | 42 | func otherwise(_ other: Parser) -> Parser { 43 | 44 | return Parser { stream in 45 | 46 | let first = try? self._runOrRestoreState(stream) 47 | return try first ?? (other._runOrRestoreState(stream)) 48 | } 49 | } 50 | 51 | func otherwiseSkip(_ other: Parser) -> Parser { 52 | 53 | return self.map { Optional.some($0) }.otherwise(other.map { _ in return nil }) 54 | } 55 | 56 | func and(_ other: Parser) -> Parser { 57 | 58 | return Parser { stream in 59 | 60 | let _ = try other._runThenRestoreState(stream) 61 | return try self._run(stream) 62 | } 63 | } 64 | 65 | func butNot(_ other: Parser) -> Parser { 66 | 67 | return Parser { stream in 68 | guard (try? other._runThenRestoreState(stream)) == nil else { 69 | throw UnexpectedInputError() 70 | } 71 | return try self._run(stream) 72 | } 73 | } 74 | 75 | func withoutConsuming() -> Parser { 76 | 77 | return Parser { stream in 78 | 79 | return try self._runThenRestoreState(stream) 80 | } 81 | } 82 | 83 | func skip() -> Parser { 84 | 85 | return Parser { stream in 86 | 87 | let _ = try self._run(stream) 88 | return () 89 | } 90 | } 91 | 92 | func surrounded(by parser: Parser) -> Parser<(T, Output, T)> { 93 | 94 | return parser.then(self).then(parser).map(flatten) 95 | } 96 | 97 | func surrounded(bySkipped parser: Parser) -> Parser { 98 | 99 | return parser.skipThen(self).thenSkip(parser) 100 | } 101 | 102 | func skipping(prefix: Parser, suffix: Parser) -> Parser { 103 | 104 | return prefix.skipThen(self).thenSkip(suffix) 105 | } 106 | 107 | func skipping(prefix: Parser) -> Parser { 108 | 109 | return prefix.skipThen(self) 110 | } 111 | 112 | func skipping(suffix: Parser) -> Parser { 113 | 114 | return self.thenSkip(suffix) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Sources/Sparse/Parser_creators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser_creators.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 07/12/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public func pure(_ result: T) -> Parser { 12 | 13 | return Parser { stream in return result } 14 | } 15 | 16 | public func pureError(_ error: Error) -> Parser { 17 | 18 | return Parser { _ in 19 | throw error 20 | } 21 | } 22 | 23 | public func pureError() -> Parser { 24 | 25 | return pureError(UnexpectedInputError()) 26 | } 27 | 28 | public func end() -> Parser { 29 | 30 | return Parser { stream in 31 | guard stream.peekNext() == nil else { 32 | throw UnexpectedInputError() 33 | } 34 | return () 35 | }.named("EOF") 36 | } 37 | 38 | public func many(_ parser: Parser) -> Parser<[T]> { 39 | 40 | return Parser<[T]> { stream in 41 | 42 | var result: [T] = [] 43 | 44 | while true { 45 | 46 | let initialState = stream.state 47 | guard let element = try? parser._runOrRestoreState(stream) else { return result } 48 | guard stream.state != initialState else { throw InfiniteLoopError() } 49 | result.append(element) 50 | } 51 | } 52 | } 53 | 54 | public func many(_ parser: Parser, separator: Parser) -> Parser<[T]> { 55 | 56 | return many(parser.thenSkip(separator)).then(parser).map { $0 + [$1] } 57 | } 58 | 59 | public func many(_ parser: Parser, prefix: Parser, suffix: Parser) -> Parser<[T]> { 60 | 61 | return many(parser, prefix: prefix).thenSkip(suffix) 62 | } 63 | 64 | public func many(_ parser: Parser, prefix: Parser) -> Parser<[T]> { 65 | 66 | return prefix.skipThen(many(parser)) 67 | } 68 | 69 | public func many(_ parser: Parser, suffix: Parser) -> Parser<[T]> { 70 | 71 | return many(parser).thenSkip(suffix) 72 | } 73 | 74 | public func many(_ parser: Parser, boundedBy bound: Parser) -> Parser<[T]> { 75 | 76 | return many(parser, prefix: bound, suffix: bound) 77 | } 78 | 79 | public func many(_ parser: Parser, until end: Parser) -> Parser<[T]> { 80 | 81 | return many(parser.butNot(end)) 82 | } 83 | 84 | public func many(_ parser: Parser, untilSkipping end: Parser) -> Parser<[T]> { 85 | 86 | return many(parser.butNot(end)).thenSkip(end) 87 | } 88 | 89 | public func many(_ parser: Parser, while check: Parser) -> Parser<[T]> { 90 | 91 | return many(parser.and(check)) 92 | } 93 | 94 | public func `optional`(_ parser: Parser) -> Parser { 95 | 96 | return parser.map { .some($0) }.otherwise(pure(nil)) 97 | } 98 | 99 | public func exactly(_ n: Int, _ parser: Parser) -> Parser<[T]> { 100 | 101 | precondition(n >= 0) 102 | 103 | var acc = pure([T]()) 104 | for _ in 0..(_ n: Int, _ parser: Parser) -> Parser<[T]> { 111 | 112 | precondition(n >= 0) 113 | 114 | return Parser<[T]> { stream in 115 | 116 | var result = [T]() 117 | 118 | // TODO: refactor many and atMost into one? 119 | while result.count < n { 120 | 121 | let initialState = stream.state 122 | guard let element = try? parser._runOrRestoreState(stream) else { return result } 123 | guard stream.state != initialState else { throw InfiniteLoopError() } 124 | result.append(element) 125 | } 126 | return result 127 | } 128 | } 129 | 130 | public func atMostOne(_ parser: Parser) -> Parser<[T]> { 131 | 132 | return atMost(1, parser) 133 | } 134 | 135 | public func atLeastOne(_ parser: Parser) -> Parser<[T]> { 136 | 137 | return atLeast(1, parser) 138 | } 139 | 140 | public func atLeast(_ n: Int, _ parser: Parser) -> Parser<[T]> { 141 | 142 | precondition(n >= 0) 143 | 144 | return exactly(n, parser).then(many(parser)).map { $0 + $1 } 145 | } 146 | 147 | public func oneOf(_ parsers: [Parser]) -> Parser { 148 | 149 | precondition(parsers.count > 0) 150 | 151 | return parsers.dropFirst().reduce(parsers[0]) { acc, parser in 152 | return acc.otherwise(parser) 153 | } 154 | } 155 | 156 | public func unwrap(_ parser: Parser, orThrow error: Error) -> Parser { 157 | 158 | return parser.map { 159 | guard let parsed = $0 else { throw error } 160 | return parsed 161 | } 162 | } 163 | 164 | public func unwrap(_ parser: Parser, orPure value: T) -> Parser { 165 | 166 | return parser.map { 167 | guard let parsed = $0 else { return value } 168 | return parsed 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Sources/Sparse/Parser_name.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser+name.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 07/12/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Parser { 12 | 13 | func named(_ name: String?) -> Parser { 14 | 15 | return Parser(name, parse: self.__parse) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Sparse/Parser_run.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Parser+run.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 07/12/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Parser { 12 | 13 | func parse(_ string: String) throws -> Output { 14 | let stream = Stream(string) 15 | return try parse(stream) 16 | } 17 | 18 | func parse(_ input: Stream) throws -> Output { 19 | do { 20 | return try _run(input) 21 | } catch { 22 | if input.error.errors.count > 0 { 23 | throw input.error 24 | } 25 | throw error 26 | } 27 | } 28 | 29 | func _run(_ input: Stream, restoreStateOnSuccess: Bool = false, restoreStateOnFailure: Bool = false) throws -> Output { 30 | let initialContext = input.context 31 | let initialState = input.state 32 | defer { input.context = initialContext } 33 | do { 34 | if let name = name { 35 | input.context = { return initialContext().appending(name: name) } 36 | } 37 | let output = try __parse(input) 38 | if restoreStateOnSuccess { 39 | input.state = initialState 40 | } 41 | return output 42 | } catch { 43 | input.incorporate(error: error) 44 | if restoreStateOnFailure { 45 | input.state = initialState 46 | } 47 | throw ignoreError 48 | } 49 | } 50 | 51 | func _runOrRestoreState(_ input: Stream) throws -> Output { 52 | return try _run(input, restoreStateOnSuccess: false, restoreStateOnFailure: true) 53 | } 54 | 55 | func _runThenRestoreState(_ input: Stream) throws -> Output { 56 | return try _run(input, restoreStateOnSuccess: true, restoreStateOnFailure: true) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Sparse/ParsingContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParsingContext.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 05/01/2017. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// Captures a hierarchy of named parsers. 12 | public struct ParsingContext: CustomStringConvertible { 13 | 14 | public let parserNames: [String] 15 | 16 | init() { 17 | 18 | self.parserNames = [] 19 | } 20 | 21 | init(parserNames: [String]) { 22 | 23 | self.parserNames = parserNames 24 | } 25 | 26 | public var description: String { 27 | 28 | guard let last = parserNames.last else { return "" } 29 | 30 | let ancestors = parserNames.dropLast() 31 | 32 | guard ancestors.count > 0 else { return last } 33 | 34 | return "\(last) in \(ancestors.joined(separator: "."))" 35 | } 36 | 37 | public func appending(name: String) -> ParsingContext { 38 | 39 | return ParsingContext(parserNames: parserNames + [name]) 40 | } 41 | 42 | var name: String? { 43 | return parserNames.last 44 | } 45 | 46 | var parent: String? { 47 | return parserNames.dropLast().last 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Sparse/PositionedInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParsingContext.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 05/12/2016. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// Conforming types capture a string and a position within that string. 12 | public protocol PositionedInput { 13 | 14 | var input: String { get } 15 | var index: String.Index { get } 16 | } 17 | 18 | extension PositionedInput { 19 | 20 | public var positionDescription: String { 21 | 22 | let parsed = input[input.startIndex.. Character? { 14 | 15 | guard index < endIndex else { return nil } 16 | guard index >= startIndex else { return nil } 17 | 18 | return self[index] 19 | } 20 | } 21 | 22 | /// Represents an input stream for parsing, capturing its changing state as it is consumed. 23 | public class Stream { 24 | 25 | public let characters: String 26 | public var index: String.Index 27 | public var context: () -> ParsingContext = { return ParsingContext() } 28 | public var error: ParserError 29 | 30 | public init(_ characters: String, index: String.Index) { 31 | 32 | self.characters = characters 33 | self.index = index 34 | self.error = ParserError(input: characters) 35 | } 36 | 37 | public convenience init(_ characters: String) { 38 | 39 | self.init(characters, index: characters.startIndex) 40 | } 41 | 42 | @discardableResult public func consumeNext() -> Character? { 43 | 44 | guard let char = characters[safe: index] else { return nil } 45 | 46 | index = characters.index(after: index) 47 | 48 | return char 49 | } 50 | 51 | public func peekNext() -> Character? { 52 | 53 | return characters[safe: index] 54 | } 55 | 56 | public func peekPrevious() -> Character? { 57 | 58 | let previousIndex = characters.index(before: index) 59 | 60 | return characters[safe: previousIndex] 61 | } 62 | 63 | public var parsed: Substring { 64 | 65 | return characters.prefix(upTo: index) 66 | } 67 | 68 | public var remainder: Substring { 69 | return characters.suffix(from: index) 70 | } 71 | 72 | public var isAtEnd: Bool { 73 | return index >= characters.endIndex 74 | } 75 | 76 | func incorporate(error: Error) { 77 | if error is IgnoreError { return } 78 | let contextualizedError = ContextualizedError(context: context, error: error) 79 | self.error.incorporate(error: contextualizedError, at: self.index) 80 | } 81 | } 82 | 83 | extension Stream { 84 | 85 | var state: String.Index { 86 | get { return index } 87 | set { index = newValue } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/Sparse/StringParsers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringParsers.swift 3 | // StringsToCSV 4 | // 5 | // Created by John Morgan on 02/12/2016. 6 | // Copyright © 2016 John Morgan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func string(_ string: String) -> Parser { 12 | 13 | return Parser { stream in 14 | 15 | let initialContext = stream.context 16 | for expectedChar in string { 17 | guard let char = stream.peekNext(), char == expectedChar else { 18 | stream.context = { return initialContext().appending(name: "'\(expectedChar)'") } 19 | throw UnexpectedInputError() 20 | } 21 | stream.consumeNext() 22 | } 23 | return string 24 | }.named(string) 25 | } 26 | 27 | public func string(of character: Parser) -> Parser { 28 | 29 | return many(character).asString() 30 | } 31 | 32 | public func anyString() -> Parser { 33 | 34 | return string(of: anyCharacter()) 35 | } 36 | 37 | extension Parser: ExpressibleByStringLiteral, ExpressibleByUnicodeScalarLiteral, ExpressibleByExtendedGraphemeClusterLiteral where Output == String { 38 | 39 | public typealias StringLiteralType = String 40 | 41 | public typealias ExtendedGraphemeClusterLiteralType = String 42 | 43 | public typealias UnicodeScalarLiteralType = String 44 | 45 | public init(extendedGraphemeClusterLiteral value: String) { 46 | self.init(stringLiteral: value) 47 | } 48 | 49 | public init(unicodeScalarLiteral value: String) { 50 | self.init(stringLiteral: value) 51 | } 52 | 53 | public init(stringLiteral value: String) { 54 | 55 | self = string(value) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Sparse/Transforms.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transforms.swift 3 | // StringsToCSV 4 | // 5 | // Created by John Morgan on 02/12/2016. 6 | // Copyright © 2016 John Morgan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Parser where Output: Collection, Output.Iterator.Element == Character { 12 | 13 | public func asString() -> Parser { 14 | 15 | return self.map { return String($0) } 16 | } 17 | } 18 | 19 | public func flatten(_ tuple: ((A,B),C)) -> (A,B,C) { 20 | 21 | return (tuple.0.0, tuple.0.1, tuple.1) 22 | } 23 | 24 | public func flatten(_ tuple: (A,(B,C))) -> (A,B,C) { 25 | 26 | return (tuple.0, tuple.1.0, tuple.1.1) 27 | } 28 | 29 | public func flatten(_ tuple: (((A,B),C),D)) -> (A,B,C,D) { 30 | 31 | return (tuple.0.0.0, tuple.0.0.1, tuple.0.1, tuple.1) 32 | } 33 | 34 | public func flatten(_ tuple: ((A,B),(C,D))) -> (A,B,C,D) { 35 | 36 | return (tuple.0.0, tuple.0.1, tuple.1.0, tuple.1.1) 37 | } 38 | 39 | public func flatten(_ tuple: ((((A,B),C),D),E)) -> (A,B,C,D,E) { 40 | 41 | return (tuple.0.0.0.0, tuple.0.0.0.1, tuple.0.0.1, tuple.0.1, tuple.1) 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Sparse/UnexpectedInputError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnexpectedInputError.swift 3 | // Pods 4 | // 5 | // Created by John Morgan on 05/01/2017. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// An error signifying that the parser found unexpected input. 12 | public struct UnexpectedInputError: ContextDescribableError { 13 | 14 | public func description(in context: () -> ParsingContext) -> String { 15 | return "Expected: \(context())" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/SparseTests/CSV/CSVExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | let csvExample = """ 6 | r1c1,r1c2,r1c3,r1c4,r1c5,r1c6,r1c7,"r1c8" 7 | r2c1,r2c2,r2c3,r2c4,r2c5,r2c6,"r2c7",r2c8 8 | r3c1,r3c2,r3c3 ,r3c4,r3c5,"r3c6",r3c7,r3c8 9 | r4c1,r4c2 ,r4c3,r4c4,r4c5 ,r4c6,r4c7,r4c8 10 | r5c1, "r5c2" ,r5c3,r5c4 ,r5c5,r5c6,r5c7,r5c8 11 | r6c1,r6c2,r6c3 , r6c4,r6c5,r6c6,r6c7,r6c8 12 | r7c1,r7c2,r7c3,"r7c4",r7c5,r7c6,r7c7,r7c8 13 | r8c1 , "r8c2" , r8c3 ,r8c4,r8c5,r8c6,r8c7 , r8c8 14 | """ 15 | -------------------------------------------------------------------------------- /Sources/SparseTests/CSV/CSVPerformance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CSVPerformance.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 15/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Sparse 11 | 12 | class CSVParserPerformance: XCTestCase { 13 | 14 | func testCSVParserPerformance() { 15 | 16 | let parser = CSVParser.csv 17 | let input = csvExample 18 | let stream = Stream(input) 19 | self.measure { 20 | let _ = try! parser.parse(stream) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/SparseTests/CSV/CSVTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CSVTests.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 14/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import Sparse 11 | import Quick 12 | import Nimble 13 | 14 | class CSVSpec: QuickSpec { 15 | 16 | override func spec() { 17 | 18 | describe("the cell parser") { 19 | 20 | let parser = CSVParser.cell 21 | 22 | it("should parse an unquoted cell as expected") { 23 | let input = "a simple cell" 24 | let stream = Stream(input) 25 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 26 | expect(output).to(equal("a simple cell")) 27 | } 28 | } 29 | it("should parse a quoted cell as expected") { 30 | let input = "\"a simple cell\"" 31 | let stream = Stream(input) 32 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 33 | expect(output).to(equal("a simple cell")) 34 | } 35 | } 36 | it("should parse a quoted cell with escapes as expected") { 37 | let input = "\"a \"\"simple\"\" cell\"" 38 | let stream = Stream(input) 39 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 40 | expect(output).to(equal("a \"simple\" cell")) 41 | } 42 | } 43 | } 44 | describe("the line parser") { 45 | 46 | let parser = CSVParser.line 47 | 48 | it("should parse unquoted cells as expected") { 49 | let input = "one cell,another" 50 | let stream = Stream(input) 51 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 52 | expect(output).to(equal(["one cell","another"])) 53 | } 54 | } 55 | it("should parse quoted cells as expected") { 56 | let input = "\"one cell\",\"another\"" 57 | let stream = Stream(input) 58 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 59 | expect(output).to(equal(["one cell","another"])) 60 | } 61 | } 62 | it("should quoted cells with escapes as expected") { 63 | let input = "\"one \"\"cell\"\"\",\"\"\"and\"\" another\"" 64 | let stream = Stream(input) 65 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 66 | expect(output).to(equal(["one \"cell\"","\"and\" another"])) 67 | } 68 | } 69 | } 70 | 71 | describe("the csv parser") { 72 | 73 | let parser = CSVParser.csv 74 | 75 | it("should parse the test file as expected") { 76 | let input = csvExample 77 | let stream = Stream(input) 78 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 79 | for r in 1...8 { 80 | for c in 1...8 { 81 | expect(output[r-1][c-1]).to(equal("r\(r)c\(c)")) 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/SparseTests/Core/CharacterParsersTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreTests.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 03/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | @testable import Sparse 12 | 13 | class CharacterParsersSpec: QuickSpec { 14 | 15 | override func spec() { 16 | 17 | describe("the character parser") { 18 | 19 | let target: Character = "X" 20 | let parser = character(target) 21 | 22 | it("should accept the right character") { 23 | if let output = shouldNotThrow({ try parser.parse(Stream(String(target))) }) { 24 | expect(output).to(equal(target)) 25 | } 26 | } 27 | it("should reject wrong characters") { 28 | _ = shouldThrow({ _ = try parser.parse(Stream("x")) }) 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/SparseTests/Core/ParserCombinatorsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParserCombinatorsTests.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 07/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import Sparse 13 | 14 | class ParserCombinatorsSpec: QuickSpec { 15 | 16 | override func spec() { 17 | 18 | describe("the 'then' function") { 19 | 20 | let parser = character("X").then(character("Y")) 21 | 22 | it("should parse good input correctly") { 23 | let input = "XY" 24 | let stream = Stream(input) 25 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 26 | expect(output.0).to(equal("X")) 27 | expect(output.1).to(equal("Y")) 28 | expect(stream.isAtEnd).to(equal(true)) 29 | } 30 | } 31 | it("should fail on bad inputs") { 32 | let badInputs = ["Xy", "X", "YY", "Xray"] 33 | for input in badInputs { 34 | let stream = Stream(input) 35 | _ = shouldThrow({ _ = try parser.parse(stream) }) 36 | } 37 | } 38 | } 39 | describe("the 'skipThen' function") { 40 | 41 | let parser = character("X").skipThen(character("Y")) 42 | 43 | it("should parse good input correctly") { 44 | let input = "XY" 45 | let stream = Stream(input) 46 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 47 | expect(output).to(equal("Y")) 48 | expect(stream.isAtEnd).to(equal(true)) 49 | } 50 | } 51 | it("should fail on bad inputs") { 52 | let badInputs = ["Xy", "X", "YY", "Xray"] 53 | for input in badInputs { 54 | let stream = Stream(input) 55 | _ = shouldThrow({ _ = try parser.parse(stream) }) 56 | } 57 | } 58 | } 59 | describe("the 'thenSkip' function") { 60 | 61 | let parser = character("X").thenSkip(character("Y")) 62 | 63 | it("should parse good input correctly") { 64 | let input = "XY" 65 | let stream = Stream(input) 66 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 67 | expect(output).to(equal("X")) 68 | expect(stream.isAtEnd).to(equal(true)) 69 | } 70 | } 71 | it("should fail on bad inputs") { 72 | let badInputs = ["Xy", "X", "YY", "Xray"] 73 | for input in badInputs { 74 | let stream = Stream(input) 75 | _ = shouldThrow({ _ = try parser.parse(stream) }) 76 | } 77 | } 78 | } 79 | describe("the 'butNot' function") { 80 | 81 | let parser = character(in: .alphanumerics).butNot(string("XX")) 82 | 83 | it("should parse valid input") { 84 | let input = "hello" 85 | let stream = Stream(input) 86 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 87 | expect(output).to(equal("h")) 88 | expect(String(stream.remainder)).to(equal("ello")) 89 | } 90 | } 91 | it("should fail on invalid input") { 92 | let input = "XX" 93 | let stream = Stream(input) 94 | _ = shouldThrow({ try parser.parse(stream) }) 95 | } 96 | } 97 | describe("the 'while' function") { 98 | 99 | let parser = many(character(in: .alphanumerics), while: characterNot("V")) 100 | 101 | it("should stop parsing due to terminating parser") { 102 | let input = "helloVworld" 103 | let stream = Stream(input) 104 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 105 | expect(output).to(equal(["h","e","l","l","o"])) 106 | expect(String(stream.remainder)).to(equal("Vworld")) 107 | } 108 | } 109 | it("should succeed if the terminator is not found") { 110 | let input = "hello" 111 | let stream = Stream(input) 112 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 113 | expect(output).to(equal(["h","e","l","l","o"])) 114 | expect(stream.isAtEnd).to(equal(true)) 115 | } 116 | } 117 | it("should stop parsing due to the original parser") { 118 | let input = "hello world" 119 | let stream = Stream(input) 120 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 121 | expect(output).to(equal(["h","e","l","l","o"])) 122 | expect(String(stream.remainder)).to(equal(" world")) 123 | } 124 | } 125 | } 126 | describe("the 'otherwise' function") { 127 | 128 | let parser = character("V").otherwise(character("X")) 129 | 130 | it("should correctly parse with the first parser") { 131 | let input = "VR" 132 | let stream = Stream(input) 133 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 134 | expect(output).to(equal("V")) 135 | expect(String(stream.remainder)).to(equal("R")) 136 | } 137 | } 138 | it("should correctly parse with the second parser") { 139 | let input = "XR" 140 | let stream = Stream(input) 141 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 142 | expect(output).to(equal("X")) 143 | expect(String(stream.remainder)).to(equal("R")) 144 | } 145 | } 146 | it("should fail if both parsers fail") { 147 | let input = "JR" 148 | let stream = Stream(input) 149 | _ = shouldThrow({ _ = try parser.parse(stream) }) 150 | } 151 | } 152 | describe("the 'otherwiseSkip' function") { 153 | 154 | let parser = character("V").otherwiseSkip(character("X")) 155 | 156 | it("should correctly parse with the first parser") { 157 | let input = "VR" 158 | let stream = Stream(input) 159 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 160 | expect(output).to(equal("V")) 161 | let remainder = String(stream.remainder) 162 | expect(remainder).to(equal("R")) 163 | } 164 | } 165 | it("should correctly skip the second parser") { 166 | let input = "XR" 167 | let stream = Stream(input) 168 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 169 | expect(output).to(beNil()) 170 | expect(String(stream.remainder)).to(equal("R")) 171 | } 172 | } 173 | it("should fail if both parsers fail") { 174 | let input = "JR" 175 | let stream = Stream(input) 176 | _ = shouldThrow({ _ = try parser.parse(stream) }) 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Sources/SparseTests/Core/ParserErrorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParserErrorTests.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 28/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import Sparse 13 | 14 | class ParserErrorSpec: QuickSpec { 15 | 16 | override func spec() { 17 | 18 | describe("a failing parser") { 19 | let parser = many(string("abc")).then(end()) 20 | 21 | it("should return the most advanced error in the stream") { 22 | let input = "abcabcabx" 23 | let stream = Stream(input) 24 | shouldThrow({ try parser.parse(stream) }) 25 | expect(stream.error.index).to(equal("abcabcab".endIndex)) 26 | } 27 | } 28 | 29 | describe("an error in a multiline stream") { 30 | let greeting = string("hello").otherwise(string("hi")) 31 | let greetings = many(greeting.thenSkip(whitespacesOrNewlines())) 32 | let parser = greetings.thenSkip(end()) 33 | 34 | it("should give the correct line number") { 35 | let input = "hi\nhello\nhi\nhello\nhowdy" 36 | let stream = Stream(input) 37 | guard let error = shouldThrow({ try parser.parse(stream) }) as? ParserError else { 38 | return 39 | } 40 | let expectedPrefix = "Line 5, Column 2" 41 | let actualPrefix = String(error.description.prefix(upTo: expectedPrefix.endIndex)) 42 | expect(actualPrefix).to(equal(expectedPrefix)) 43 | } 44 | } 45 | 46 | describe("an infinite loop parser") { 47 | let x = string("x").withoutConsuming() 48 | let parser = many(x) 49 | 50 | it("should throw the correct error") { 51 | let input = "xxx" 52 | let stream = Stream(input) 53 | guard let error = shouldThrow({ try parser.parse(stream) }) as? ParserError else { 54 | return 55 | } 56 | let expectedErrorDescription = """ 57 | Line 1, Column 1 58 | xxx 59 | ^ 60 | Parser stuck in infinite loop 61 | """ 62 | expect(error.description).to(equal(expectedErrorDescription)) 63 | } 64 | } 65 | 66 | describe("a multiline input") { 67 | 68 | let digit = character(in: .decimalDigits).named("digit") 69 | let number = string(of: digit) 70 | let parser = many(number, separator: newline()).thenSkip(end()) 71 | 72 | it("should give the correct line numbers for errors") { 73 | let input = "1\n2\n3\r\n4\nX" 74 | let stream = Stream(input) 75 | guard let error = shouldThrow({ try parser.parse(stream) }) as? ParserError else { 76 | return 77 | } 78 | let expectedErrorDescription = """ 79 | Line 5, Column 1 80 | X 81 | ^ 82 | Expected: digit 83 | Expected: newline 84 | Expected: EOF 85 | """ 86 | expect(error.description).to(equal(expectedErrorDescription)) 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/SparseTests/Core/Quick+Throw.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuickSpec+Throw.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 07/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | 13 | @discardableResult func shouldNotThrow(file: FileString = #file, line: UInt = #line, _ closure: () throws -> O) -> O? { 14 | 15 | do { 16 | return try closure() 17 | } catch { 18 | fail("Call threw: \(error)", file: file, line: line) 19 | return nil 20 | } 21 | } 22 | 23 | @discardableResult func shouldThrow(file: FileString = #file, line: UInt = #line, _ closure: () throws -> O) -> Error? { 24 | 25 | do { 26 | let output = try closure() 27 | fail("Call did not throw any error, returned \(output)", file: file, line: line) 28 | return nil 29 | } catch { 30 | return error 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/SparseTests/DotStrings/DotStringsExample.swift: -------------------------------------------------------------------------------- 1 | // 2 | 3 | import Foundation 4 | 5 | let dotStringsExample = #""" 6 | /* 7 | valid.strings 8 | Sparse 9 | 10 | Created by John Morgan on 09/12/2016. 11 | Copyright © 2016 CocoaPods. All rights reserved. 12 | */ 13 | 14 | // Unattached single-line comment to be skipped 15 | 16 | /* Unattached multi-line comment to be skipped, 17 | because it is not attached to a translation */ 18 | 19 | "K: commentless key" = "V: commentless translation"; 20 | 21 | /* Multiline-style comment on preceding line */ 22 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 23 | 24 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 25 | 26 | /* Multiline-style comment on preceding lines 27 | An example of more than one line comment */ 28 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 29 | 30 | /* Combined multi-line and single-line comments */ 31 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 32 | 33 | // Several lines of single-line style comments 34 | // These should be combined on new lines 35 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 36 | 37 | /* Unattached comment to be skipped */ 38 | 39 | "K: no whitespace"="V: no whitespace"; 40 | "K: no preceding double newline" = "V:no preceding double newline"; 41 | 42 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 43 | 44 | "K: with a\new line" = "V: with a\new line"; 45 | 46 | // Unattached single-line comment to be skipped 47 | 48 | /* Unattached multi-line comment to be skipped, 49 | because it is not attached to a translation */ 50 | 51 | "K: commentless key" = "V: commentless translation"; 52 | 53 | /* Multiline-style comment on preceding line */ 54 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 55 | 56 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 57 | 58 | /* Multiline-style comment on preceding lines 59 | An example of more than one line comment */ 60 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 61 | 62 | /* Combined multi-line and single-line comments */ 63 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 64 | 65 | // Several lines of single-line style comments 66 | // These should be combined on new lines 67 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 68 | 69 | /* Unattached comment to be skipped */ 70 | 71 | "K: no whitespace"="V: no whitespace"; 72 | "K: no preceding double newline" = "V:no preceding double newline"; 73 | 74 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 75 | 76 | "K: with a\new line" = "V: with a\new line"; 77 | 78 | // Unattached single-line comment to be skipped 79 | 80 | /* Unattached multi-line comment to be skipped, 81 | because it is not attached to a translation */ 82 | 83 | "K: commentless key" = "V: commentless translation"; 84 | 85 | /* Multiline-style comment on preceding line */ 86 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 87 | 88 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 89 | 90 | /* Multiline-style comment on preceding lines 91 | An example of more than one line comment */ 92 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 93 | 94 | /* Combined multi-line and single-line comments */ 95 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 96 | 97 | // Several lines of single-line style comments 98 | // These should be combined on new lines 99 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 100 | 101 | /* Unattached comment to be skipped */ 102 | 103 | "K: no whitespace"="V: no whitespace"; 104 | "K: no preceding double newline" = "V:no preceding double newline"; 105 | 106 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 107 | 108 | "K: with a\new line" = "V: with a\new line"; 109 | 110 | // Unattached single-line comment to be skipped 111 | 112 | /* Unattached multi-line comment to be skipped, 113 | because it is not attached to a translation */ 114 | 115 | "K: commentless key" = "V: commentless translation"; 116 | 117 | /* Multiline-style comment on preceding line */ 118 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 119 | 120 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 121 | 122 | /* Multiline-style comment on preceding lines 123 | An example of more than one line comment */ 124 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 125 | 126 | /* Combined multi-line and single-line comments */ 127 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 128 | 129 | // Several lines of single-line style comments 130 | // These should be combined on new lines 131 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 132 | 133 | /* Unattached comment to be skipped */ 134 | 135 | "K: no whitespace"="V: no whitespace"; 136 | "K: no preceding double newline" = "V:no preceding double newline"; 137 | 138 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 139 | 140 | "K: with a\new line" = "V: with a\new line"; 141 | 142 | // Unattached single-line comment to be skipped 143 | 144 | /* Unattached multi-line comment to be skipped, 145 | because it is not attached to a translation */ 146 | 147 | "K: commentless key" = "V: commentless translation"; 148 | 149 | /* Multiline-style comment on preceding line */ 150 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 151 | 152 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 153 | 154 | /* Multiline-style comment on preceding lines 155 | An example of more than one line comment */ 156 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 157 | 158 | /* Combined multi-line and single-line comments */ 159 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 160 | 161 | // Several lines of single-line style comments 162 | // These should be combined on new lines 163 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 164 | 165 | /* Unattached comment to be skipped */ 166 | 167 | "K: no whitespace"="V: no whitespace"; 168 | "K: no preceding double newline" = "V:no preceding double newline"; 169 | 170 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 171 | 172 | "K: with a\new line" = "V: with a\new line"; 173 | 174 | // Unattached single-line comment to be skipped 175 | 176 | /* Unattached multi-line comment to be skipped, 177 | because it is not attached to a translation */ 178 | 179 | "K: commentless key" = "V: commentless translation"; 180 | 181 | /* Multiline-style comment on preceding line */ 182 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 183 | 184 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 185 | 186 | /* Multiline-style comment on preceding lines 187 | An example of more than one line comment */ 188 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 189 | 190 | /* Combined multi-line and single-line comments */ 191 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 192 | 193 | // Several lines of single-line style comments 194 | // These should be combined on new lines 195 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 196 | 197 | /* Unattached comment to be skipped */ 198 | 199 | "K: no whitespace"="V: no whitespace"; 200 | "K: no preceding double newline" = "V:no preceding double newline"; 201 | 202 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 203 | 204 | "K: with a\new line" = "V: with a\new line"; 205 | 206 | // Unattached single-line comment to be skipped 207 | 208 | /* Unattached multi-line comment to be skipped, 209 | because it is not attached to a translation */ 210 | 211 | "K: commentless key" = "V: commentless translation"; 212 | 213 | /* Multiline-style comment on preceding line */ 214 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 215 | 216 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 217 | 218 | /* Multiline-style comment on preceding lines 219 | An example of more than one line comment */ 220 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 221 | 222 | /* Combined multi-line and single-line comments */ 223 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 224 | 225 | // Several lines of single-line style comments 226 | // These should be combined on new lines 227 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 228 | 229 | /* Unattached comment to be skipped */ 230 | 231 | "K: no whitespace"="V: no whitespace"; 232 | "K: no preceding double newline" = "V:no preceding double newline"; 233 | 234 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 235 | 236 | "K: with a\new line" = "V: with a\new line"; 237 | 238 | // Unattached single-line comment to be skipped 239 | 240 | /* Unattached multi-line comment to be skipped, 241 | because it is not attached to a translation */ 242 | 243 | "K: commentless key" = "V: commentless translation"; 244 | 245 | /* Multiline-style comment on preceding line */ 246 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 247 | 248 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 249 | 250 | /* Multiline-style comment on preceding lines 251 | An example of more than one line comment */ 252 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 253 | 254 | /* Combined multi-line and single-line comments */ 255 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 256 | 257 | // Several lines of single-line style comments 258 | // These should be combined on new lines 259 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 260 | 261 | /* Unattached comment to be skipped */ 262 | 263 | "K: no whitespace"="V: no whitespace"; 264 | "K: no preceding double newline" = "V:no preceding double newline"; 265 | 266 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 267 | 268 | "K: with a\new line" = "V: with a\new line"; 269 | 270 | // Unattached single-line comment to be skipped 271 | 272 | /* Unattached multi-line comment to be skipped, 273 | because it is not attached to a translation */ 274 | 275 | "K: commentless key" = "V: commentless translation"; 276 | 277 | /* Multiline-style comment on preceding line */ 278 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 279 | 280 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 281 | 282 | /* Multiline-style comment on preceding lines 283 | An example of more than one line comment */ 284 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 285 | 286 | /* Combined multi-line and single-line comments */ 287 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 288 | 289 | // Several lines of single-line style comments 290 | // These should be combined on new lines 291 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 292 | 293 | /* Unattached comment to be skipped */ 294 | 295 | "K: no whitespace"="V: no whitespace"; 296 | "K: no preceding double newline" = "V:no preceding double newline"; 297 | 298 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 299 | 300 | "K: with a\new line" = "V: with a\new line"; 301 | 302 | // Unattached single-line comment to be skipped 303 | 304 | /* Unattached multi-line comment to be skipped, 305 | because it is not attached to a translation */ 306 | 307 | "K: commentless key" = "V: commentless translation"; 308 | 309 | /* Multiline-style comment on preceding line */ 310 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 311 | 312 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 313 | 314 | /* Multiline-style comment on preceding lines 315 | An example of more than one line comment */ 316 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 317 | 318 | /* Combined multi-line and single-line comments */ 319 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 320 | 321 | // Several lines of single-line style comments 322 | // These should be combined on new lines 323 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 324 | 325 | /* Unattached comment to be skipped */ 326 | 327 | "K: no whitespace"="V: no whitespace"; 328 | "K: no preceding double newline" = "V:no preceding double newline"; 329 | 330 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 331 | 332 | "K: with a\new line" = "V: with a\new line"; 333 | 334 | // Unattached single-line comment to be skipped 335 | 336 | /* Unattached multi-line comment to be skipped, 337 | because it is not attached to a translation */ 338 | 339 | "K: commentless key" = "V: commentless translation"; 340 | 341 | /* Multiline-style comment on preceding line */ 342 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 343 | 344 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 345 | 346 | /* Multiline-style comment on preceding lines 347 | An example of more than one line comment */ 348 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 349 | 350 | /* Combined multi-line and single-line comments */ 351 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 352 | 353 | // Several lines of single-line style comments 354 | // These should be combined on new lines 355 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 356 | 357 | /* Unattached comment to be skipped */ 358 | 359 | "K: no whitespace"="V: no whitespace"; 360 | "K: no preceding double newline" = "V:no preceding double newline"; 361 | 362 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 363 | 364 | "K: with a\new line" = "V: with a\new line"; 365 | 366 | // Unattached single-line comment to be skipped 367 | 368 | /* Unattached multi-line comment to be skipped, 369 | because it is not attached to a translation */ 370 | 371 | "K: commentless key" = "V: commentless translation"; 372 | 373 | /* Multiline-style comment on preceding line */ 374 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 375 | 376 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 377 | 378 | /* Multiline-style comment on preceding lines 379 | An example of more than one line comment */ 380 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 381 | 382 | /* Combined multi-line and single-line comments */ 383 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 384 | 385 | // Several lines of single-line style comments 386 | // These should be combined on new lines 387 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 388 | 389 | /* Unattached comment to be skipped */ 390 | 391 | "K: no whitespace"="V: no whitespace"; 392 | "K: no preceding double newline" = "V:no preceding double newline"; 393 | 394 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 395 | 396 | "K: with a\new line" = "V: with a\new line"; 397 | 398 | // Unattached single-line comment to be skipped 399 | 400 | /* Unattached multi-line comment to be skipped, 401 | because it is not attached to a translation */ 402 | 403 | "K: commentless key" = "V: commentless translation"; 404 | 405 | /* Multiline-style comment on preceding line */ 406 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 407 | 408 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 409 | 410 | /* Multiline-style comment on preceding lines 411 | An example of more than one line comment */ 412 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 413 | 414 | /* Combined multi-line and single-line comments */ 415 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 416 | 417 | // Several lines of single-line style comments 418 | // These should be combined on new lines 419 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 420 | 421 | /* Unattached comment to be skipped */ 422 | 423 | "K: no whitespace"="V: no whitespace"; 424 | "K: no preceding double newline" = "V:no preceding double newline"; 425 | 426 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 427 | 428 | "K: with a\new line" = "V: with a\new line"; 429 | 430 | // Unattached single-line comment to be skipped 431 | 432 | /* Unattached multi-line comment to be skipped, 433 | because it is not attached to a translation */ 434 | 435 | "K: commentless key" = "V: commentless translation"; 436 | 437 | /* Multiline-style comment on preceding line */ 438 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 439 | 440 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 441 | 442 | /* Multiline-style comment on preceding lines 443 | An example of more than one line comment */ 444 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 445 | 446 | /* Combined multi-line and single-line comments */ 447 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 448 | 449 | // Several lines of single-line style comments 450 | // These should be combined on new lines 451 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 452 | 453 | /* Unattached comment to be skipped */ 454 | 455 | "K: no whitespace"="V: no whitespace"; 456 | "K: no preceding double newline" = "V:no preceding double newline"; 457 | 458 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 459 | 460 | "K: with a\new line" = "V: with a\new line"; 461 | 462 | // Unattached single-line comment to be skipped 463 | 464 | /* Unattached multi-line comment to be skipped, 465 | because it is not attached to a translation */ 466 | 467 | "K: commentless key" = "V: commentless translation"; 468 | 469 | /* Multiline-style comment on preceding line */ 470 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 471 | 472 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 473 | 474 | /* Multiline-style comment on preceding lines 475 | An example of more than one line comment */ 476 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 477 | 478 | /* Combined multi-line and single-line comments */ 479 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 480 | 481 | // Several lines of single-line style comments 482 | // These should be combined on new lines 483 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 484 | 485 | /* Unattached comment to be skipped */ 486 | 487 | "K: no whitespace"="V: no whitespace"; 488 | "K: no preceding double newline" = "V:no preceding double newline"; 489 | 490 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 491 | 492 | "K: with a\new line" = "V: with a\new line"; 493 | 494 | // Unattached single-line comment to be skipped 495 | 496 | /* Unattached multi-line comment to be skipped, 497 | because it is not attached to a translation */ 498 | 499 | "K: commentless key" = "V: commentless translation"; 500 | 501 | /* Multiline-style comment on preceding line */ 502 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 503 | 504 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 505 | 506 | /* Multiline-style comment on preceding lines 507 | An example of more than one line comment */ 508 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 509 | 510 | /* Combined multi-line and single-line comments */ 511 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 512 | 513 | // Several lines of single-line style comments 514 | // These should be combined on new lines 515 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 516 | 517 | /* Unattached comment to be skipped */ 518 | 519 | "K: no whitespace"="V: no whitespace"; 520 | "K: no preceding double newline" = "V:no preceding double newline"; 521 | 522 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 523 | 524 | "K: with a\new line" = "V: with a\new line"; 525 | 526 | // Unattached single-line comment to be skipped 527 | 528 | /* Unattached multi-line comment to be skipped, 529 | because it is not attached to a translation */ 530 | 531 | "K: commentless key" = "V: commentless translation"; 532 | 533 | /* Multiline-style comment on preceding line */ 534 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 535 | 536 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 537 | 538 | /* Multiline-style comment on preceding lines 539 | An example of more than one line comment */ 540 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 541 | 542 | /* Combined multi-line and single-line comments */ 543 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 544 | 545 | // Several lines of single-line style comments 546 | // These should be combined on new lines 547 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 548 | 549 | /* Unattached comment to be skipped */ 550 | 551 | "K: no whitespace"="V: no whitespace"; 552 | "K: no preceding double newline" = "V:no preceding double newline"; 553 | 554 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 555 | 556 | "K: with a\new line" = "V: with a\new line"; 557 | 558 | // Unattached single-line comment to be skipped 559 | 560 | /* Unattached multi-line comment to be skipped, 561 | because it is not attached to a translation */ 562 | 563 | "K: commentless key" = "V: commentless translation"; 564 | 565 | /* Multiline-style comment on preceding line */ 566 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 567 | 568 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 569 | 570 | /* Multiline-style comment on preceding lines 571 | An example of more than one line comment */ 572 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 573 | 574 | /* Combined multi-line and single-line comments */ 575 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 576 | 577 | // Several lines of single-line style comments 578 | // These should be combined on new lines 579 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 580 | 581 | /* Unattached comment to be skipped */ 582 | 583 | "K: no whitespace"="V: no whitespace"; 584 | "K: no preceding double newline" = "V:no preceding double newline"; 585 | 586 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 587 | 588 | "K: with a\new line" = "V: with a\new line"; 589 | 590 | // Unattached single-line comment to be skipped 591 | 592 | /* Unattached multi-line comment to be skipped, 593 | because it is not attached to a translation */ 594 | 595 | "K: commentless key" = "V: commentless translation"; 596 | 597 | /* Multiline-style comment on preceding line */ 598 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 599 | 600 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 601 | 602 | /* Multiline-style comment on preceding lines 603 | An example of more than one line comment */ 604 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 605 | 606 | /* Combined multi-line and single-line comments */ 607 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 608 | 609 | // Several lines of single-line style comments 610 | // These should be combined on new lines 611 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 612 | 613 | /* Unattached comment to be skipped */ 614 | 615 | "K: no whitespace"="V: no whitespace"; 616 | "K: no preceding double newline" = "V:no preceding double newline"; 617 | 618 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 619 | 620 | "K: with a\new line" = "V: with a\new line"; 621 | 622 | // Unattached single-line comment to be skipped 623 | 624 | /* Unattached multi-line comment to be skipped, 625 | because it is not attached to a translation */ 626 | 627 | "K: commentless key" = "V: commentless translation"; 628 | 629 | /* Multiline-style comment on preceding line */ 630 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 631 | 632 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 633 | 634 | /* Multiline-style comment on preceding lines 635 | An example of more than one line comment */ 636 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 637 | 638 | /* Combined multi-line and single-line comments */ 639 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 640 | 641 | // Several lines of single-line style comments 642 | // These should be combined on new lines 643 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 644 | 645 | /* Unattached comment to be skipped */ 646 | 647 | "K: no whitespace"="V: no whitespace"; 648 | "K: no preceding double newline" = "V:no preceding double newline"; 649 | 650 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 651 | 652 | "K: with a\new line" = "V: with a\new line"; 653 | 654 | // Unattached single-line comment to be skipped 655 | 656 | /* Unattached multi-line comment to be skipped, 657 | because it is not attached to a translation */ 658 | 659 | "K: commentless key" = "V: commentless translation"; 660 | 661 | /* Multiline-style comment on preceding line */ 662 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 663 | 664 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 665 | 666 | /* Multiline-style comment on preceding lines 667 | An example of more than one line comment */ 668 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 669 | 670 | /* Combined multi-line and single-line comments */ 671 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 672 | 673 | // Several lines of single-line style comments 674 | // These should be combined on new lines 675 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 676 | 677 | /* Unattached comment to be skipped */ 678 | 679 | "K: no whitespace"="V: no whitespace"; 680 | "K: no preceding double newline" = "V:no preceding double newline"; 681 | 682 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 683 | 684 | "K: with a\new line" = "V: with a\new line"; 685 | 686 | // Unattached single-line comment to be skipped 687 | 688 | /* Unattached multi-line comment to be skipped, 689 | because it is not attached to a translation */ 690 | 691 | "K: commentless key" = "V: commentless translation"; 692 | 693 | /* Multiline-style comment on preceding line */ 694 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 695 | 696 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 697 | 698 | /* Multiline-style comment on preceding lines 699 | An example of more than one line comment */ 700 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 701 | 702 | /* Combined multi-line and single-line comments */ 703 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 704 | 705 | // Several lines of single-line style comments 706 | // These should be combined on new lines 707 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 708 | 709 | /* Unattached comment to be skipped */ 710 | 711 | "K: no whitespace"="V: no whitespace"; 712 | "K: no preceding double newline" = "V:no preceding double newline"; 713 | 714 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 715 | 716 | "K: with a\new line" = "V: with a\new line"; 717 | 718 | // Unattached single-line comment to be skipped 719 | 720 | /* Unattached multi-line comment to be skipped, 721 | because it is not attached to a translation */ 722 | 723 | "K: commentless key" = "V: commentless translation"; 724 | 725 | /* Multiline-style comment on preceding line */ 726 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 727 | 728 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 729 | 730 | /* Multiline-style comment on preceding lines 731 | An example of more than one line comment */ 732 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 733 | 734 | /* Combined multi-line and single-line comments */ 735 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 736 | 737 | // Several lines of single-line style comments 738 | // These should be combined on new lines 739 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 740 | 741 | /* Unattached comment to be skipped */ 742 | 743 | "K: no whitespace"="V: no whitespace"; 744 | "K: no preceding double newline" = "V:no preceding double newline"; 745 | 746 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 747 | 748 | "K: with a\new line" = "V: with a\new line"; 749 | 750 | // Unattached single-line comment to be skipped 751 | 752 | /* Unattached multi-line comment to be skipped, 753 | because it is not attached to a translation */ 754 | 755 | "K: commentless key" = "V: commentless translation"; 756 | 757 | /* Multiline-style comment on preceding line */ 758 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 759 | 760 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 761 | 762 | /* Multiline-style comment on preceding lines 763 | An example of more than one line comment */ 764 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 765 | 766 | /* Combined multi-line and single-line comments */ 767 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 768 | 769 | // Several lines of single-line style comments 770 | // These should be combined on new lines 771 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 772 | 773 | /* Unattached comment to be skipped */ 774 | 775 | "K: no whitespace"="V: no whitespace"; 776 | "K: no preceding double newline" = "V:no preceding double newline"; 777 | 778 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 779 | 780 | "K: with a\new line" = "V: with a\new line"; 781 | 782 | // Unattached single-line comment to be skipped 783 | 784 | /* Unattached multi-line comment to be skipped, 785 | because it is not attached to a translation */ 786 | 787 | "K: commentless key" = "V: commentless translation"; 788 | 789 | /* Multiline-style comment on preceding line */ 790 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 791 | 792 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 793 | 794 | /* Multiline-style comment on preceding lines 795 | An example of more than one line comment */ 796 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 797 | 798 | /* Combined multi-line and single-line comments */ 799 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 800 | 801 | // Several lines of single-line style comments 802 | // These should be combined on new lines 803 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 804 | 805 | /* Unattached comment to be skipped */ 806 | 807 | "K: no whitespace"="V: no whitespace"; 808 | "K: no preceding double newline" = "V:no preceding double newline"; 809 | 810 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 811 | 812 | "K: with a\new line" = "V: with a\new line"; 813 | 814 | // Unattached single-line comment to be skipped 815 | 816 | /* Unattached multi-line comment to be skipped, 817 | because it is not attached to a translation */ 818 | 819 | "K: commentless key" = "V: commentless translation"; 820 | 821 | /* Multiline-style comment on preceding line */ 822 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 823 | 824 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 825 | 826 | /* Multiline-style comment on preceding lines 827 | An example of more than one line comment */ 828 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 829 | 830 | /* Combined multi-line and single-line comments */ 831 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 832 | 833 | // Several lines of single-line style comments 834 | // These should be combined on new lines 835 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 836 | 837 | /* Unattached comment to be skipped */ 838 | 839 | "K: no whitespace"="V: no whitespace"; 840 | "K: no preceding double newline" = "V:no preceding double newline"; 841 | 842 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 843 | 844 | "K: with a\new line" = "V: with a\new line"; 845 | 846 | // Unattached single-line comment to be skipped 847 | 848 | /* Unattached multi-line comment to be skipped, 849 | because it is not attached to a translation */ 850 | 851 | "K: commentless key" = "V: commentless translation"; 852 | 853 | /* Multiline-style comment on preceding line */ 854 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 855 | 856 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 857 | 858 | /* Multiline-style comment on preceding lines 859 | An example of more than one line comment */ 860 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 861 | 862 | /* Combined multi-line and single-line comments */ 863 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 864 | 865 | // Several lines of single-line style comments 866 | // These should be combined on new lines 867 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 868 | 869 | /* Unattached comment to be skipped */ 870 | 871 | "K: no whitespace"="V: no whitespace"; 872 | "K: no preceding double newline" = "V:no preceding double newline"; 873 | 874 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 875 | 876 | "K: with a\new line" = "V: with a\new line"; 877 | 878 | // Unattached single-line comment to be skipped 879 | 880 | /* Unattached multi-line comment to be skipped, 881 | because it is not attached to a translation */ 882 | 883 | "K: commentless key" = "V: commentless translation"; 884 | 885 | /* Multiline-style comment on preceding line */ 886 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 887 | 888 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 889 | 890 | /* Multiline-style comment on preceding lines 891 | An example of more than one line comment */ 892 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 893 | 894 | /* Combined multi-line and single-line comments */ 895 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 896 | 897 | // Several lines of single-line style comments 898 | // These should be combined on new lines 899 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 900 | 901 | /* Unattached comment to be skipped */ 902 | 903 | "K: no whitespace"="V: no whitespace"; 904 | "K: no preceding double newline" = "V:no preceding double newline"; 905 | 906 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 907 | 908 | "K: with a\new line" = "V: with a\new line"; 909 | 910 | // Unattached single-line comment to be skipped 911 | 912 | /* Unattached multi-line comment to be skipped, 913 | because it is not attached to a translation */ 914 | 915 | "K: commentless key" = "V: commentless translation"; 916 | 917 | /* Multiline-style comment on preceding line */ 918 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 919 | 920 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 921 | 922 | /* Multiline-style comment on preceding lines 923 | An example of more than one line comment */ 924 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 925 | 926 | /* Combined multi-line and single-line comments */ 927 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 928 | 929 | // Several lines of single-line style comments 930 | // These should be combined on new lines 931 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 932 | 933 | /* Unattached comment to be skipped */ 934 | 935 | "K: no whitespace"="V: no whitespace"; 936 | "K: no preceding double newline" = "V:no preceding double newline"; 937 | 938 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 939 | 940 | "K: with a\new line" = "V: with a\new line"; 941 | 942 | // Unattached single-line comment to be skipped 943 | 944 | /* Unattached multi-line comment to be skipped, 945 | because it is not attached to a translation */ 946 | 947 | "K: commentless key" = "V: commentless translation"; 948 | 949 | /* Multiline-style comment on preceding line */ 950 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 951 | 952 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 953 | 954 | /* Multiline-style comment on preceding lines 955 | An example of more than one line comment */ 956 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 957 | 958 | /* Combined multi-line and single-line comments */ 959 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 960 | 961 | // Several lines of single-line style comments 962 | // These should be combined on new lines 963 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 964 | 965 | /* Unattached comment to be skipped */ 966 | 967 | "K: no whitespace"="V: no whitespace"; 968 | "K: no preceding double newline" = "V:no preceding double newline"; 969 | 970 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 971 | 972 | "K: with a\new line" = "V: with a\new line"; 973 | 974 | // Unattached single-line comment to be skipped 975 | 976 | /* Unattached multi-line comment to be skipped, 977 | because it is not attached to a translation */ 978 | 979 | "K: commentless key" = "V: commentless translation"; 980 | 981 | /* Multiline-style comment on preceding line */ 982 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 983 | 984 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 985 | 986 | /* Multiline-style comment on preceding lines 987 | An example of more than one line comment */ 988 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 989 | 990 | /* Combined multi-line and single-line comments */ 991 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 992 | 993 | // Several lines of single-line style comments 994 | // These should be combined on new lines 995 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 996 | 997 | /* Unattached comment to be skipped */ 998 | 999 | "K: no whitespace"="V: no whitespace"; 1000 | "K: no preceding double newline" = "V:no preceding double newline"; 1001 | 1002 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1003 | 1004 | "K: with a\new line" = "V: with a\new line"; 1005 | 1006 | // Unattached single-line comment to be skipped 1007 | 1008 | /* Unattached multi-line comment to be skipped, 1009 | because it is not attached to a translation */ 1010 | 1011 | "K: commentless key" = "V: commentless translation"; 1012 | 1013 | /* Multiline-style comment on preceding line */ 1014 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1015 | 1016 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1017 | 1018 | /* Multiline-style comment on preceding lines 1019 | An example of more than one line comment */ 1020 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1021 | 1022 | /* Combined multi-line and single-line comments */ 1023 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1024 | 1025 | // Several lines of single-line style comments 1026 | // These should be combined on new lines 1027 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1028 | 1029 | /* Unattached comment to be skipped */ 1030 | 1031 | "K: no whitespace"="V: no whitespace"; 1032 | "K: no preceding double newline" = "V:no preceding double newline"; 1033 | 1034 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1035 | 1036 | "K: with a\new line" = "V: with a\new line"; 1037 | 1038 | // Unattached single-line comment to be skipped 1039 | 1040 | /* Unattached multi-line comment to be skipped, 1041 | because it is not attached to a translation */ 1042 | 1043 | "K: commentless key" = "V: commentless translation"; 1044 | 1045 | /* Multiline-style comment on preceding line */ 1046 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1047 | 1048 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1049 | 1050 | /* Multiline-style comment on preceding lines 1051 | An example of more than one line comment */ 1052 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1053 | 1054 | /* Combined multi-line and single-line comments */ 1055 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1056 | 1057 | // Several lines of single-line style comments 1058 | // These should be combined on new lines 1059 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1060 | 1061 | /* Unattached comment to be skipped */ 1062 | 1063 | "K: no whitespace"="V: no whitespace"; 1064 | "K: no preceding double newline" = "V:no preceding double newline"; 1065 | 1066 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1067 | 1068 | "K: with a\new line" = "V: with a\new line"; 1069 | 1070 | // Unattached single-line comment to be skipped 1071 | 1072 | /* Unattached multi-line comment to be skipped, 1073 | because it is not attached to a translation */ 1074 | 1075 | "K: commentless key" = "V: commentless translation"; 1076 | 1077 | /* Multiline-style comment on preceding line */ 1078 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1079 | 1080 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1081 | 1082 | /* Multiline-style comment on preceding lines 1083 | An example of more than one line comment */ 1084 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1085 | 1086 | /* Combined multi-line and single-line comments */ 1087 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1088 | 1089 | // Several lines of single-line style comments 1090 | // These should be combined on new lines 1091 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1092 | 1093 | /* Unattached comment to be skipped */ 1094 | 1095 | "K: no whitespace"="V: no whitespace"; 1096 | "K: no preceding double newline" = "V:no preceding double newline"; 1097 | 1098 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1099 | 1100 | "K: with a\new line" = "V: with a\new line"; 1101 | 1102 | // Unattached single-line comment to be skipped 1103 | 1104 | /* Unattached multi-line comment to be skipped, 1105 | because it is not attached to a translation */ 1106 | 1107 | "K: commentless key" = "V: commentless translation"; 1108 | 1109 | /* Multiline-style comment on preceding line */ 1110 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1111 | 1112 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1113 | 1114 | /* Multiline-style comment on preceding lines 1115 | An example of more than one line comment */ 1116 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1117 | 1118 | /* Combined multi-line and single-line comments */ 1119 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1120 | 1121 | // Several lines of single-line style comments 1122 | // These should be combined on new lines 1123 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1124 | 1125 | /* Unattached comment to be skipped */ 1126 | 1127 | "K: no whitespace"="V: no whitespace"; 1128 | "K: no preceding double newline" = "V:no preceding double newline"; 1129 | 1130 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1131 | 1132 | "K: with a\new line" = "V: with a\new line"; 1133 | 1134 | // Unattached single-line comment to be skipped 1135 | 1136 | /* Unattached multi-line comment to be skipped, 1137 | because it is not attached to a translation */ 1138 | 1139 | "K: commentless key" = "V: commentless translation"; 1140 | 1141 | /* Multiline-style comment on preceding line */ 1142 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1143 | 1144 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1145 | 1146 | /* Multiline-style comment on preceding lines 1147 | An example of more than one line comment */ 1148 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1149 | 1150 | /* Combined multi-line and single-line comments */ 1151 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1152 | 1153 | // Several lines of single-line style comments 1154 | // These should be combined on new lines 1155 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1156 | 1157 | /* Unattached comment to be skipped */ 1158 | 1159 | "K: no whitespace"="V: no whitespace"; 1160 | "K: no preceding double newline" = "V:no preceding double newline"; 1161 | 1162 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1163 | 1164 | "K: with a\new line" = "V: with a\new line"; 1165 | 1166 | // Unattached single-line comment to be skipped 1167 | 1168 | /* Unattached multi-line comment to be skipped, 1169 | because it is not attached to a translation */ 1170 | 1171 | "K: commentless key" = "V: commentless translation"; 1172 | 1173 | /* Multiline-style comment on preceding line */ 1174 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1175 | 1176 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1177 | 1178 | /* Multiline-style comment on preceding lines 1179 | An example of more than one line comment */ 1180 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1181 | 1182 | /* Combined multi-line and single-line comments */ 1183 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1184 | 1185 | // Several lines of single-line style comments 1186 | // These should be combined on new lines 1187 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1188 | 1189 | /* Unattached comment to be skipped */ 1190 | 1191 | "K: no whitespace"="V: no whitespace"; 1192 | "K: no preceding double newline" = "V:no preceding double newline"; 1193 | 1194 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1195 | 1196 | "K: with a\new line" = "V: with a\new line"; 1197 | 1198 | // Unattached single-line comment to be skipped 1199 | 1200 | /* Unattached multi-line comment to be skipped, 1201 | because it is not attached to a translation */ 1202 | 1203 | "K: commentless key" = "V: commentless translation"; 1204 | 1205 | /* Multiline-style comment on preceding line */ 1206 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1207 | 1208 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1209 | 1210 | /* Multiline-style comment on preceding lines 1211 | An example of more than one line comment */ 1212 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1213 | 1214 | /* Combined multi-line and single-line comments */ 1215 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1216 | 1217 | // Several lines of single-line style comments 1218 | // These should be combined on new lines 1219 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1220 | 1221 | /* Unattached comment to be skipped */ 1222 | 1223 | "K: no whitespace"="V: no whitespace"; 1224 | "K: no preceding double newline" = "V:no preceding double newline"; 1225 | 1226 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1227 | 1228 | "K: with a\new line" = "V: with a\new line"; 1229 | 1230 | // Unattached single-line comment to be skipped 1231 | 1232 | /* Unattached multi-line comment to be skipped, 1233 | because it is not attached to a translation */ 1234 | 1235 | "K: commentless key" = "V: commentless translation"; 1236 | 1237 | /* Multiline-style comment on preceding line */ 1238 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1239 | 1240 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1241 | 1242 | /* Multiline-style comment on preceding lines 1243 | An example of more than one line comment */ 1244 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1245 | 1246 | /* Combined multi-line and single-line comments */ 1247 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1248 | 1249 | // Several lines of single-line style comments 1250 | // These should be combined on new lines 1251 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1252 | 1253 | /* Unattached comment to be skipped */ 1254 | 1255 | "K: no whitespace"="V: no whitespace"; 1256 | "K: no preceding double newline" = "V:no preceding double newline"; 1257 | 1258 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1259 | 1260 | "K: with a\new line" = "V: with a\new line"; 1261 | 1262 | // Unattached single-line comment to be skipped 1263 | 1264 | /* Unattached multi-line comment to be skipped, 1265 | because it is not attached to a translation */ 1266 | 1267 | "K: commentless key" = "V: commentless translation"; 1268 | 1269 | /* Multiline-style comment on preceding line */ 1270 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1271 | 1272 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1273 | 1274 | /* Multiline-style comment on preceding lines 1275 | An example of more than one line comment */ 1276 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1277 | 1278 | /* Combined multi-line and single-line comments */ 1279 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1280 | 1281 | // Several lines of single-line style comments 1282 | // These should be combined on new lines 1283 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1284 | 1285 | /* Unattached comment to be skipped */ 1286 | 1287 | "K: no whitespace"="V: no whitespace"; 1288 | "K: no preceding double newline" = "V:no preceding double newline"; 1289 | 1290 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1291 | 1292 | "K: with a\new line" = "V: with a\new line"; 1293 | 1294 | // Unattached single-line comment to be skipped 1295 | 1296 | /* Unattached multi-line comment to be skipped, 1297 | because it is not attached to a translation */ 1298 | 1299 | "K: commentless key" = "V: commentless translation"; 1300 | 1301 | /* Multiline-style comment on preceding line */ 1302 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1303 | 1304 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1305 | 1306 | /* Multiline-style comment on preceding lines 1307 | An example of more than one line comment */ 1308 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1309 | 1310 | /* Combined multi-line and single-line comments */ 1311 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1312 | 1313 | // Several lines of single-line style comments 1314 | // These should be combined on new lines 1315 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1316 | 1317 | /* Unattached comment to be skipped */ 1318 | 1319 | "K: no whitespace"="V: no whitespace"; 1320 | "K: no preceding double newline" = "V:no preceding double newline"; 1321 | 1322 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1323 | 1324 | "K: with a\new line" = "V: with a\new line"; 1325 | 1326 | // Unattached single-line comment to be skipped 1327 | 1328 | /* Unattached multi-line comment to be skipped, 1329 | because it is not attached to a translation */ 1330 | 1331 | "K: commentless key" = "V: commentless translation"; 1332 | 1333 | /* Multiline-style comment on preceding line */ 1334 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1335 | 1336 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1337 | 1338 | /* Multiline-style comment on preceding lines 1339 | An example of more than one line comment */ 1340 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1341 | 1342 | /* Combined multi-line and single-line comments */ 1343 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1344 | 1345 | // Several lines of single-line style comments 1346 | // These should be combined on new lines 1347 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1348 | 1349 | /* Unattached comment to be skipped */ 1350 | 1351 | "K: no whitespace"="V: no whitespace"; 1352 | "K: no preceding double newline" = "V:no preceding double newline"; 1353 | 1354 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1355 | 1356 | "K: with a\new line" = "V: with a\new line"; 1357 | 1358 | // Unattached single-line comment to be skipped 1359 | 1360 | /* Unattached multi-line comment to be skipped, 1361 | because it is not attached to a translation */ 1362 | 1363 | "K: commentless key" = "V: commentless translation"; 1364 | 1365 | /* Multiline-style comment on preceding line */ 1366 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1367 | 1368 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1369 | 1370 | /* Multiline-style comment on preceding lines 1371 | An example of more than one line comment */ 1372 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1373 | 1374 | /* Combined multi-line and single-line comments */ 1375 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1376 | 1377 | // Several lines of single-line style comments 1378 | // These should be combined on new lines 1379 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1380 | 1381 | /* Unattached comment to be skipped */ 1382 | 1383 | "K: no whitespace"="V: no whitespace"; 1384 | "K: no preceding double newline" = "V:no preceding double newline"; 1385 | 1386 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1387 | 1388 | "K: with a\new line" = "V: with a\new line"; 1389 | 1390 | // Unattached single-line comment to be skipped 1391 | 1392 | /* Unattached multi-line comment to be skipped, 1393 | because it is not attached to a translation */ 1394 | 1395 | "K: commentless key" = "V: commentless translation"; 1396 | 1397 | /* Multiline-style comment on preceding line */ 1398 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1399 | 1400 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1401 | 1402 | /* Multiline-style comment on preceding lines 1403 | An example of more than one line comment */ 1404 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1405 | 1406 | /* Combined multi-line and single-line comments */ 1407 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1408 | 1409 | // Several lines of single-line style comments 1410 | // These should be combined on new lines 1411 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1412 | 1413 | /* Unattached comment to be skipped */ 1414 | 1415 | "K: no whitespace"="V: no whitespace"; 1416 | "K: no preceding double newline" = "V:no preceding double newline"; 1417 | 1418 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1419 | 1420 | "K: with a\new line" = "V: with a\new line"; 1421 | 1422 | // Unattached single-line comment to be skipped 1423 | 1424 | /* Unattached multi-line comment to be skipped, 1425 | because it is not attached to a translation */ 1426 | 1427 | "K: commentless key" = "V: commentless translation"; 1428 | 1429 | /* Multiline-style comment on preceding line */ 1430 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1431 | 1432 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1433 | 1434 | /* Multiline-style comment on preceding lines 1435 | An example of more than one line comment */ 1436 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1437 | 1438 | /* Combined multi-line and single-line comments */ 1439 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1440 | 1441 | // Several lines of single-line style comments 1442 | // These should be combined on new lines 1443 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1444 | 1445 | /* Unattached comment to be skipped */ 1446 | 1447 | "K: no whitespace"="V: no whitespace"; 1448 | "K: no preceding double newline" = "V:no preceding double newline"; 1449 | 1450 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1451 | 1452 | "K: with a\new line" = "V: with a\new line"; 1453 | 1454 | // Unattached single-line comment to be skipped 1455 | 1456 | /* Unattached multi-line comment to be skipped, 1457 | because it is not attached to a translation */ 1458 | 1459 | "K: commentless key" = "V: commentless translation"; 1460 | 1461 | /* Multiline-style comment on preceding line */ 1462 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1463 | 1464 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1465 | 1466 | /* Multiline-style comment on preceding lines 1467 | An example of more than one line comment */ 1468 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1469 | 1470 | /* Combined multi-line and single-line comments */ 1471 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1472 | 1473 | // Several lines of single-line style comments 1474 | // These should be combined on new lines 1475 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1476 | 1477 | /* Unattached comment to be skipped */ 1478 | 1479 | "K: no whitespace"="V: no whitespace"; 1480 | "K: no preceding double newline" = "V:no preceding double newline"; 1481 | 1482 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1483 | 1484 | "K: with a\new line" = "V: with a\new line"; 1485 | 1486 | // Unattached single-line comment to be skipped 1487 | 1488 | /* Unattached multi-line comment to be skipped, 1489 | because it is not attached to a translation */ 1490 | 1491 | "K: commentless key" = "V: commentless translation"; 1492 | 1493 | /* Multiline-style comment on preceding line */ 1494 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1495 | 1496 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1497 | 1498 | /* Multiline-style comment on preceding lines 1499 | An example of more than one line comment */ 1500 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1501 | 1502 | /* Combined multi-line and single-line comments */ 1503 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1504 | 1505 | // Several lines of single-line style comments 1506 | // These should be combined on new lines 1507 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1508 | 1509 | /* Unattached comment to be skipped */ 1510 | 1511 | "K: no whitespace"="V: no whitespace"; 1512 | "K: no preceding double newline" = "V:no preceding double newline"; 1513 | 1514 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1515 | 1516 | "K: with a\new line" = "V: with a\new line"; 1517 | 1518 | // Unattached single-line comment to be skipped 1519 | 1520 | /* Unattached multi-line comment to be skipped, 1521 | because it is not attached to a translation */ 1522 | 1523 | "K: commentless key" = "V: commentless translation"; 1524 | 1525 | /* Multiline-style comment on preceding line */ 1526 | "K: Multiline-style comment on preceding line" = "V: Multiline-style comment on preceding line"; 1527 | 1528 | "K: Single-line comment on same line" = "V: Single-line comment on same line"; // Single-line comment on same line 1529 | 1530 | /* Multiline-style comment on preceding lines 1531 | An example of more than one line comment */ 1532 | "K: Multiline-style comment on multiple lines" = "V: Multiline-style comment on multiple lines"; 1533 | 1534 | /* Combined multi-line and single-line comments */ 1535 | "K: Combined multi-line and single-line comments" = "V: Combined multi-line and single-line comments"; // These should be combined on new lines 1536 | 1537 | // Several lines of single-line style comments 1538 | // These should be combined on new lines 1539 | "K: Several lines of single-line style comments" = "V: Several lines of single-line style comments"; 1540 | 1541 | /* Unattached comment to be skipped */ 1542 | 1543 | "K: no whitespace"="V: no whitespace"; 1544 | "K: no preceding double newline" = "V:no preceding double newline"; 1545 | 1546 | "K: with \"escaped quotes\"" = "V: with \"escaped quotes\""; 1547 | 1548 | "K: with a\new line" = "V: with a\new line"; 1549 | 1550 | """# 1551 | -------------------------------------------------------------------------------- /Sources/SparseTests/DotStrings/DotStringsParserPerformance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringsParserPerformance.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 11/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Sparse 11 | 12 | class DotStringsParserPerformance: XCTestCase { 13 | 14 | func testDotStringsParserPerformance() { 15 | 16 | let parser = DotStringsParser.stringsParser 17 | let input = dotStringsExample 18 | let stream = Stream(input) 19 | self.measure { 20 | let _ = try! parser.parse(stream) 21 | } 22 | } 23 | 24 | func testPlistPerformanceForComparison() { 25 | 26 | let data = dotStringsExample.data(using: .utf8)! 27 | self.measure { 28 | let _ = try! PropertyListSerialization.propertyList(from: data, format: nil) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/SparseTests/DotStrings/DotStringsParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DotStringsTests.swift 3 | // Sparse 4 | // 5 | // Created by John Morgan on 03/12/2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import Sparse 13 | 14 | class DotStringsSpec: QuickSpec { 15 | 16 | override func spec() { 17 | 18 | describe("the DotStrings parser") { 19 | 20 | let parser = DotStringsParser.stringsParser 21 | 22 | it("should produce the same output as plist serialization") { 23 | let input = dotStringsExample 24 | let stream = Stream(input) 25 | let data = input.data(using: .utf8)! 26 | if let output = shouldNotThrow({ try parser.parse(stream) }), 27 | let plist = shouldNotThrow({ try PropertyListSerialization.propertyList(from: data, format: nil)}) as? [String: String] { 28 | var dict = [String : String]() 29 | for entry in output { 30 | dict[entry.key] = entry.translation 31 | } 32 | expect(dict).to(equal(plist)) 33 | } 34 | } 35 | } 36 | describe("the kvp parser") { 37 | 38 | let parser = DotStringsParser.kvp 39 | it("should correctly parse an uncommented key-value pair") { 40 | 41 | let input = "\"K: \\\"commentless key\"=\"V: commentless translation\";" 42 | let stream = Stream(input) 43 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 44 | expect(output.0).to(equal("K: \"commentless key")) 45 | expect(output.1).to(equal("V: commentless translation")) 46 | } 47 | } 48 | } 49 | describe("the entry parser") { 50 | 51 | let parser = DotStringsParser.entryObject 52 | it("should correctly parse an uncommented key-value pair") { 53 | 54 | let input = "\"K: commentless key\" = \"V: commentless translation\";" 55 | let stream = Stream(input) 56 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 57 | expect(output.key).to(equal("K: commentless key")) 58 | expect(output.translation).to(equal("V: commentless translation")) 59 | } 60 | } 61 | } 62 | describe("the entries parser") { 63 | 64 | let parser = DotStringsParser.entries 65 | it("should correctly parse an uncommented key-value pair") { 66 | let input = "\"K: commentless key\" = \"V: commentless translation\";" 67 | let stream = Stream(input) 68 | if let output = shouldNotThrow({ try parser.parse(stream) }) { 69 | expect(stream.isAtEnd).to(equal(true)) 70 | expect(output).toNot(beNil()) 71 | expect(output.count).to(equal(1)) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sparse.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'Sparse' 4 | s.version = '0.4.1' 5 | s.swift_version = '5.1' 6 | s.summary = 'Sparse is a simple parsing library written in Swift.' 7 | s.description = <<-DESC 8 | Sparse is a simple parsing library, written in Swift. It is based 9 | on the parser-combinator approach as used by Haskell's Parsec. 10 | Its focus is on natural language parser creation and descriptive 11 | error messages. 12 | DESC 13 | s.homepage = 'https://github.com/johnpatrickmorgan/Sparse' 14 | s.license = { :type => 'MIT', :file => 'LICENSE' } 15 | s.author = { 'johnpatrickmorgan' => 'johnpatrickmorganuk@gmail.com' } 16 | s.source = { :git => 'https://github.com/johnpatrickmorgan/Sparse.git', :tag => s.version.to_s } 17 | s.social_media_url = 'https://twitter.com/jpmmusic' 18 | s.ios.deployment_target = '8.0' 19 | s.osx.deployment_target = '10.10' 20 | s.source_files = 'Sources/Sparse/**/*' 21 | end 22 | --------------------------------------------------------------------------------