Coder Social home page Coder Social logo

flight-school / regularexpressiondecoder Goto Github PK

View Code? Open in Web Editor NEW
175.0 5.0 7.0 45 KB

A decoder that constructs objects from regular expression matches.

Home Page: https://flight.school

License: MIT License

Swift 100.00%
swift regular-expression codable

regularexpressiondecoder's Introduction

Regular Expression Decoder

Build Status License Swift Version

A decoder that constructs objects from regular expression matches.


For more information about creating your own custom decoders, consult Chapter 7 of the Flight School Guide to Swift Codable. For more information about using regular expressions in Swift, check out Chapter 6 of the Flight School Guide to Swift Strings.

Requirements

  • Swift 5+
  • iOS 11+ or macOS 10.13+

Usage

import RegularExpressionDecoder

let ticker = """
AAPL 170.69▲0.51
GOOG 1122.57▲2.41
AMZN 1621.48▼18.52
MSFT 106.57=0.00
SWIFT 5.0.0▲1.0.0
"""

let pattern: RegularExpressionPattern<Stock, Stock.CodingKeys> = #"""
\b
(?<\#(.symbol)>[A-Z]{1,4}) \s+
(?<\#(.price)>\d{1,}\.\d{2}) \s*
(?<\#(.sign)>([▲▼=])
(?<\#(.change)>\d{1,}\.\d{2})
\b
"""#

let decoder = try RegularExpressionDecoder<Stock>(
                    pattern: pattern,
                    options: .allowCommentsAndWhitespace
                  )

try decoder.decode([Stock].self, from: ticker)
// Decodes [AAPL, GOOG, AMZN, MSFT] (but not SWIFT, which is invalid)

Explanation

Let's say that you're building an app that parses stock quotes from a text-based stream of price changes.

let ticker = """
AAPL 170.69▲0.51
GOOG 1122.57▲2.41
AMZN 1621.48▼18.52
MSFT 106.57=0.00
"""

Each stock is represented by the following structure:

  • The symbol, consisting of 1 to 4 uppercase letters, followed by a space
  • The price, formatted as a number with 2 decimal places
  • A sign, indicating a price gain (), loss (), or no change (=)
  • The magnitude of the gain or loss, formatted the same as the price

These format constraints lend themselves naturally to representation by a regular expression, such as:

/\b[A-Z]{1,4} \d{1,}\.\d{2}[▲▼=]\d{1,}\.\d{2}\b/

Note: The \b metacharacter anchors matches to word boundaries.

This regular expression can distinguish between valid and invalid stock quotes.

"AAPL 170.69▲0.51" // valid
"SWIFT 5.0.0▲1.0.0" // invalid

However, to extract individual components from a quote (symbol, price, etc.) the regular expression must contain capture groups, of which there are two varieties: positional capture groups and named capture groups.

Positional capture groups are demarcated in the pattern by enclosing parentheses ((___)). With some slight modifications, we can make original regular expression capture each part of the stock quote:

/\b([A-Z]{1,4}) (\d{1,}\.\d{2})([▲▼=])(\d{1,}\.\d{2})\b/

When matched, the symbol can be accessed by the first capture group, the price by the second, and so on.

For large numbers of capture groups --- especially in patterns with nested groups --- one can easily lose track of which parts correspond to which positions. So another approach is to assign names to capture groups, which are denoted by the syntax (?<NAME>___).

/\b
(?<symbol>[A-Z]{1,4}) \s+
(?<price>\d{1,}\.\d{2}) \s*
(?<sign>([▲▼=])
(?<change>\d{1,}\.\d{2})
\b/

Note: Most regular expression engines --- including the one used by NSRegularExpression --- provide a mode to ignore whitespace; this lets you segment long patterns over multiple lines, making them easier to read and understand.

Theoretically, this approach allows you to access each group by name for each match of the regular expression. In practice, doing this in Swift can be inconvenient, as it requires you to interact with cumbersome NSRegularExpression APIs and somehow incorporate it into your model layer.

RegularExpressionDecoder provides a convenient solution to constructing Decodable objects from regular expression matches by automatically matching coding keys to capture group names. And it can do so safely, thanks to the new ExpressibleByStringInterpolation protocol in Swift 5.

To understand how, let's start by considering the following Stock model, which adopts the Decodable protocol:

struct Stock: Decodable {
    let symbol: String
    var price: Double

    enum Sign: String, Decodable {
        case gain = ""
        case unchanged = "="
        case loss = ""
    }

    private var sign: Sign
    private var change: Double = 0.0
    var movement: Double {
        switch sign {
        case .gain: return +change
        case .unchanged: return 0.0
        case .loss: return -change
        }
    }
}

So far, so good.

Now, normally, the Swift compiler automatically synthesizes conformance to Decodable, including a nested CodingKeys type. But in order to make this next part work correctly, we'll have to do this ourselves:

extension Stock {
    enum CodingKeys: String, CodingKey {
        case symbol
        case price
        case sign
        case change
    }
}

Here's where things get really interesting: remember our regular expression with named capture patterns from before? We can replace the hard-coded names with interpolations of the Stock type's coding keys.

import RegularExpressionDecoder

let pattern: RegularExpressionPattern<Stock, Stock.CodingKeys> = #"""
\b
(?<\#(.symbol)>[A-Z]{1,4}) \s+
(?<\#(.price)>\d{1,}\.\d{2}) \s*
(?<\#(.sign)>[▲▼=])
(?<\#(.change)>\d{1,}\.\d{2})
\b
"""#

Note: This example benefits greatly from another new feature in Swift 5: raw string literals. Those octothorps (#) at the start and end tell the compiler to ignore escape characters (\) unless they also include an octothorp (\#( )). Using raw string literals, we can write regular expression metacharacters like \b, \d, and \s without double escaping them (i.e. \\b).

Thanks to ExpressibleByStringInterpolation, we can restrict interpolation segments to only accept those coding keys, thereby ensuring a direct 1:1 match between capture groups and their decoded properties. And not only that --- this approach lets us to verify that keys have valid regex-friendly names and aren't captured more than once. It's enormously powerful, allowing code to be incredibly expressive without compromising safety or performance.

When all is said and done, RegularExpressionDecoder lets you decode types from a string according to a regular expression pattern much the same as you might from JSON or a property list using a decoder:

let decoder = try RegularExpressionDecoder<Stock>(
                        pattern: pattern,
                        options: .allowCommentsAndWhitespace
                  )

try decoder.decode([Stock].self, from: ticker)
// Decodes [AAPL, GOOG, AMZN, MSFT]

License

MIT

Contact

Mattt (@mattt)

regularexpressiondecoder's People

Contributors

mattt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.