Skip to content

Commit

Permalink
60
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Jun 2, 2019
1 parent 067dff6 commit 5061ecc
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import Foundation

struct Parser<A> {
let run: (inout Substring) -> A?
}

let int = Parser<Int> { str in
let prefix = str.prefix(while: { $0.isNumber })
guard let int = Int(prefix) else { return nil }
str.removeFirst(prefix.count)
return int
}

let double = Parser<Double> { str in
let prefix = str.prefix(while: { $0.isNumber || $0 == "." })
guard let match = Double(prefix) else { return nil }
str.removeFirst(prefix.count)
return match
}

func literal(_ literal: String) -> Parser<Void> {
return Parser<Void> { str in
guard str.hasPrefix(literal) else { return nil }
str.removeFirst(literal.count)
return ()
}
}

func always<A>(_ a: A) -> Parser<A> {
return Parser<A> { _ in a }
}

extension Parser {
static var never: Parser {
return Parser { _ in nil }
}
}

struct Coordinate {
let latitude: Double
let longitude: Double
}


extension Parser {
func run(_ str: String) -> (match: A?, rest: Substring) {
var str = str[...]
let match = self.run(&str)
return (match, str)
}
}


// map: ((A) -> B) -> (F<A>) -> F<B>

// F<A> = Parser<A>
// map: ((A) -> B) -> (Parser<A>) -> Parser<B>

// map(id) = id

[1, 2, 3]
.map { $0 }

Optional("Blob")
.map { $0 }


// map: (Parser<A>, (A) -> B) -> Parser<B>

extension Parser {
func map<B>(_ f: @escaping (A) -> B) -> Parser<B> {
return Parser<B> { str -> B? in
self.run(&str).map(f)
}
}

func fakeMap<B>(_ f: @escaping (A) -> B) -> Parser<B> {
return Parser<B> { _ in nil }
}
func fakeMap2<B>(_ f: @escaping (A) -> B) -> Parser<B> {
return Parser<B> { str in
let matchB = self.run(&str).map(f)
str = ""
return matchB
}
}
}

int.map { $0 }
int.fakeMap { $0 }.run("123")
int
.fakeMap2 { $0 }.run("123 Hello World")
int
.run("123 Hello World")

let even = int.map { $0 % 2 == 0 }

even.run("123 Hello World")
even.run("42 Hello World")

let char = Parser<Character> { str in
guard !str.isEmpty else { return nil }
return str.removeFirst()
}

let northSouth = Parser<Double> { str in
guard
let cardinal = str.first,
cardinal == "N" || cardinal == "S"
else { return nil }
str.removeFirst(1)
return cardinal == "N" ? 1 : -1
}

// flatMap: ((A) -> M<B>) -> (M<A>) -> M<B>


extension Parser {
func flatMap<B>(_ f: @escaping (A) -> Parser<B>) -> Parser<B> {
return Parser<B> { 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
}
}
}


let eastWest = Parser<Double> { str in
guard
let cardinal = str.first,
cardinal == "E" || cardinal == "W"
else { return nil }
str.removeFirst(1)
return cardinal == "E" ? 1 : -1
}

func parseLatLong(_ str: String) -> Coordinate? {
var str = str[...]

guard
let lat = double.run(&str),
literal("° ").run(&str) != nil,
let latSign = northSouth.run(&str),
literal(", ").run(&str) != nil,
let long = double.run(&str),
literal("° ").run(&str) != nil,
let longSign = eastWest.run(&str)
else { return nil }

return Coordinate(
latitude: lat * latSign,
longitude: long * longSign
)
}

print(String(describing: parseLatLong("40.6782° N, 73.9442° W")))


"40.6782° N, 73.9442° W"

let coord = double
.flatMap { lat in
literal("° ")
.flatMap { _ in
northSouth
.flatMap { latSign in
literal(", ")
.flatMap { _ in
double
.flatMap { long in
literal("° ")
.flatMap { _ in
eastWest
.map { longSign in
return Coordinate(
latitude: lat * latSign,
longitude: long * longSign
)
}
}
}
}
}
}
}

coord.run("40.6782° N, 73.9442° W")
coord.run("40.6782° Z, 73.9442° W")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='macos'>
<timeline fileName='timeline.xctimeline'/>
</playground>
5 changes: 5 additions & 0 deletions 0060-composable-parsing-flat-map/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## [Point-Free](https://www.pointfree.co)

> #### This directory contains code from Point-Free Episode: [Composable Parsing: Flat-Map](https://www.pointfree.co/episodes/ep60-composable-parsing-flat-map)
>
> The `map` function on parsers is powerful, but there are still a lot of things it cannot do. We will see that in trying to solve some of its limitations we are naturally led to our old friend the `flatMap` function.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ This repository is the home of code written on episodes of
1. [What Is a Parser?: Part 1](0056-what-is-a-parser-pt1)
1. [What Is a Parser?: Part 2](0057-what-is-a-parser-pt2)
1. [What Is a Parser?: Part 3](0058-what-is-a-parser-pt3)
1. [Composable Parsing: Map](0059-composable-parsing-map)
1. [Composable Parsing: Flat-Map](0060-composable-parsing-flat-map)

0 comments on commit 5061ecc

Please sign in to comment.