├── .github └── workflows │ └── testSuite.yml ├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── SwiLex │ ├── Error.swift │ ├── Regex.swift │ ├── Scanner.swift │ ├── SwiLex.swift │ ├── SwiLexable.swift │ └── Token.swift ├── SwiLex.png └── Tests ├── LinuxMain.swift └── SwiLexTests ├── CalculatorTokensTests.swift ├── Tools └── TokensAssert.swift ├── WordNumberTokensTests.swift └── XCTestManifests.swift /.github/workflows/testSuite.yml: -------------------------------------------------------------------------------- 1 | name: TestSuite 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: swift build -v 18 | - name: Run tests 19 | run: swift test -v 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /build 3 | /.build 4 | /.swiftpm 5 | /*.xcodeproj 6 | /Packages 7 | xcuserdata/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 yassir RAMDANI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiLex", 7 | products: [ 8 | .library(name: "SwiLex", targets: ["SwiLex"]), 9 | ], 10 | targets: [ 11 | .target(name: "SwiLex"), 12 | .testTarget(name: "SwiLexTests", dependencies: ["SwiLex"]), 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | “SwiLex”/ 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | A universal lexer library in Swift. 15 |

16 | 17 | - [Introduction](#introduction) 18 | - [Installation](#installation) 19 | - [Usage](#usage) 20 | - [Simple Lexer example](#simple-lexer-example) 21 | - [Conditianal tokens Lexer example](#conditianal-tokens-lexer-example) 22 | - [Documentation](#documentation) 23 | - [Features](#features) 24 | - [Contributing](#contributing) 25 | - [Author](#author) 26 | - [License](#license) 27 | 28 | 29 | ## Introduction 30 | In computer science a Lexer or a tokenizer is a program that converts a sequence of characters (raw string) into a sequence of tokens. 31 | 32 | SwiLex is a universal lexer which means that you can use it to build *any* lexer only by defining your tokens in a Swift enum (in few lines of code). 33 | 34 | *Combined with **SwiParse** it allows to build a full Parser. ([learn more](https://github.com/yassram/SwiParse/))* 35 | 36 | ## Installation 37 | SwiLex is distributed as a Swift Package using [SPM](https://swift.org/package-manager/). 38 | 39 | To install SwiLex, add the following line to the `dependencies` list in your `Package.swift`: 40 | 41 | ```swift 42 | .package(url: "https://github.com/yassram/SwiLex.git", .upToNextMinor(from: "1.1.0")), 43 | ``` 44 | 45 | *SwiLex will support other dependency managers soon.* 46 | 47 | ## Usage 48 | 49 | Import SwiLex to use it: 50 | 51 | ```swift 52 | import SwiLex 53 | ``` 54 | 55 | ### Simple Lexer example 56 | 57 | Define a SwiLaxable enum to list your possible tokens with their corresponding regular expressions: 58 | 59 | ```swift 60 | enum WordNumberTokens: String, SwiLexable { 61 | static var separators: Set = [" "] 62 | 63 | case text = "[A-Za-z]*" 64 | case number = "[0-9]*" 65 | 66 | case eof 67 | case none 68 | } 69 | ``` 70 | 71 | Then create an instance of SwiLex and call the lex function: 72 | 73 | ```swift 74 | var lexer = SwiLex() 75 | let tokenList = try lexer.lex(input: " H3Ll0 W0r 1d 1234 aBcD ") 76 | 77 | // returns [text[H], number[3], text[Ll], number[0], text[W], number[0], 78 | // text[r], number[1], text[d], number[1234], text[aBcD]] 79 | ``` 80 | 81 | This will return a list of tokens with the type of the token and its value (the matched string). 82 | 83 | ### Conditianal tokens Lexer example 84 | 85 | SwiLex provides a mechanism for conditionally activating tokens. This tokens are only available for specific `Mode`. 86 | 87 | In the following example we want to make the `.quotedString` token available only when the `.quote` mode is active. 88 | 89 | Start by defining the possible `Mode`s. 90 | 91 | ```swift 92 | enum Modes { 93 | case normal 94 | case quote 95 | } 96 | ``` 97 | 98 | Then define a SwiLaxable enum to list the possible tokens with their corresponding regular expressions (like for a simple Lexer): 99 | 100 | ```swift 101 | enum QuotedStringTextNumberTokens: String, SwiLexable { 102 | static var separators: Set = [" "] 103 | 104 | case text = "[A-Za-z]*" 105 | case number = "[0-9]*" 106 | case doubleQuote = "\"" 107 | case quotedString = #"[^"]*"# 108 | 109 | case eof 110 | case none 111 | } 112 | ``` 113 | 114 | Next, define the tokens' availability for the possible modes by implementing the `activeForModes` property: 115 | 116 | ```swift 117 | extension QuotedStringTextNumberTokens { 118 | var activeForModes: Set { 119 | switch self { 120 | case .doubleQuote: 121 | return [.quote, .normal] 122 | case .quotedString: 123 | return [.quote] 124 | default: 125 | return [.normal] 126 | } 127 | } 128 | } 129 | ``` 130 | > - `.doubleQuote` is available for both `.normal` and `.quote` modes allowing to switch between them (by defining the end and the begining of a quote).
131 | > - `.quotedString` is only available for the `.quote` mode.
132 | > - All the other tokens are only availbale for the `.normal` mode. 133 | 134 | The last step is to tell when to change the curent mode. 135 | 136 | For that implement the function `changeMode(current mode: Modes?) -> Modes?`: 137 | 138 | ```swift 139 | extension QuotedStringTextNumberTokens { 140 | func changeMode(current mode: Modes?) -> Modes? { 141 | switch self { 142 | case .doubleQuote: 143 | return mode == .normal ? .quote : .normal 144 | default: 145 | return mode 146 | } 147 | } 148 | } 149 | ``` 150 | 151 | Finally create an instance of SwiLex and call the lex function with the initial mode: 152 | 153 | ```swift 154 | let input = #"4lb3rt Einstein said "If you can't explain it to a 6 year old, you don't understand it yourself.""# 155 | 156 | var lexer = SwiLex() 157 | let tokenList = try lexer.lex(input: input, initialMode: .normal) 158 | 159 | // [number[4], text[lb], number[3], text[rt], text[Einstein], text[said], doubleQuote["], 160 | // quotedString[If you can't explain it to a 6 year old, you don't understand it yourself.], 161 | // doubleQuote["]] 162 | 163 | ``` 164 | 165 | This will return a list of tokens with the type of the token and its value (the matched string). 166 | 167 | ### Documentation 168 | A documentation with more examples is available [here](https://github.com/yassram/SwiLex/wiki) 169 | 170 | 171 | ## Features 172 | - [x] Tokens defined using only a simple `SwiLexable` enum. 173 | - [x] Support conditional tokens with custom modes. 174 | - [x] Support actions on each match by implementing the `onLex(raw: Substring)` function. 175 | - [x] Errors with line number and the issue's substring. 176 | - [ ] Add detailed documentation with more examples. 177 | - [ ] Support Cocoapods and Carthage. 178 | - [x] 🔥 **SwiParse**, a general-purpose parser generator that can be linked to **SwiLex** to generate a full parser. *([released here](https://github.com/yassram/SwiParse))* 🔥 179 | 180 | 181 | ## Contributing 182 | This is an open source project, so feel free to contribute. How? 183 | - Open an issue. 184 | - Send feedback via email. 185 | - Propose your own fixes, suggestions and open a pull request with the changes. 186 | 187 | ## Author 188 | Yassir Ramdani 189 | 190 | ## License 191 | 192 | ``` 193 | MIT License 194 | 195 | Copyright (c) 2020 yassir RAMDANI 196 | 197 | Permission is hereby granted, free of charge, to any person obtaining a copy 198 | of this software and associated documentation files (the "Software"), to deal 199 | in the Software without restriction, including without limitation the rights 200 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 201 | copies of the Software, and to permit persons to whom the Software is 202 | furnished to do so, subject to the following conditions: 203 | 204 | The above copyright notice and this permission notice shall be included in all 205 | copies or substantial portions of the Software. 206 | 207 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 208 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 209 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 210 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 211 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 212 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 213 | SOFTWARE. 214 | ``` 215 | -------------------------------------------------------------------------------- /Sources/SwiLex/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // SwiLex 4 | // 5 | // Created by Yassir Ramdani on 12/09/2020. 6 | // Copyright © 2020 Yassir Ramdani. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum SwiLexError: Error { 12 | case noTokenMatched(at: Substring, line: Int) 13 | } 14 | 15 | extension SwiLexError : LocalizedError { 16 | public var errorDescription: String? { 17 | switch self { 18 | case let .noTokenMatched(at: str, line: line): 19 | return "No token matched \(str), line: \(line)" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SwiLex/Regex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Regex.swift 3 | // SwiLex 4 | // 5 | // Created by Yassir Ramdani on 05/09/2020. 6 | // Copyright © 2020 Yassir Ramdani. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Substring { 12 | func matches(pattern: String) -> String.Index { 13 | guard let matchRange = range(of: pattern, options: .regularExpression), 14 | matchRange.lowerBound == startIndex 15 | else { return startIndex } 16 | return matchRange.upperBound 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SwiLex/Scanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scanner.swift 3 | // SwiLex 4 | // 5 | // Created by Yassir Ramdani on 06/09/2020. 6 | // Copyright © 2020 Yassir Ramdani. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension SwiLex { 12 | struct Scanner { 13 | private let input: String 14 | private(set) var index: String.Index 15 | private(set) var currentLineNumber: Int 16 | 17 | init(input: String) { 18 | self.input = input 19 | index = self.input.startIndex 20 | currentLineNumber = 0 21 | } 22 | } 23 | } 24 | 25 | extension SwiLex.Scanner { 26 | private var end: String.Index { input.endIndex } 27 | var reachedEnd: Bool { index == end } 28 | var remainingString: Substring { input[index...] } 29 | 30 | private mutating func next() { 31 | index = input.index(after: index) 32 | } 33 | 34 | func buffer(to index: String.Index) -> Substring { 35 | input[self.index ..< index] 36 | } 37 | 38 | mutating func move(to index: String.Index) { 39 | currentLineNumber += buffer(to: index).filter { $0 == "\n" }.count 40 | self.index = index 41 | } 42 | 43 | mutating func removeSeparators() { 44 | while !reachedEnd { 45 | if Tokens.separators.contains(input[index]) { 46 | currentLineNumber += input[index] == "\n" ? 1 : 0 47 | next() 48 | } else { break } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/SwiLex/SwiLex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiLex.swift 3 | // SwiLex 4 | // 5 | // Created by Yassir Ramdani on 05/09/2020. 6 | // Copyright © 2020 Yassir Ramdani. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct SwiLex { 12 | /// The active mode *if conditional tokens are used*. 13 | private var currentMode: Tokens.Mode? 14 | 15 | // MARK: Public interface 16 | 17 | public init() {} 18 | 19 | /// The lexing function. 20 | /// 21 | /// Lexes an input string using a **SwiLexable enum** that defines the available tokens and their **regular expressions**. 22 | /// 23 | /// - Returns: A list of Tokens from a **raw string** and a **SwiLexable enum** defining the available tokens and their **regular expressions**. 24 | /// 25 | /// - Parameters: 26 | /// - input: The input string to be lexed. 27 | /// - initialMode: The initial active mode if conditional tokens are used. 28 | /// 29 | /// 30 | /// # Example 31 | /// 32 | /// ``` 33 | /// enum WordNumberTokens: String, SwiLexable { 34 | /// static var separators: Set = [" "] 35 | /// case txt = "[A-Za-z]*" 36 | /// case number = "[0-9]*" 37 | /// } 38 | /// var lexer = SwiLex() 39 | /// let tokens = try lexer.lex(input: " H3Ll0 W0r1d") 40 | /// // returns [txt[H], number[3], txt[Ll], number[0], txt[W], number[0], txt[r], number[1], txt[d]] 41 | /// ``` 42 | /// - Note: More examples with complex cases and conditional tokens are available in the project documentation. 43 | @discardableResult 44 | public mutating func lex(input: String, initialMode: Tokens.Mode? = nil) throws -> [Token] { 45 | currentMode = initialMode 46 | 47 | var scanner = Scanner(input: input) 48 | scanner.removeSeparators() 49 | 50 | var stream = [Token]() 51 | 52 | while !scanner.reachedEnd { 53 | var longestMatch: Tokens? 54 | var longestMatchIndex = scanner.index 55 | for token in Tokens.allCases { 56 | if let currentMode = currentMode, !token.activeForModes.contains(currentMode) { continue } 57 | let currentMatchIndex = scanner.remainingString.matches(pattern: token.pattern) 58 | if currentMatchIndex > longestMatchIndex { 59 | longestMatch = token 60 | longestMatchIndex = currentMatchIndex 61 | } 62 | } 63 | 64 | guard let matchedToken = longestMatch else { 65 | throw SwiLexError.noTokenMatched(at: scanner.remainingString.prefix(20), 66 | line: scanner.currentLineNumber) 67 | } 68 | 69 | let rawString = scanner.buffer(to: longestMatchIndex) 70 | let newToken = Token(type: matchedToken, value: rawString) 71 | stream.append(newToken) 72 | matchedToken.onLex(raw: rawString) 73 | currentMode = matchedToken.changeMode(current: currentMode) 74 | scanner.move(to: longestMatchIndex) 75 | scanner.removeSeparators() 76 | } 77 | let end = Token(type: Tokens.eof, value: "") 78 | stream.append(end) 79 | return stream 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/SwiLex/SwiLexable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiLexable.swift 3 | // SwiLex 4 | // 5 | // Created by Yassir Ramdani on 05/09/2020. 6 | // Copyright © 2020 Yassir Ramdani. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: Public interface 12 | 13 | /// A type of tokens that can be used to **lex** an input string using **SwiLex** (terminal tokens). 14 | public protocol SwiLexable: Hashable, RawRepresentable, CaseIterable where Self.RawValue == String { 15 | /// Possible modes while Lexing. 16 | associatedtype Mode: Hashable = AnyHashable 17 | 18 | static var eof: Self { get } 19 | static var none: Self { get } 20 | 21 | /// Characters used as sparators. 22 | /// 23 | /// Separators are ignored by the lexer and won't appear in the result token list. 24 | static var separators: Set { get } 25 | 26 | /// The pattern (*regular expression*) of the token. 27 | var pattern: String { get } 28 | /// The set of modes for which the token is active. 29 | /// 30 | /// SwiLex supports conditional tokens. 31 | /// 32 | /// Conditional tokens are tokens that are only availble if some specific modes are active. 33 | /// 34 | /// - note: If not set, all tokens are available by default. 35 | /// 36 | /// # Example 37 | /// In the bellow example, `.dubQuote` can be matched in both `.normal` and `.quote` modes 38 | /// (`.normal` mode to start a quote and `.quote` mode to end it). 39 | /// 40 | /// `.quotedStr` can only be matched in `.quote` mode. 41 | /// 42 | /// ``` 43 | /// var activeForModes: Set { 44 | /// switch self { 45 | /// case .dubQuote: 46 | /// return [.quote, .normal] 47 | /// case .quotedStr: 48 | /// return [.quote] 49 | /// default: 50 | /// return [.normal] 51 | /// } 52 | /// } 53 | /// ``` 54 | /// 55 | var activeForModes: Set { get } 56 | 57 | /// This function is called whenever a substring matches a token to perform actions on the matched string. 58 | /// 59 | /// - Parameters: 60 | /// - raw: The matched substring. 61 | func onLex(raw: Substring) 62 | 63 | /// This function is called whenever a substring matches a token to update the **current mode** if multiple modes are used. 64 | /// 65 | /// This function is used to support conditional tokens. 66 | /// 67 | /// # Example 68 | /// In the bellow example, we use changeMode to switch between the `.normal` and the `.quote` mode each time a `.dubQuote` is matched. 69 | /// 70 | /// By changing the **current mode** we can enable / disable a set of tokens. 71 | /// 72 | /// ``` 73 | /// func changeMode(current mode: Mode?) -> Mode? { 74 | /// switch self { 75 | /// case .dubQuote: 76 | /// return mode == .normal ? .quote : .normal 77 | /// default: 78 | /// return mode 79 | /// } 80 | /// } 81 | /// ``` 82 | /// 83 | /// - note: By changing the current mode, only tokens for witch the attribute **activeForModes** contains the new mode are **active**. 84 | /// 85 | /// - Parameters: 86 | /// - mode: The current active mode. 87 | func changeMode(current mode: Mode?) -> Mode? 88 | } 89 | 90 | // MARK: Default implementation 91 | 92 | public extension SwiLexable { 93 | /// The pattern (*regular expression*) of the token. 94 | /// By default the token's pattern is its rawValue. 95 | var pattern: String { rawValue } 96 | 97 | /// The set of modes for which the token is active. 98 | /// By default it is an empty set. 99 | var activeForModes: Set { [] } 100 | 101 | /// This function is called whenever a substring matches a token to perform actions on the matched string. 102 | /// By default this function does nothing, 103 | /// 104 | /// - Parameters: 105 | /// - raw: The matched substring. 106 | func onLex(raw _: Substring) {} 107 | 108 | /// This function is called whenever a substring matches a token to update the **current mode** if multiple modes are used. 109 | /// By default the current mode does not change. 110 | /// - Parameters: 111 | /// - mode: The current active mode. 112 | func changeMode(current mode: Mode?) -> Mode? { return mode } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/SwiLex/Token.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token.swift 3 | // SwiLex 4 | // 5 | // Created by Yassir Ramdani on 05/09/2020. 6 | // Copyright © 2020 Yassir Ramdani. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: Public interface 12 | 13 | /// An extracted token. 14 | public struct Token: CustomStringConvertible { 15 | // MARK: Properties 16 | 17 | /// The type of the token. 18 | public var type: Tokens 19 | /// The raw substring value of the matched token. 20 | public var value: Substring 21 | 22 | /// A default description for *pretty* prints. 23 | public var description: String { 24 | return "\(type)[\(value)]" 25 | } 26 | } 27 | 28 | extension Token: Equatable { 29 | public static func == (lhs: Token, rhs: Token) -> Bool { 30 | return lhs.type == rhs.type && lhs.value == rhs.value 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SwiLex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yassram/SwiLex/b9d886e7114ed4166a963cb560e61da98013b625/SwiLex.png -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiLexTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += WordNumberTokensTests.allTests() 7 | tests += CalculatorTokensTests.allTests() 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /Tests/SwiLexTests/CalculatorTokensTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiLex 2 | import XCTest 3 | 4 | final class CalculatorTokensTests: XCTestCase { 5 | enum CalculatorTokens: String, SwiLexable { 6 | static var separators: Set = [" ", "\n"] 7 | case op = "[+-/*]" 8 | case float = "[0-9]+.[0-9]+" 9 | case int = "[0-9]+" 10 | case lParenthesis = #"\("# 11 | case rParenthesis = #"\)"# 12 | 13 | case eof 14 | case none 15 | } 16 | 17 | func testIntSum() throws { 18 | var lexer = SwiLex() 19 | let tokens = try lexer.lex(input: "1 + 1") 20 | AssertEqualTokens(tokens1: tokens, 21 | tokens2: [ 22 | Token(type: .int, value: "1"), 23 | Token(type: .op, value: "+"), 24 | Token(type: .int, value: "1"), 25 | Token(type: .eof, value: ""), 26 | ]) 27 | } 28 | 29 | func testFloatSum() throws { 30 | var lexer = SwiLex() 31 | let tokens = try lexer.lex(input: "1.2 + 1.004") 32 | AssertEqualTokens(tokens1: tokens, 33 | tokens2: [ 34 | Token(type: .float, value: "1.2"), 35 | Token(type: .op, value: "+"), 36 | Token(type: .float, value: "1.004"), 37 | Token(type: .eof, value: ""), 38 | ]) 39 | } 40 | 41 | func testFloatIntSum() throws { 42 | var lexer = SwiLex() 43 | let tokens = try lexer.lex(input: " 1332.4322 + 1 ") 44 | AssertEqualTokens(tokens1: tokens, 45 | tokens2: [ 46 | Token(type: .float, value: "1332.4322"), 47 | Token(type: .op, value: "+"), 48 | Token(type: .int, value: "1"), 49 | Token(type: .eof, value: ""), 50 | ]) 51 | } 52 | 53 | func testComplexEprssion() throws { 54 | var lexer = SwiLex() 55 | let tokens = try lexer.lex(input: "( 1332.4322 + 1 ) *2 / 44.44 + ((2.3- 2) * 4 ) / 0.3 ") 56 | print(tokens) 57 | AssertEqualTokens(tokens1: tokens, 58 | tokens2: [ 59 | Token(type: .lParenthesis, value: "("), 60 | Token(type: .float, value: "1332.4322"), 61 | Token(type: .op, value: "+"), 62 | Token(type: .int, value: "1"), 63 | Token(type: .rParenthesis, value: ")"), 64 | Token(type: .op, value: "*"), 65 | Token(type: .int, value: "2"), 66 | Token(type: .op, value: "/"), 67 | Token(type: .float, value: "44.44"), 68 | Token(type: .op, value: "+"), 69 | Token(type: .lParenthesis, value: "("), 70 | Token(type: .lParenthesis, value: "("), 71 | Token(type: .float, value: "2.3"), 72 | Token(type: .op, value: "-"), 73 | Token(type: .int, value: "2"), 74 | Token(type: .rParenthesis, value: ")"), 75 | Token(type: .op, value: "*"), 76 | Token(type: .int, value: "4"), 77 | Token(type: .rParenthesis, value: ")"), 78 | Token(type: .op, value: "/"), 79 | Token(type: .float, value: "0.3"), 80 | Token(type: .eof, value: ""), 81 | ]) 82 | } 83 | 84 | static var allTests = [ 85 | ("testIntSum", testIntSum), 86 | ("testFloatSum", testFloatSum), 87 | ("testFloatIntSum", testFloatIntSum), 88 | ("testComplexEprssion", testComplexEprssion), 89 | ] 90 | 91 | override func setUp() { 92 | super.setUp() 93 | continueAfterFailure = false 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Tests/SwiLexTests/Tools/TokensAssert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokensAssert.swift 3 | // SwiLex 4 | // 5 | // Created by Yassir Ramdani on 12/09/2020. 6 | // Copyright © 2020 Yassir Ramdani. All rights reserved. 7 | // 8 | 9 | import SwiLex 10 | import XCTest 11 | 12 | func AssertEqualTokens(tokens1: [Token], tokens2: [Token]) { 13 | XCTAssertEqual(tokens1.count, tokens2.count, "Wrong tokens' number") 14 | for i in 0 ..< tokens1.count { 15 | XCTAssertEqual(tokens1[i].type, tokens2[i].type, "Wrong token's type at index \(i)") 16 | XCTAssertEqual(tokens1[i].value, tokens2[i].value, "Wrong token's value at index \(i)") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/SwiLexTests/WordNumberTokensTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SwiLex 2 | import XCTest 3 | 4 | final class WordNumberTokensTests: XCTestCase { 5 | enum WordNumberTokens: String, SwiLexable { 6 | static var separators: Set = [" ", "\n"] 7 | case txt = "[A-Za-z]*" 8 | case number = "[0-9]*" 9 | 10 | case eof 11 | case none 12 | } 13 | 14 | func testWords() throws { 15 | var lexer = SwiLex() 16 | let tokens = try lexer.lex(input: " HELLO h i abc ABC Hi hello boy XD") 17 | AssertEqualTokens(tokens1: tokens, 18 | tokens2: [ 19 | Token(type: .txt, value: "HELLO"), 20 | Token(type: .txt, value: "h"), 21 | Token(type: .txt, value: "i"), 22 | Token(type: .txt, value: "abc"), 23 | Token(type: .txt, value: "ABC"), 24 | Token(type: .txt, value: "Hi"), 25 | Token(type: .txt, value: "hello"), 26 | Token(type: .txt, value: "boy"), 27 | Token(type: .txt, value: "XD"), 28 | Token(type: .eof, value: ""), 29 | ]) 30 | } 31 | 32 | func testNumbers() throws { 33 | var lexer = SwiLex() 34 | let tokens = try lexer.lex(input: " 233 2 1 23123 3333333 324 33 2") 35 | AssertEqualTokens(tokens1: tokens, 36 | tokens2: [ 37 | Token(type: .number, value: "233"), 38 | Token(type: .number, value: "2"), 39 | Token(type: .number, value: "1"), 40 | Token(type: .number, value: "23123"), 41 | Token(type: .number, value: "3333333"), 42 | Token(type: .number, value: "324"), 43 | Token(type: .number, value: "33"), 44 | Token(type: .number, value: "2"), 45 | Token(type: .eof, value: ""), 46 | ]) 47 | } 48 | 49 | func testWordsAndNumbers() throws { 50 | var lexer = SwiLex() 51 | let tokens = try lexer.lex(input: " 233 2 1 23123abc ABC Hi3333333 324 33 2") 52 | AssertEqualTokens(tokens1: tokens, 53 | tokens2: [ 54 | Token(type: .number, value: "233"), 55 | Token(type: .number, value: "2"), 56 | Token(type: .number, value: "1"), 57 | Token(type: .number, value: "23123"), 58 | Token(type: .txt, value: "abc"), 59 | Token(type: .txt, value: "ABC"), 60 | Token(type: .txt, value: "Hi"), 61 | Token(type: .number, value: "3333333"), 62 | Token(type: .number, value: "324"), 63 | Token(type: .number, value: "33"), 64 | Token(type: .number, value: "2"), 65 | Token(type: .eof, value: ""), 66 | ]) 67 | } 68 | 69 | static var allTests = [ 70 | ("testWords", testWords), 71 | ("testNumbers", testNumbers), 72 | ("testWordsAndNumbers", testWordsAndNumbers), 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /Tests/SwiLexTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(WordNumberTokensTests.allTests), 7 | testCase(CalculatorTokensTests.allTests), 8 | ] 9 | } 10 | #endif 11 | --------------------------------------------------------------------------------