diff --git a/0063-parser-combinators-pt2/ParserCombinators.playground/Contents.swift b/0063-parser-combinators-pt2/ParserCombinators.playground/Contents.swift new file mode 100644 index 00000000..9f474377 --- /dev/null +++ b/0063-parser-combinators-pt2/ParserCombinators.playground/Contents.swift @@ -0,0 +1,444 @@ + +struct Parser { + let run: (inout Substring) -> A? +} + + + + + + + + + + + + + + + + + +let int = Parser { str in + let prefix = str.prefix(while: { $0.isNumber }) + let match = Int(prefix) + str.removeFirst(prefix.count) + return match +} + +let double = Parser { str in + let prefix = str.prefix(while: { $0.isNumber || $0 == "." }) + let match = Double(prefix) + str.removeFirst(prefix.count) + return match +} + +let char = Parser { str in + guard !str.isEmpty else { return nil } + return str.removeFirst() +} + +func literal(_ p: String) -> Parser { + return Parser { str in + guard str.hasPrefix(p) else { return nil } + str.removeFirst(p.count) + return () + } +} + + + + + + + + + + + + + + + + + + + + + +func always(_ a: A) -> Parser { + return Parser { _ in a } +} + +extension Parser { + static var never: Parser { + return Parser { _ in nil } + } +} + + + + + + + + + + + + + + + + + + + + + +extension Parser { + func map(_ f: @escaping (A) -> B) -> Parser { + return Parser { str -> B? in + self.run(&str).map(f) + } + } + + func flatMap(_ f: @escaping (A) -> Parser) -> Parser { + return Parser { str -> B? in + let original = str + let matchA = self.run(&str) + let parserB = matchA.map(f) + guard let matchB = parserB?.run(&str) else { + str = original + return nil + } + return matchB + } + } +} + +func zip(_ a: Parser, _ b: Parser) -> Parser<(A, B)> { + return Parser<(A, B)> { str -> (A, B)? in + let original = str + guard let matchA = a.run(&str) else { return nil } + guard let matchB = b.run(&str) else { + str = original + return nil + } + return (matchA, matchB) + } +} + + + + + +func zip( + _ a: Parser, + _ b: Parser, + _ c: Parser + ) -> Parser<(A, B, C)> { + return zip(a, zip(b, c)) + .map { a, bc in (a, bc.0, bc.1) } +} +func zip( + _ a: Parser, + _ b: Parser, + _ c: Parser, + _ d: Parser + ) -> Parser<(A, B, C, D)> { + return zip(a, zip(b, c, d)) + .map { a, bcd in (a, bcd.0, bcd.1, bcd.2) } +} +func zip( + _ a: Parser, + _ b: Parser, + _ c: Parser, + _ d: Parser, + _ e: Parser + ) -> Parser<(A, B, C, D, E)> { + + return zip(a, zip(b, c, d, e)) + .map { a, bcde in (a, bcde.0, bcde.1, bcde.2, bcde.3) } +} +func zip( + _ a: Parser, + _ b: Parser, + _ c: Parser, + _ d: Parser, + _ e: Parser, + _ f: Parser + ) -> Parser<(A, B, C, D, E, F)> { + return zip(a, zip(b, c, d, e, f)) + .map { a, bcdef in (a, bcdef.0, bcdef.1, bcdef.2, bcdef.3, bcdef.4) } +} +func zip( + _ a: Parser, + _ b: Parser, + _ c: Parser, + _ d: Parser, + _ e: Parser, + _ f: Parser, + _ g: Parser + ) -> Parser<(A, B, C, D, E, F, G)> { + return zip(a, zip(b, c, d, e, f, g)) + .map { a, bcdefg in (a, bcdefg.0, bcdefg.1, bcdefg.2, bcdefg.3, bcdefg.4, bcdefg.5) } +} + + + + + + + + + + + + + + + + + + + + + +extension Parser { + func run(_ str: String) -> (match: A?, rest: Substring) { + var str = str[...] + let match = self.run(&str) + return (match, str) + } +} + + + + + + + + + + + + + + + + + + + + + +// 40.446° N, 79.982° W +struct Coordinate { + let latitude: Double + let longitude: Double +} + +func prefix(while p: @escaping (Character) -> Bool) -> Parser { + return Parser { str in + let prefix = str.prefix(while: p) + str.removeFirst(prefix.count) + return prefix + } +} + +let zeroOrMoreSpaces = prefix( + while: { $0 == " " }) + .map { _ in () } +// Parser { str -> Void? in +// let prefix = str.prefix(while: { $0 == " " }) +// str.removeFirst(prefix.count) +// return () +//} +let oneOrMoreSpaces = prefix( + while: { $0 == " " }) + .flatMap { + $0.isEmpty + ? .never + : always(()) +} +// Parser { str -> Void? in +// let prefix = str.prefix(while: { $0 == " " }) +// guard !prefix.isEmpty else { return nil } +// str.removeFirst(prefix.count) +// return () +//} + + + +let northSouth = char + .flatMap { + $0 == "N" ? always(1.0) + : $0 == "S" ? always(-1) + : .never +} +let eastWest = char + .flatMap { + $0 == "E" ? always(1.0) + : $0 == "W" ? always(-1) + : .never +} +let latitude = zip( + double, + literal("°"), + oneOrMoreSpaces, + northSouth + ) + .map { lat, _, _, latSign in lat * latSign } +let longitude = zip( + double, + literal("°"), + oneOrMoreSpaces, + eastWest + ) + .map { long, _, _, longSign in long * longSign } +let coord = zip( + zeroOrMoreSpaces, + latitude, + literal(","), + oneOrMoreSpaces, + longitude + ) + .map { _, lat, _, _, long in + Coordinate( + latitude: lat, + longitude: long + ) +} + + + +coord.run("40.446° N, 79.982° W") + + +coord.run("40.446° N, 79.982° W") +coord.run("40.446° N, 79.982° W ") +coord.run(" 40.446° N, 79.982° W ") + + +import Foundation + +let df = DateFormatter() +df.dateStyle = .medium + +df.date(from: "Jan 29, 2018") +df.date(from: "Jan 29, 2018") +df.date(from: " Jan 29, 2018") + + + +try NSRegularExpression(pattern: " *") + + + +Scanner().charactersToBeSkipped = .whitespaces + + + +oneOrMoreSpaces.run(" Hello, world!") +oneOrMoreSpaces.run("Hello, world!") + + + +"€42,£42,$42" + + +enum Currency { case eur, gbp, usd } +let currency = char.flatMap { + $0 == "€" ? always(Currency.eur) + : $0 == "£" ? always(.gbp) + : $0 == "$" ? always(.usd) + : .never +} + + +struct Money { + let currency: Currency + let value: Double +} +let money = zip(currency, double).map(Money.init) + + +money.run("€42,£42,$42") + + +zip(money, literal(","), money, literal(","), money) + .run("€42,£42") + + +func zeroOrMore( + _ p: Parser, + separatedBy s: Parser + ) -> Parser<[A]> { + return Parser<[A]> { str in + var rest = str + var matches: [A] = [] + while let match = p.run(&str) { + rest = str + matches.append(match) + if s.run(&str) == nil { + return matches + } + } + str = rest + return matches + } +} + + +zeroOrMore(money, separatedBy: literal(",")) + .run("€42,£42,$42,") + .match +zeroOrMore(money, separatedBy: literal(",")) + .run("€42,£42,$42,") + .rest + + +zeroOrMore(money, separatedBy: literal("")) + .run("€42£42$42") + .match + +let commaOrNewline = char + .flatMap { $0 == "," || $0 == "\n" ? always(()) : .never } + +dump( +zeroOrMore(money, separatedBy: commaOrNewline) + .run(""" +€42,£42,$42 +€42,£42,$42 +€42,£42,$42,฿10 +""") + .match) + +zeroOrMore(money, separatedBy: commaOrNewline) + .run(""" +€42,£42,$42 +€42,£42,$42 +€42,£42,$42,฿10 +""") + .rest + +dump( + zeroOrMore(money, separatedBy: commaOrNewline) + .run(""" +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +€42,£42,$42,€42,£42,$42,€42,£42,$42,€42,£42,$42 +""") + .match) diff --git a/0063-parser-combinators-pt2/ParserCombinators.playground/contents.xcplayground b/0063-parser-combinators-pt2/ParserCombinators.playground/contents.xcplayground new file mode 100644 index 00000000..63b6dd8d --- /dev/null +++ b/0063-parser-combinators-pt2/ParserCombinators.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/0063-parser-combinators-pt2/README.md b/0063-parser-combinators-pt2/README.md new file mode 100644 index 00000000..f36a8474 --- /dev/null +++ b/0063-parser-combinators-pt2/README.md @@ -0,0 +1,5 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [Parser Combinators: Part 2](https://www.pointfree.co/episodes/ep63-parser-combinators-pt2) +> +> Let’s solve another common parsing problem using parser combinators! It’s common to want to parse multiple values off a string, and while `zip` gets us part of the way there, it doesn’t let us part _any_ number of values! Luckily there’s a parser combinator that can help, and it really packs a punch. diff --git a/README.md b/README.md index a403d93b..8eda73f7 100644 --- a/README.md +++ b/README.md @@ -64,3 +64,5 @@ This repository is the home of code written on episodes of 1. [Composable Parsing: Map](0059-composable-parsing-map) 1. [Composable Parsing: Flat-Map](0060-composable-parsing-flat-map) 1. [Composable Parsing: Zip](0061-composable-parsing-zip) +1. [Parser Combinators: Part 1](0062-parser-combinators-pt1) +1. [Parser Combinators: Part 2](0063-parser-combinators-pt2)