Skip to content

Commit c50afa1

Browse files
Expanded readme, multi-platform support
1 parent 8677488 commit c50afa1

10 files changed

Lines changed: 166 additions & 53 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ on:
1010

1111
jobs:
1212
test:
13-
name: Test
13+
name: Test on destination ${{ matrix.destination }}
1414
runs-on: macOS-latest
15+
strategy:
16+
matrix:
17+
destination: ["platform=macOS", "platform=iOS Simulator,name=iPhone 8"]
1518
steps:
1619
- uses: actions/checkout@v1
1720
- name: install swiftlint
1821
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install swiftlint
19-
- name: xcodebuild test
20-
run: set -o pipefail && xcodebuild test -scheme Flexer -destination "platform=macOS" | xcpretty
22+
- name: update submodules
23+
run: git submodule update --init
24+
- name: xcodebuild test ${{ matrix.destination }}
25+
run: set -o pipefail && xcodebuild test -scheme Flexer -destination "${{ matrix.destination }}" | xcpretty

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "xcconfigs"]
2+
path = xcconfigs
3+
url = https://github.com/mrackwitz/xcconfigs.git

Flexer.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
C908A1272454AECC00ADE4D9 /* LookAheadIteratorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LookAheadIteratorProtocol.swift; sourceTree = "<group>"; };
4343
C908A12F2457239A00ADE4D9 /* ExampleLexerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleLexerTests.swift; sourceTree = "<group>"; };
4444
C908A13124574B2E00ADE4D9 /* TokenProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenProtocol.swift; sourceTree = "<group>"; };
45+
C9C421B6247942CD00694622 /* FlexerTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = FlexerTests.xcconfig; sourceTree = "<group>"; };
4546
/* End PBXFileReference section */
4647

4748
/* Begin PBXFrameworksBuildPhase section */
@@ -102,6 +103,7 @@
102103
C908A0C224532B2E00ADE4D9 /* Info.plist */,
103104
C908A0CF24532C1200ADE4D9 /* BasicTextCharacterLexerTests.swift */,
104105
C908A12F2457239A00ADE4D9 /* ExampleLexerTests.swift */,
106+
C9C421B6247942CD00694622 /* FlexerTests.xcconfig */,
105107
);
106108
path = FlexerTests;
107109
sourceTree = "<group>";
@@ -410,6 +412,7 @@
410412
};
411413
C908A0CA24532B2E00ADE4D9 /* Debug */ = {
412414
isa = XCBuildConfiguration;
415+
baseConfigurationReference = C9C421B6247942CD00694622 /* FlexerTests.xcconfig */;
413416
buildSettings = {
414417
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
415418
CODE_SIGN_STYLE = Automatic;
@@ -428,6 +431,7 @@
428431
};
429432
C908A0CB24532B2E00ADE4D9 /* Release */ = {
430433
isa = XCBuildConfiguration;
434+
baseConfigurationReference = C9C421B6247942CD00694622 /* FlexerTests.xcconfig */;
431435
buildSettings = {
432436
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
433437
CODE_SIGN_STYLE = Automatic;

Flexer/Flexer.xcconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
// Configuration settings file format documentation can be found at:
1010
// https://help.apple.com/xcode/#/dev745c5c974
1111

12+
#include "xcconfigs/UniversalFramework_Framework.xcconfig"
13+
1214
PRODUCT_NAME = Flexer
1315
PRODUCT_BUNDLE_IDENTIFIER = com.chimehq.Flexer
1416
PRODUCT_MODULE_NAME = Flexer
@@ -19,4 +21,5 @@ INFOPLIST_FILE = Flexer/Info.plist
1921
MACH_O_TYPE = staticlib
2022

2123
MACOSX_DEPLOYMENT_TARGET = 10.11
24+
IPHONEOS_DEPLOYMENT_TARGET = 10.0
2225
SWIFT_VERSION = 5.0

FlexerTests/BasicTextCharacterLexerTests.swift

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -48,47 +48,32 @@ extension BasicTextCharacterLexerTests {
4848
}
4949
}
5050

51-
//extension LexerTests {
52-
// func testSingleCharacterLowercaseRun() {
53-
// let string = "a"
54-
// var lexer = BasicTextLexer(reader: StringReader(string))
55-
//
56-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .lowercaseRun, range: NSRange(0..<1), in: string))
57-
// XCTAssertNil(lexer.next())
58-
// }
59-
//
60-
// func testMultiCharacterLowercaseRun() {
61-
// let string = "abc"
62-
// var lexer = BasicTextLexer(reader: StringReader(string))
63-
//
64-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .lowercaseRun, range: NSRange(0..<3), in: string))
65-
// XCTAssertNil(lexer.next())
66-
// }
67-
//
68-
// func testMultiCharacterUppercaseRun() {
69-
// let string = "ABC"
70-
// var lexer = BasicTextLexer(reader: StringReader(string))
71-
//
72-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .uppercaseRun, range: NSRange(0..<3), in: string))
73-
// XCTAssertNil(lexer.next())
74-
// }
75-
//
76-
// func testMixedCharacterWordRun() {
77-
// let string = "AbcDef"
78-
// var lexer = BasicTextLexer(reader: StringReader(string))
79-
//
80-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .uppercaseRun, range: NSRange(0..<1), in: string))
81-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .lowercaseRun, range: NSRange(1..<3), in: string))
82-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .uppercaseRun, range: NSRange(3..<4), in: string))
83-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .lowercaseRun, range: NSRange(4..<6), in: string))
84-
// XCTAssertNil(lexer.next())
85-
// }
86-
//
87-
// func testDigitRun() {
88-
// let string = "1234567890"
89-
// var lexer = BasicTextLexer(reader: StringReader(string))
90-
//
91-
// XCTAssertEqual(lexer.next(), BasicTextToken(kind: .numberRun, range: NSRange(0..<10), in: string))
92-
// XCTAssertNil(lexer.next())
93-
// }
94-
//}
51+
extension BasicTextCharacterLexerTests {
52+
func testSingleLowercaseCharacter() {
53+
let string = "a"
54+
var lexer = BasicTextCharacterLexer(string: string)
55+
56+
XCTAssertEqual(lexer.next(), BasicTextCharacter(kind: .lowercaseLetter, range: NSRange(0..<1), in: string))
57+
XCTAssertNil(lexer.next())
58+
}
59+
60+
func testMultiCharacterUppercaseRun() {
61+
let string = "ABC"
62+
var lexer = BasicTextCharacterLexer(string: string)
63+
64+
XCTAssertEqual(lexer.next(), BasicTextCharacter(kind: .uppercaseLetter, range: NSRange(0..<1), in: string))
65+
XCTAssertEqual(lexer.next(), BasicTextCharacter(kind: .uppercaseLetter, range: NSRange(1..<2), in: string))
66+
XCTAssertEqual(lexer.next(), BasicTextCharacter(kind: .uppercaseLetter, range: NSRange(2..<3), in: string))
67+
XCTAssertNil(lexer.next())
68+
}
69+
70+
func testDigitRun() {
71+
let string = "123"
72+
var lexer = BasicTextCharacterLexer(string: string)
73+
74+
XCTAssertEqual(lexer.next(), BasicTextCharacter(kind: .digit, range: NSRange(0..<1), in: string))
75+
XCTAssertEqual(lexer.next(), BasicTextCharacter(kind: .digit, range: NSRange(1..<2), in: string))
76+
XCTAssertEqual(lexer.next(), BasicTextCharacter(kind: .digit, range: NSRange(2..<3), in: string))
77+
XCTAssertNil(lexer.next())
78+
}
79+
}

FlexerTests/ExampleLexerTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ struct ExampleTokenSequence: Sequence, IteratorProtocol, StringInitializable {
6363
}
6464
}
6565

66+
typealias ExampleTokenLexer = LookAheadSequence<ExampleTokenSequence>
67+
6668
class ExampleLexerTests: XCTestCase {
6769
func testTokens() {
6870
let string = "abc d_eF\t\t\tGhi123 JKL 123 **&\nz"
69-
var lexer = ExampleTokenSequence(string: string).lookAhead
71+
var lexer = ExampleTokenLexer(string: string)
7072

7173
XCTAssertEqual(lexer.next(), ExampleToken(kind: .word, range: NSRange(0..<3), in: string))
7274
XCTAssertEqual(lexer.next(), ExampleToken(kind: .whitespace, range: NSRange(3..<4), in: string))

FlexerTests/FlexerTests.xcconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// FlexerTests.xcconfig
3+
// Flexer
4+
//
5+
// Created by Matt Massicotte on 2020-05-23.
6+
// Copyright © 2020 Chime Systems Inc. All rights reserved.
7+
//
8+
9+
// Configuration settings file format documentation can be found at:
10+
// https://help.apple.com/xcode/#/dev745c5c974
11+
12+
#include "xcconfigs/UniversalFramework_Test.xcconfig"
13+
14+
SUPPORTED_PLATFORMS = macosx iphonesimulator iphoneos

Package.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// swift-tools-version:5.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "Flexer",
7+
platforms: [.macOS(.v10_12), .iOS(.v10)],
8+
products: [
9+
.library(name: "Flexer", targets: ["Flexer"]),
10+
],
11+
dependencies: [],
12+
targets: [
13+
.target(name: "Flexer", dependencies: [], path: "Flexer/"),
14+
.testTarget(name: "FlexerTests", dependencies: ["Flexer"], path: "FlexerTests/"),
15+
]
16+
)

README.md

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,41 @@
33

44
# Flexer
55

6-
Flexer is a small library for building lexers in Swift.
6+
Flexer is a small library for building lexers in Swift. It is compatible with all Apple platforms.
77

88
- API tailored for hand-written parsing
99
- Fully Swift `String`-compatible
1010
- Based around `Sequence` and `IteratorProtocol` procotols
1111

1212
It turns out that Swift's `Sequence` and `Iterator` concepts work pretty well for processing tokens. They make for a familiar API that also offers a surprsing amount of power. Flexer builds on these concepts with some new protocols that are made specifically for lexing, but are generally applicable to all `Sequence` types.
1313

14+
## Integration
15+
16+
### Swift Package Manager
17+
18+
```swift
19+
dependencies: [
20+
.package(url: "https://github.com/ChimeHQ/Flexer.git")
21+
]
22+
```
23+
24+
### Carthage
25+
26+
```
27+
github "ChimeHQ/Flexer"
28+
```
29+
1430
## Look-Ahead
1531

1632
Core to lexing is the ability to look ahead at future tokens without advancing. Flexer implements look-ahead with a protocol called `LookAheadIteratorProtocol`. The whole implemenation is inspired by the `lazy` property of `Sequence`, and works very similarly.
1733

1834
```swift
1935
let lookAheadSequence = anySequence.lookAhead
2036

21-
37+
let next = lookAheadSequence.peek()
2238
```
2339

24-
The main work of building your lexer is then defining a Sequence type of tokens. All of lexing facilities you might need can then be exposed with a simple `typealias`.
40+
The main work of building your lexer is then defining a Sequence type of tokens. All of the lexing facilities you might need can then be exposed with a `typealias`.
2541

2642
```swift
2743
typealias MyLexer = LookAheadSequence<MyTokenSequence>
@@ -37,6 +53,70 @@ let tabToken = lexer.nextUntil({ $0.kind == .tab })
3753

3854
Your custom token sequence can be built by creating a struct that conforms to `Sequence`. To make this easier, Flexer includes a type that can be used as a foundation for creating more complex token streams, called `BasicTextCharacterSequence`. It is a sequence of `BasicTextCharacter` elements. It breaks up a string into commonly-needed tokens, catagorized by kind and range within the source string. This approach uses the `Token` type, which stores a kind and a range within the source string.
3955

40-
It is usually much easier to build up more complex lexing functionality with the convenience of Swift switch pattern matching, instead of having to worry about the underlying characters and ranges themselves.
56+
It is usually much easier to build up more complex lexing functionality with the convenience of Swift switch pattern matching, instead of having to worry about the underlying characters and ranges themselves. You can do this by wrapping up a `BasicTextCharacterSequence` in your own custom sequence.
57+
58+
Here's a fully-functioning example that produces four different token types. It shows off some of the scanning and look-ahead facilities that can be handy both for constructing and also using your lexer.
59+
60+
```swift
61+
enum ExampleTokenKind {
62+
case word
63+
case number
64+
case symbol
65+
case whitespace
66+
}
67+
68+
typealias ExampleToken = Flexer.Token<ExampleTokenKind>
69+
70+
struct ExampleTokenSequence: Sequence, IteratorProtocol, StringInitializable {
71+
public typealias Element = ExampleToken
72+
73+
private var lexer: BasicTextCharacterLexer
74+
75+
public init(string: String) {
76+
self.lexer = BasicTextCharacterLexer(string: string)
77+
}
78+
79+
public mutating func next() -> Element? {
80+
guard let token = lexer.peek() else {
81+
return nil
82+
}
83+
84+
switch token.kind {
85+
case .lowercaseLetter, .uppercaseLetter, .underscore:
86+
guard let endingToken = lexer.nextUntil(notIn: [.lowercaseLetter, .uppercaseLetter, .underscore, .digit]) else {
87+
return nil
88+
}
89+
90+
return ExampleToken(kind: .word, range: token.startIndex..<endingToken.endIndex)
91+
case .digit:
92+
guard let endingToken = lexer.nextUntil({ $0.kind != .digit }) else {
93+
return nil
94+
}
95+
96+
return ExampleToken(kind: .number, range: token.startIndex..<endingToken.endIndex)
97+
case .newline, .tab, .space:
98+
guard let endingToken = lexer.nextUntil(notIn: [.newline, .tab, .space]) else {
99+
return nil
100+
}
101+
102+
return ExampleToken(kind: .whitespace, range: token.startIndex..<endingToken.endIndex)
103+
default:
104+
break
105+
}
106+
107+
guard let endingToken = lexer.nextUntil(in: [.newline, .tab, .space, .lowercaseLetter, .uppercaseLetter, .underscore, .digit]) else {
108+
return nil
109+
}
110+
111+
return ExampleToken(kind: .symbol, range: token.startIndex..<endingToken.endIndex)
112+
}
113+
}
114+
115+
typealias ExampleTokenLexer = LookAheadSequence<ExampleTokenSequence>
116+
```
117+
118+
### Suggestions or Feedback
119+
120+
We'd love to hear from you! Get in touch via [twitter](https://twitter.com/chimehq), an issue, or a pull request.
41121

42-
Flexer also includes a generic `Token` struct, that you might find handy for defining your own custom token types. `BasicTextCharacter` is implemented in terms of `Token`.
122+
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.

xcconfigs

Submodule xcconfigs added at 90aae8c

0 commit comments

Comments
 (0)