diff --git a/Sources/App/Middlewares/CommonErrorMiddleware.swift b/Sources/App/Middlewares/CommonErrorMiddleware.swift index b8d3b0c..43eb242 100644 --- a/Sources/App/Middlewares/CommonErrorMiddleware.swift +++ b/Sources/App/Middlewares/CommonErrorMiddleware.swift @@ -1,49 +1,49 @@ import Vapor final class CommonErrorMiddleware: Middleware { - func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { - return next.respond(to: request).flatMapError { (error) in - let headers: HTTPHeaders - let status: HTTPResponseStatus - switch error { - case let abort as AbortError: - headers = abort.headers - status = abort.status - default: - headers = [:] - status = .internalServerError - } + func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { + return next.respond(to: request).flatMapError { (error) in + let headers: HTTPHeaders + let status: HTTPResponseStatus + switch error { + case let abort as AbortError: + headers = abort.headers + status = abort.status + default: + headers = [:] + status = .internalServerError + } - let errotTitles: [UInt: String] = [ - 400: "Bad Request", - 401: "Unauthorized", - 403: "Access Denied", - 404: "Resource not found", - 500: "Webservice currently unavailable", - 503: "Webservice currently unavailable", - ] + let errotTitles: [UInt: String] = [ + 400: "Bad Request", + 401: "Unauthorized", + 403: "Access Denied", + 404: "Resource not found", + 500: "Webservice currently unavailable", + 503: "Webservice currently unavailable", + ] - let errotReasons: [UInt: String] = [ - 400: "The server cannot process the request due to something that is perceived to be a client error.", - 401: "The requested resource requires an authentication.", - 403: "The requested resource requires an authentication.", - 404: "The requested resource could not be found but may be available again in the future.", - 500: "An unexpected condition was encountered. Our service team has been dispatched to bring it back online.", - 503: "We've got some trouble with our backend upstream cluster. Our service team has been dispatched to bring it back online.", - ] + let errotReasons: [UInt: String] = [ + 400: "The server cannot process the request due to something that is perceived to be a client error.", + 401: "The requested resource requires an authentication.", + 403: "The requested resource requires an authentication.", + 404: "The requested resource could not be found but may be available again in the future.", + 500: "An unexpected condition was encountered. Our service team has been dispatched to bring it back online.", + 503: "We've got some trouble with our backend upstream cluster. Our service team has been dispatched to bring it back online.", + ] - if request.headers[.accept].map({ $0.lowercased() }).contains("application/json") { - return request.eventLoop.makeSucceededFuture(["error": status.code]) - .encodeResponse(status: status, headers: headers, for: request) - } else { - return request.view.render("error", [ - "title": "We've got some trouble", - "error": errotTitles[status.code], - "reason": errotReasons[status.code], - "status": "\(status.code)", - ]) - .encodeResponse(status: status, headers: headers, for: request) - } - } + if request.headers[.accept].map({ $0.lowercased() }).contains("application/json") { + return request.eventLoop.makeSucceededFuture(["error": status.code]) + .encodeResponse(status: status, headers: headers, for: request) + } else { + return request.view.render("error", [ + "title": "We've got some trouble", + "error": errotTitles[status.code], + "reason": errotReasons[status.code], + "status": "\(status.code)", + ]) + .encodeResponse(status: status, headers: headers, for: request) + } } + } } diff --git a/Sources/App/Middlewares/CustomHeaderMiddleware.swift b/Sources/App/Middlewares/CustomHeaderMiddleware.swift index 8b3ca25..4b06caa 100644 --- a/Sources/App/Middlewares/CustomHeaderMiddleware.swift +++ b/Sources/App/Middlewares/CustomHeaderMiddleware.swift @@ -1,11 +1,11 @@ import Vapor final class CustomHeaderMiddleware: Middleware { - func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { - return next.respond(to: request).map { (response) in - response.headers.add(name: "X-Frame-Options", value: "DENY") - response.headers.add(name: "Permissions-Policy", value: "interest-cohort=()") - return response - } + func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { + return next.respond(to: request).map { (response) in + response.headers.add(name: "X-Frame-Options", value: "DENY") + response.headers.add(name: "Permissions-Policy", value: "interest-cohort=()") + return response } + } } diff --git a/Sources/App/Models/ResultResponse.swift b/Sources/App/Models/ResultResponse.swift index 7e74f52..a743e16 100644 --- a/Sources/App/Models/ResultResponse.swift +++ b/Sources/App/Models/ResultResponse.swift @@ -2,7 +2,7 @@ import Foundation import Vapor struct ResultResponse: Content { - let method: RequestMethod - let result: String - let error: String + let method: RequestMethod + let result: String + let error: String } diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 070bd68..b027ef7 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -1,22 +1,22 @@ import Vapor import Leaf -public func configure(_ app: Application) throws { - app.middleware = Middlewares() - app.middleware.use(CommonErrorMiddleware()) - app.middleware.use(CustomHeaderMiddleware()) +public func configure(_ app: Application) async throws { + app.middleware = Middlewares() + app.middleware.use(CommonErrorMiddleware()) + app.middleware.use(CustomHeaderMiddleware()) - let publicDirectory = "\(app.directory.publicDirectory)/dist" - app.middleware.use(FileMiddleware(publicDirectory: publicDirectory)) - - app.http.server.configuration.port = Environment.process.PORT.flatMap { Int($0) } ?? 8080 - app.http.server.configuration.requestDecompression = .enabled - app.http.server.configuration.responseCompression = .enabled - app.http.server.configuration.supportPipelining = true + let publicDirectory = "\(app.directory.publicDirectory)/dist" + app.middleware.use(FileMiddleware(publicDirectory: publicDirectory)) - app.views.use(.leaf) - app.leaf.configuration.rootDirectory = publicDirectory - app.leaf.cache.isEnabled = app.environment.isRelease - - try routes(app) + app.http.server.configuration.port = Environment.process.PORT.flatMap { Int($0) } ?? 8080 + app.http.server.configuration.requestDecompression = .enabled + app.http.server.configuration.responseCompression = .enabled + app.http.server.configuration.supportPipelining = true + + app.views.use(.leaf) + app.leaf.configuration.rootDirectory = publicDirectory + app.leaf.cache.isEnabled = app.environment.isRelease + + try routes(app) } diff --git a/Sources/App/entrypoint.swift b/Sources/App/entrypoint.swift index 6c21174..96ff4de 100644 --- a/Sources/App/entrypoint.swift +++ b/Sources/App/entrypoint.swift @@ -1,24 +1,4 @@ import Vapor -import Dispatch -import Logging - -/// This extension is temporary and can be removed once Vapor gets this support. -private extension Vapor.Application { - static let baseExecutionQueue = DispatchQueue(label: "vapor.codes.entrypoint") - - func runFromAsyncMainEntrypoint() async throws { - try await withCheckedThrowingContinuation { continuation in - Vapor.Application.baseExecutionQueue.async { [self] in - do { - try self.run() - continuation.resume() - } catch { - continuation.resume(throwing: error) - } - } - } - } -} @main enum Entrypoint { @@ -26,10 +6,16 @@ enum Entrypoint { var env = try Environment.detect() try LoggingSystem.bootstrap(from: &env) - let app = Application(env) - defer { app.shutdown() } + let app = try await Application.make(env) - try await configure(app) - try await app.runFromAsyncMainEntrypoint() + do { + try await configure(app) + try await app.execute() + } catch { + app.logger.report(error: error) + try? await app.asyncShutdown() + throw error + } + try await app.asyncShutdown() } } diff --git a/Sources/ExpressionParser/ExpressionParser.swift b/Sources/ExpressionParser/ExpressionParser.swift index e316347..626afec 100644 --- a/Sources/ExpressionParser/ExpressionParser.swift +++ b/Sources/ExpressionParser/ExpressionParser.swift @@ -3,1430 +3,1430 @@ import Foundation @testable @_spi(RegexBuilder) import _StringProcessing struct ExpressionParser { - private(set) var tokens = [Token]() - - private let pattern: String - private let insensitive: Bool - - private var depth = 0 - private var groupCount = 0 - - init(pattern: String, insensitive: Bool = true) { - self.pattern = pattern - self.insensitive = insensitive + private(set) var tokens = [Token]() + + private let pattern: String + private let insensitive: Bool + + private var depth = 0 + private var groupCount = 0 + + init(pattern: String, insensitive: Bool = true) { + self.pattern = pattern + self.insensitive = insensitive + } + + mutating func parse() throws { + let ast = try _RegexParser.parse(pattern, .traditional) + emitNode(ast.root) + } + + private mutating func emitNode(_ node: AST.Node) { + switch node { + case .alternation(let alt): + emitAlternation(alt) + case .concatenation(let concatenation): + for node in concatenation.children { + emitNode(node) + } + case .group(let group): + emitGroup(group) + case .conditional(let conditional): + emitConditional(conditional) + case .quantification(let quant): + emitQuantification(quant) + case .quote(let quote): + emitQuote(quote) + case .trivia(let trivia): + emitTrivia(trivia) + case .interpolation(let interpolation): + emitInterpolation(interpolation) + case .atom(let atom): + emitAtom(atom) + case .customCharacterClass(let ccc): + emitCustomCharacterClass(ccc) + case .absentFunction(let absentFunction): + emitAbsentFunction(absentFunction) + case .empty(let empty): + emitEmpty(empty) } + } - mutating func parse() throws { - let ast = try _RegexParser.parse(pattern, .traditional) - emitNode(ast.root) + private mutating func emitAlternation(_ alt: AST.Alternation) { + let children = alt.children + for node in children.dropLast() { + emitNode(node) } - private mutating func emitNode(_ node: AST.Node) { - switch node { - case .alternation(let alt): - emitAlternation(alt) - case .concatenation(let concatenation): - for node in concatenation.children { - emitNode(node) - } - case .group(let group): - emitGroup(group) - case .conditional(let conditional): - emitConditional(conditional) - case .quantification(let quant): - emitQuantification(quant) - case .quote(let quote): - emitQuote(quote) - case .trivia(let trivia): - emitTrivia(trivia) - case .interpolation(let interpolation): - emitInterpolation(interpolation) - case .atom(let atom): - emitAtom(atom) - case .customCharacterClass(let ccc): - emitCustomCharacterClass(ccc) - case .absentFunction(let absentFunction): - emitAbsentFunction(absentFunction) - case .empty(let empty): - emitEmpty(empty) - } + for pipe in alt.pipes { + tokens.append( + Token( + classes: ["alt"], + location: Location( + start: pipe.start.utf16Offset(in: pattern), + end: pipe.end.utf16Offset(in: pattern) + ), + selection: Location( + start: pipe.start.utf16Offset(in: pattern), + end: pipe.end.utf16Offset(in: pattern) + ), + related: Related( + location: Location( + start: alt.startPosition.utf16Offset(in: pattern), + end: alt.endPosition.utf16Offset(in: pattern) + ) + ), + tooltip: Tooltip(category: "quants", key: "alt") + ) + ) } - private mutating func emitAlternation(_ alt: AST.Alternation) { - let children = alt.children - for node in children.dropLast() { - emitNode(node) - } + emitNode(children.last!) + } - for pipe in alt.pipes { - tokens.append( - Token( - classes: ["alt"], - location: Location( - start: pipe.start.utf16Offset(in: pattern), - end: pipe.end.utf16Offset(in: pattern) - ), - selection: Location( - start: pipe.start.utf16Offset(in: pattern), - end: pipe.end.utf16Offset(in: pattern) - ), - related: Related( - location: Location( - start: alt.startPosition.utf16Offset(in: pattern), - end: alt.endPosition.utf16Offset(in: pattern) - ) - ), - tooltip: Tooltip(category: "quants", key: "alt") - ) - ) - } + private mutating func emitGroup(_ group: AST.Group) { + let category: String + let key: String + var substitution = [String: String]() + + switch group.kind.value { + case .capture: + groupCount += 1 + category = "groups" + key = "group" + substitution = ["{{group.num}}": "\(groupCount)"] + case .namedCapture(_): + groupCount += 1 + category = "groups" + key = "namedgroup" + case .balancedCapture(_): + groupCount += 1 + category = "groups" + key = "balancedcapture" + case .nonCapture: + category = "groups" + key = "noncapgroup" + case .nonCaptureReset: + groupCount += 1 + category = "groups" + key = "branchreset" + case .atomicNonCapturing: + groupCount += 1 + category = "groups" + key = "atomic" + case .lookahead: + category = "lookaround" + key = "poslookahead" + case .negativeLookahead: + groupCount += 1 + category = "lookaround" + key = "neglookahead" + case .nonAtomicLookahead: + groupCount += 1 + category = "lookaround" + key = "nonatomicposlookahead" + case .lookbehind: + category = "lookaround" + key = "poslookbehind" + case .negativeLookbehind: + groupCount += 1 + category = "lookaround" + key = "neglookbehind" + case .nonAtomicLookbehind: + groupCount += 1 + category = "lookaround" + key = "nonatomicposlookbehind" + case .scriptRun: + groupCount += 1 + category = "Script run. " + key = "" + case .atomicScriptRun: + groupCount += 1 + category = "Atomic script run. " + key = "" + case .changeMatchingOptions(_): + groupCount += 1 + category = "Change matching options" + key = "" + } - emitNode(children.last!) + // Content + tokens.append( + Token( + classes: ["group-\(depth)"], + location: Location( + start: group.startPosition.utf16Offset(in: pattern), + end: group.endPosition.utf16Offset(in: pattern) + ) + ) + ) + + // Open parenthesis + tokens.append( + Token( + classes: ["group", "group-\(depth)"], + location: Location( + start: group.kind.location.start.utf16Offset(in: pattern), + end: group.kind.location.end.utf16Offset(in: pattern) + ), + selection: Location( + start: group.startPosition.utf16Offset(in: pattern), + end: group.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: category, key: key, substitution: substitution) + ) + ) + + // Close parenthesis + tokens.append( + Token( + classes: ["group", "group-\(depth)"], + location: Location( + start: group.child.location.end.utf16Offset(in: pattern), + end: group.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: group.startPosition.utf16Offset(in: pattern), + end: group.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: category, key: key, substitution: substitution) + ) + ) + + depth += 1 + for node in group.children { + emitNode(node) } - private mutating func emitGroup(_ group: AST.Group) { - let category: String - let key: String - var substitution = [String: String]() - - switch group.kind.value { - case .capture: - groupCount += 1 - category = "groups" - key = "group" - substitution = ["{{group.num}}": "\(groupCount)"] - case .namedCapture(_): - groupCount += 1 - category = "groups" - key = "namedgroup" - case .balancedCapture(_): - groupCount += 1 - category = "groups" - key = "balancedcapture" - case .nonCapture: - category = "groups" - key = "noncapgroup" - case .nonCaptureReset: - groupCount += 1 - category = "groups" - key = "branchreset" - case .atomicNonCapturing: - groupCount += 1 - category = "groups" - key = "atomic" - case .lookahead: - category = "lookaround" - key = "poslookahead" - case .negativeLookahead: - groupCount += 1 - category = "lookaround" - key = "neglookahead" - case .nonAtomicLookahead: - groupCount += 1 - category = "lookaround" - key = "nonatomicposlookahead" - case .lookbehind: - category = "lookaround" - key = "poslookbehind" - case .negativeLookbehind: - groupCount += 1 - category = "lookaround" - key = "neglookbehind" - case .nonAtomicLookbehind: - groupCount += 1 - category = "lookaround" - key = "nonatomicposlookbehind" - case .scriptRun: - groupCount += 1 - category = "Script run. " - key = "" - case .atomicScriptRun: - groupCount += 1 - category = "Atomic script run. " - key = "" - case .changeMatchingOptions(_): - groupCount += 1 - category = "Change matching options" - key = "" - } + depth -= 1 + } - // Content - tokens.append( - Token( - classes: ["group-\(depth)"], - location: Location( - start: group.startPosition.utf16Offset(in: pattern), - end: group.endPosition.utf16Offset(in: pattern) - ) - ) + private mutating func emitConditional(_ conditional: AST.Conditional) { + let category: String + let key: String + var substitution = [String: String]() + + switch conditional.condition.kind { + case .groupMatched(let ref): + switch ref.kind { + case .absolute(let n): + category = "other" + key = "conditionalgroup" + substitution = ["{{name}}": "\(n)"] + case .relative(let n): + category = "other" + key = "conditionalgroup" + substitution = ["{{name}}": "\(n)"] + case .named(let name): + category = "other" + key = "conditionalgroup" + substitution = ["{{name}}": "\(name)"] + } + + tokens.append( + Token( + classes: ["special"], + location: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.condition.location.end.utf16Offset(in: pattern) + 1 + ), + selection: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: category, key: key, substitution: substitution) ) - - // Open parenthesis - tokens.append( - Token( - classes: ["group", "group-\(depth)"], - location: Location( - start: group.kind.location.start.utf16Offset(in: pattern), - end: group.kind.location.end.utf16Offset(in: pattern) - ), - selection: Location( - start: group.startPosition.utf16Offset(in: pattern), - end: group.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: category, key: key, substitution: substitution) - ) + ) + case .recursionCheck: + break + case .groupRecursionCheck(_): + tokens.append( + Token( + classes: ["special"], + location: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.condition.location.end.utf16Offset(in: pattern) + 1 + ), + selection: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "other", key: "recursion") ) - - // Close parenthesis - tokens.append( - Token( - classes: ["group", "group-\(depth)"], - location: Location( - start: group.child.location.end.utf16Offset(in: pattern), - end: group.endPosition.utf16Offset(in: pattern) - ), - selection: Location( - start: group.startPosition.utf16Offset(in: pattern), - end: group.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: category, key: key, substitution: substitution) - ) + ) + case .defineGroup: + break + case .pcreVersionCheck(_): + break + case .group(let group): + tokens.append( + Token( + classes: ["special"], + location: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.condition.location.start.utf16Offset(in: pattern) + ), + selection: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "other", key: "conditional") ) + ) + + // Open parenthesis + tokens.append( + Token( + classes: ["special"], + location: Location( + start: group.kind.location.start.utf16Offset(in: pattern), + end: group.kind.location.end.utf16Offset(in: pattern) + ), + selection: Location( + start: group.startPosition.utf16Offset(in: pattern), + end: group.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "misc", key: "condition") + ) + ) + + // Close parenthesis + tokens.append( + Token( + classes: ["special"], + location: Location( + start: group.child.location.end.utf16Offset(in: pattern), + end: group.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: group.startPosition.utf16Offset(in: pattern), + end: group.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "misc", key: "condition") + ) + ) - depth += 1 - for node in group.children { - emitNode(node) - } - - depth -= 1 + for node in group.children { + emitNode(node) + } } - private mutating func emitConditional(_ conditional: AST.Conditional) { - let category: String - let key: String - var substitution = [String: String]() - - switch conditional.condition.kind { - case .groupMatched(let ref): - switch ref.kind { - case .absolute(let n): - category = "other" - key = "conditionalgroup" - substitution = ["{{name}}": "\(n)"] - case .relative(let n): - category = "other" - key = "conditionalgroup" - substitution = ["{{name}}": "\(n)"] - case .named(let name): - category = "other" - key = "conditionalgroup" - substitution = ["{{name}}": "\(name)"] - } - - tokens.append( - Token( - classes: ["special"], - location: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.condition.location.end.utf16Offset(in: pattern) + 1 - ), - selection: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: category, key: key, substitution: substitution) - ) - ) - case .recursionCheck: - break - case .groupRecursionCheck(_): - tokens.append( - Token( - classes: ["special"], - location: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.condition.location.end.utf16Offset(in: pattern) + 1 - ), - selection: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "other", key: "recursion") - ) - ) - case .defineGroup: - break - case .pcreVersionCheck(_): - break - case .group(let group): - tokens.append( - Token( - classes: ["special"], - location: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.condition.location.start.utf16Offset(in: pattern) - ), - selection: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "other", key: "conditional") - ) - ) - - // Open parenthesis - tokens.append( - Token( - classes: ["special"], - location: Location( - start: group.kind.location.start.utf16Offset(in: pattern), - end: group.kind.location.end.utf16Offset(in: pattern) - ), - selection: Location( - start: group.startPosition.utf16Offset(in: pattern), - end: group.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "misc", key: "condition") - ) - ) - - // Close parenthesis - tokens.append( - Token( - classes: ["special"], - location: Location( - start: group.child.location.end.utf16Offset(in: pattern), - end: group.endPosition.utf16Offset(in: pattern) - ), - selection: Location( - start: group.startPosition.utf16Offset(in: pattern), - end: group.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "misc", key: "condition") - ) - ) - - for node in group.children { - emitNode(node) - } - } - - emitNode(conditional.trueBranch) - - if let pipe = conditional.pipe { - tokens.append( - Token( - classes: ["special"], - location: Location( - start: pipe.start.utf16Offset(in: pattern), - end: pipe.end.utf16Offset(in: pattern) - ), - selection: Location( - start: pipe.start.utf16Offset(in: pattern), - end: pipe.end.utf16Offset(in: pattern) - ), - related: Related( - location: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.endPosition.utf16Offset(in: pattern) - ) - ), - tooltip: Tooltip(category: "misc", key: "conditionalelse") - ) - ) - } - - emitNode(conditional.falseBranch) - - tokens.append( - Token( - classes: ["special"], - location: Location( - start: conditional.falseBranch.location.end.utf16Offset(in: pattern), - end: conditional.endPosition.utf16Offset(in: pattern) - ), - selection: Location( - start: conditional.startPosition.utf16Offset(in: pattern), - end: conditional.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "other", key: "conditional") + emitNode(conditional.trueBranch) + + if let pipe = conditional.pipe { + tokens.append( + Token( + classes: ["special"], + location: Location( + start: pipe.start.utf16Offset(in: pattern), + end: pipe.end.utf16Offset(in: pattern) + ), + selection: Location( + start: pipe.start.utf16Offset(in: pattern), + end: pipe.end.utf16Offset(in: pattern) + ), + related: Related( + location: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.endPosition.utf16Offset(in: pattern) ) + ), + tooltip: Tooltip(category: "misc", key: "conditionalelse") ) + ) } - private mutating func emitQuantification(_ quant: AST.Quantification) { - emitNode(quant.child) - - let substitution: [String: String] - switch quant.amount.value { - case .zeroOrMore: // * - substitution = ["{{getQuant()}}": "0 or more"] - case .oneOrMore: // + - substitution = ["{{getQuant()}}": "1 or more"] - case .zeroOrOne: // ? - substitution = ["{{getQuant()}}": "between 0 and 1"] - case .exactly(let n): // {n} - substitution = ["{{getQuant()}}": String(pattern[n.location.range])] - case .nOrMore(let n): // {n,} - substitution = ["{{getQuant()}}": "\(pattern[n.location.range]) or more"] - case .upToN(let n): // {,n} - substitution = ["{{getQuant()}}": "between 0 and \(pattern[n.location.range]))"] - case .range(let n, let m): // {n,m} - substitution = ["{{getQuant()}}": "between \(pattern[n.location.range]) and \(pattern[m.location.range])"] - } - - tokens.append( - Token( - classes: ["quant"], - location: Location( - start: quant.amount.location.start.utf16Offset(in: pattern), - end: quant.amount.location.end.utf16Offset(in: pattern) - ), - selection: Location( - start: quant.amount.location.start.utf16Offset(in: pattern), - end: quant.amount.location.end.utf16Offset(in: pattern) - ), - related: Related( - location: Location( - start: quant.startPosition.utf16Offset(in: pattern), - end: quant.endPosition.utf16Offset(in: pattern) - ) - ), - tooltip: Tooltip(category: "quants", key: "quant", substitution: substitution) - ) - ) + emitNode(conditional.falseBranch) + + tokens.append( + Token( + classes: ["special"], + location: Location( + start: conditional.falseBranch.location.end.utf16Offset(in: pattern), + end: conditional.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: conditional.startPosition.utf16Offset(in: pattern), + end: conditional.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "other", key: "conditional") + ) + ) + } + + private mutating func emitQuantification(_ quant: AST.Quantification) { + emitNode(quant.child) - switch quant.kind.value { - case .eager: - break - case .reluctant: - tokens.append( - Token( - classes: ["lazy"], - location: Location( - start: quant.kind.location.start.utf16Offset(in: pattern), - end: quant.kind.location.end.utf16Offset(in: pattern) - ), - selection: Location( - start: quant.kind.location.start.utf16Offset(in: pattern), - end: quant.kind.location.end.utf16Offset(in: pattern) - ), - related: Related( - location: Location( - start: quant.amount.location.start.utf16Offset(in: pattern), - end: quant.amount.location.end.utf16Offset(in: pattern) - ) - ), - tooltip: Tooltip(category: "quants", key: "lazy") - ) - ) - case .possessive: - tokens.append( - Token( - classes: ["possessive"], - location: Location( - start: quant.kind.location.start.utf16Offset(in: pattern), - end: quant.kind.location.end.utf16Offset(in: pattern) - ), - selection: Location( - start: quant.kind.location.start.utf16Offset(in: pattern), - end: quant.kind.location.end.utf16Offset(in: pattern) - ), - related: Related( - location: Location( - start: quant.amount.location.start.utf16Offset(in: pattern), - end: quant.amount.location.end.utf16Offset(in: pattern) - ) - ), - tooltip: Tooltip(category: "quants", key: "possessive") - ) - ) - } + let substitution: [String: String] + switch quant.amount.value { + case .zeroOrMore: // * + substitution = ["{{getQuant()}}": "0 or more"] + case .oneOrMore: // + + substitution = ["{{getQuant()}}": "1 or more"] + case .zeroOrOne: // ? + substitution = ["{{getQuant()}}": "between 0 and 1"] + case .exactly(let n): // {n} + substitution = ["{{getQuant()}}": String(pattern[n.location.range])] + case .nOrMore(let n): // {n,} + substitution = ["{{getQuant()}}": "\(pattern[n.location.range]) or more"] + case .upToN(let n): // {,n} + substitution = ["{{getQuant()}}": "between 0 and \(pattern[n.location.range]))"] + case .range(let n, let m): // {n,m} + substitution = ["{{getQuant()}}": "between \(pattern[n.location.range]) and \(pattern[m.location.range])"] } - private mutating func emitQuote(_ quote: AST.Quote) { - tokens.append( - Token( - classes: ["esc"], - location: Location( - start: quote.startPosition.utf16Offset(in: pattern), - end: quote.endPosition.utf16Offset(in: pattern) - ), - selection: Location( - start: quote.startPosition.utf16Offset(in: pattern), - end: quote.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "escchars", key: "escsequence", substitution: ["{{value}}" : quote.literal]) + tokens.append( + Token( + classes: ["quant"], + location: Location( + start: quant.amount.location.start.utf16Offset(in: pattern), + end: quant.amount.location.end.utf16Offset(in: pattern) + ), + selection: Location( + start: quant.amount.location.start.utf16Offset(in: pattern), + end: quant.amount.location.end.utf16Offset(in: pattern) + ), + related: Related( + location: Location( + start: quant.startPosition.utf16Offset(in: pattern), + end: quant.endPosition.utf16Offset(in: pattern) + ) + ), + tooltip: Tooltip(category: "quants", key: "quant", substitution: substitution) + ) + ) + + switch quant.kind.value { + case .eager: + break + case .reluctant: + tokens.append( + Token( + classes: ["lazy"], + location: Location( + start: quant.kind.location.start.utf16Offset(in: pattern), + end: quant.kind.location.end.utf16Offset(in: pattern) + ), + selection: Location( + start: quant.kind.location.start.utf16Offset(in: pattern), + end: quant.kind.location.end.utf16Offset(in: pattern) + ), + related: Related( + location: Location( + start: quant.amount.location.start.utf16Offset(in: pattern), + end: quant.amount.location.end.utf16Offset(in: pattern) ) + ), + tooltip: Tooltip(category: "quants", key: "lazy") ) - } - - private mutating func emitTrivia(_ trivia: AST.Trivia) { - tokens.append( - Token( - classes: ["comment"], - location: Location( - start: trivia.startPosition.utf16Offset(in: pattern), - end: trivia.endPosition.utf16Offset(in: pattern) - ), - selection: Location( - start: trivia.startPosition.utf16Offset(in: pattern), - end: trivia.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "other", key: "comment") + ) + case .possessive: + tokens.append( + Token( + classes: ["possessive"], + location: Location( + start: quant.kind.location.start.utf16Offset(in: pattern), + end: quant.kind.location.end.utf16Offset(in: pattern) + ), + selection: Location( + start: quant.kind.location.start.utf16Offset(in: pattern), + end: quant.kind.location.end.utf16Offset(in: pattern) + ), + related: Related( + location: Location( + start: quant.amount.location.start.utf16Offset(in: pattern), + end: quant.amount.location.end.utf16Offset(in: pattern) ) + ), + tooltip: Tooltip(category: "quants", key: "possessive") ) + ) + } + } + + private mutating func emitQuote(_ quote: AST.Quote) { + tokens.append( + Token( + classes: ["esc"], + location: Location( + start: quote.startPosition.utf16Offset(in: pattern), + end: quote.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: quote.startPosition.utf16Offset(in: pattern), + end: quote.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "escchars", key: "escsequence", substitution: ["{{value}}" : quote.literal]) + ) + ) + } + + private mutating func emitTrivia(_ trivia: AST.Trivia) { + tokens.append( + Token( + classes: ["comment"], + location: Location( + start: trivia.startPosition.utf16Offset(in: pattern), + end: trivia.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: trivia.startPosition.utf16Offset(in: pattern), + end: trivia.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "other", key: "comment") + ) + ) + } + + private mutating func emitInterpolation(_ interpolation: AST.Interpolation) { + tokens.append( + Token( + classes: ["interpolation"], + location: Location( + start: interpolation.startPosition.utf16Offset(in: pattern), + end: interpolation.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: interpolation.startPosition.utf16Offset(in: pattern), + end: interpolation.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: "other", key: "interpolation") + ) + ) + } + + private mutating func emitAtom(_ atom: AST.Atom) { + let `class`: String + let category: String + let key: String + var substitution = [String: String]() + + switch atom.kind { + case .char(let c): + let charcode = c.unicodeScalars.map { String(format: "U+%X", $0.value) }.joined(separator: " ") + + let value = String(pattern[atom.location.range]) + if value.hasPrefix("\\") { + `class` = "esc" + category = "misc" + key = "escchar" + substitution = [ + "{{getChar()}}": #""\#(c)""#, + "{{code}}": charcode, + "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" + ] + } else { + `class` = "char" + category = "misc" + key = "char" + substitution = [ + "{{getChar()}}": #""\#(c)""#, + "{{code}}": charcode, + "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" + ] + } + case .scalar(let scalar): + `class` = "char" + category = "misc" + key = "char" + substitution = [ + "{{getChar()}}": #""\#(String(scalar.value))""#, + "{{code}}": String(format: "U+%X", scalar.value.value), + "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" + ] + case .scalarSequence(let scalarSequence): + let scalars = scalarSequence.scalars + let value = scalars.map { String($0.value) }.joined() + let charcode = scalars.map { String(format: "U+%X", $0.value.value) }.joined(separator: " ") + + `class` = "char" + category = "misc" + key = "char" + substitution = [ + "{{getChar()}}": #""\#(value)""#, + "{{code}}": charcode, + "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" + ] + case .property(let prop): + `class` = "charclass" + + switch prop.kind { + case .any: + category = "misc" + key = "any" + case .assigned: + category = "misc" + key = "assigned" + case .ascii: + category = "misc" + key = "ascii" + case .generalCategory(let cat): + let uniCat: String + switch cat { + case .other: + uniCat = "Other" + case .control: + uniCat = "Control" + case .format: + uniCat = "Format" + case .unassigned: + uniCat = "Unassigned" + case .privateUse: + uniCat = "Private use" + case .surrogate: + uniCat = "Surrogate" + case .letter: + uniCat = "Letter" + case .casedLetter: + uniCat = "Cased letter" + case .lowercaseLetter: + uniCat = "Lower case letter" + case .modifierLetter: + uniCat = "Modifier letter" + case .otherLetter: + uniCat = "Other letter" + case .titlecaseLetter: + uniCat = "Title case letter" + case .uppercaseLetter: + uniCat = "Upper case letter" + case .mark: + uniCat = "Mark" + case .spacingMark: + uniCat = "Spacing mark" + case .enclosingMark: + uniCat = "Enclosing mark" + case .nonspacingMark: + uniCat = "Non-spacing mark" + case .number: + uniCat = "Number" + case .decimalNumber: + uniCat = "Decimal number" + case .letterNumber: + uniCat = "Letter number" + case .otherNumber: + uniCat = "Other number" + case .punctuation: + uniCat = "Punctuation" + case .connectorPunctuation: + uniCat = "Connector punctuation" + case .dashPunctuation: + uniCat = "Dash punctuation" + case .closePunctuation: + uniCat = "Close punctuation" + case .finalPunctuation: + uniCat = "Final punctuation" + case .initialPunctuation: + uniCat = "Initial punctuation" + case .otherPunctuation: + uniCat = "Other punctuation" + case .openPunctuation: + uniCat = "Open punctuation" + case .symbol: + uniCat = "Symbol" + case .currencySymbol: + uniCat = "Currency symbol" + case .modifierSymbol: + uniCat = "Modifier symbol" + case .mathSymbol: + uniCat = "Mathematical symbol" + case .otherSymbol: + uniCat = "Other symbol" + case .separator: + uniCat = "Separator" + case .lineSeparator: + uniCat = "Line separator" + case .paragraphSeparator: + uniCat = "Paragraph separator" + case .spaceSeparator: + uniCat = "Space separator" + } + category = "charclasses" + key = "unicodecat" + substitution = ["{{getUniCat()}}": uniCat] + case .binary(let property, value: let value): + switch property { + case .asciiHexDigit: + break + case .alphabetic: + break + case .bidiControl: + break + case .bidiMirrored: + break + case .cased: + break + case .compositionExclusion: + break + case .caseIgnorable: + break + case .changesWhenCasefolded: + break + case .changesWhenCasemapped: + break + case .changesWhenNFKCCasefolded: + break + case .changesWhenLowercased: + break + case .changesWhenTitlecased: + break + case .changesWhenUppercased: + break + case .dash: + break + case .deprecated: + break + case .defaultIgnorableCodePoint: + break + case .diacratic: + break + case .emojiModifierBase: + break + case .emojiComponent: + break + case .emojiModifier: + break + case .emoji: + break + case .emojiPresentation: + break + case .extender: + break + case .extendedPictographic: + break + case .fullCompositionExclusion: + break + case .graphemeBase: + break + case .graphemeExtended: + break + case .graphemeLink: + break + case .hexDigit: + break + case .hyphen: + break + case .idContinue: + break + case .ideographic: + break + case .idStart: + break + case .idsBinaryOperator: + break + case .idsTrinaryOperator: + break + case .joinControl: + break + case .logicalOrderException: + break + case .lowercase: + break + case .math: + break + case .noncharacterCodePoint: + break + case .otherAlphabetic: + break + case .otherDefaultIgnorableCodePoint: + break + case .otherGraphemeExtended: + break + case .otherIDContinue: + break + case .otherIDStart: + break + case .otherLowercase: + break + case .otherMath: + break + case .otherUppercase: + break + case .patternSyntax: + break + case .patternWhitespace: + break + case .prependedConcatenationMark: + break + case .quotationMark: + break + case .radical: + break + case .regionalIndicator: + break + case .softDotted: + break + case .sentenceTerminal: + break + case .terminalPunctuation: + break + case .unifiedIdiograph: + break + case .uppercase: + break + case .variationSelector: + break + case .whitespace: + break + case .xidContinue: + break + case .xidStart: + break + case .expandsOnNFC: + break + case .expandsOnNFD: + break + case .expandsOnNFKC: + break + case .expandsOnNFKD: + break + } + category = "charclasses" + key = "binary" + case .script(_): + category = "charclasses" + key = "script" + case .scriptExtension(_): + category = "charclasses" + key = "scriptextension" + case .named(_): + category = "charclasses" + key = "named" + case .numericType(_): + category = "charclasses" + key = "numerictype" + case .numericValue(_): + category = "charclasses" + key = "numericvalue" + case .mapping(_, _): + category = "charclasses" + key = "mapping" + case .ccc(_): + category = "charclasses" + key = "ccc" + case .age(major: let major, minor: let minor): + category = "charclasses" + key = "age" + case .block(_): + category = "charclasses" + key = "block" + case .posix(let property): + switch property { + case .alnum: + break + case .blank: + break + case .graph: + break + case .print: + break + case .word: + break + case .xdigit: + break + } + category = "charclasses" + key = "posixcharclass" + substitution = ["{{value}}": "\(property)"] + case .pcreSpecial(_): + category = "pcreSpecial" + key = "pcrespecial" + case .javaSpecial(_): + category = "javaSpecial" + key = "javaspecial" + case .invalid(key: let k, value: let v): + category = "charclasses" + key = "invalid" + } + case .escaped(let escaped): + switch escaped { + case .alarm: + `class` = "esc" + category = "misc" + key = "escchar" + substitution = ["{{getChar()}}": "ALARM", "{{code}}": "(bell, 0x07)"] + case .escape: + `class` = "esc" + category = "misc" + key = "escchar" + substitution = ["{{getChar()}}": "ESCAPE", "{{code}}": "(escape, 0x1B)"] + case .formfeed: + `class` = "esc" + category = "misc" + key = "escchar" + substitution = ["{{getChar()}}": "FORM FEED", "{{code}}": "(form feed, 0x0C)"] + case .newline: + `class` = "esc" + category = "misc" + key = "escchar" + substitution = ["{{getChar()}}": "LINE FEED", "{{code}}": "(ASCII 0x0A)"] + case .carriageReturn: + `class` = "esc" + category = "misc" + key = "escchar" + substitution = ["{{getChar()}}": "CARRIAGE RETURN", "{{code}}": "(ASCII 0x0D)"] + case .tab: + `class` = "esc" + category = "misc" + key = "escchar" + substitution = ["{{getChar()}}": "TAB", "{{code}}": "(ASCII 0x09)"] + case .singleDataUnit: + `class` = "esc" + category = "misc" + key = "escchar" + substitution = ["{{getChar()}}": "SINGLE DATA UNIT", "{{code}}": "N/A"] + case .decimalDigit: + `class` = "charclass" + category = "charclasses" + key = "digit" + case .notDecimalDigit: + `class` = "charclass" + category = "charclasses" + key = "notdigit" + case .horizontalWhitespace: + `class` = "charclass" + category = "charclasses" + key = "hwhitespace" + case .notHorizontalWhitespace: + `class` = "charclass" + category = "charclasses" + key = "nothwhitespace" + case .notNewline: + `class` = "charclass" + category = "charclasses" + key = "notlinebreak" + case .newlineSequence: + `class` = "charclass" + category = "charclasses" + key = "linebreak" + case .whitespace: + `class` = "charclass" + category = "charclasses" + key = "whitespace" + case .notWhitespace: + `class` = "charclass" + category = "charclasses" + key = "notwhitespace" + case .verticalTab: + `class` = "charclass" + category = "charclasses" + key = "vwhitespace" + case .notVerticalTab: + `class` = "charclass" + category = "charclasses" + key = "notvwhitespace" + case .wordCharacter: + `class` = "charclass" + category = "charclasses" + key = "word" + case .notWordCharacter: + `class` = "charclass" + category = "charclasses" + key = "notword" + case .backspace: + `class` = "anchor" + category = "charclasses" + key = "wordboundary" + case .graphemeCluster: + `class` = "charclass" + category = "charclasses" + key = "graphemecluster" + case .wordBoundary: + `class` = "anchor" + category = "anchors" + key = "wordboundary" + case .notWordBoundary: + `class` = "anchor" + category = "anchors" + key = "notwordboundary" + case .startOfSubject: + `class` = "anchor" + category = "anchors" + key = "bos" + case .endOfSubjectBeforeNewline: + `class` = "anchor" + category = "anchors" + key = "eos" + case .endOfSubject: + `class` = "anchor" + category = "anchors" + key = "abseos" + case .firstMatchingPositionInSubject: + `class` = "anchor" + category = "anchors" + key = "prevmatchend" + case .resetStartOfMatch: + `class` = "charclass" + category = "lookaround" + key = "keepout" + case .trueAnychar: + `class` = "charclass" + category = "charclass" + key = "trueanychar" + case .textSegment: + `class` = "charclass" + category = "charclass" + key = "textsegment" + case .notTextSegment: + `class` = "charclass" + category = "charclass" + key = "nottextsegment" + } + case .keyboardControl(_): + `class` = "charclass" + category = "charclass" + key = "keyboardcontrol" + case .keyboardMeta(_): + `class` = "charclass" + category = "charclass" + key = "keyboardmeta" + case .keyboardMetaControl(_): + `class` = "charclass" + category = "charclass" + key = "keyboardmetacontrol" + case .namedCharacter(_): + `class` = "charclass" + category = "charclass" + key = "namedcharacter" + case .dot: + `class` = "charclass" + category = "charclasses" + key = "dot" + case .caretAnchor: + `class` = "anchor" + category = "anchors" + key = "bof" + case .dollarAnchor: + `class` = "anchor" + category = "anchors" + key = "eof" + case .backreference(let ref): + `class` = "ref" + switch ref.kind { + case .absolute(let n): + category = "groups" + key = "numref" + substitution = ["{{group.num}}": "\(n)"] + case .relative(let n): + category = "groups" + key = "numref" + substitution = ["{{group.num}}": "\(n)"] + case .named(let name): + category = "groups" + key = "namedref" + substitution = ["{{group.name}}": name] + } + case .subpattern(let ref): + if ref.kind.recursesWholePattern { + `class` = "special" + category = "other" + key = "recursion" + } else { + `class` = "charclass" + category = "charclasses" + key = "subpattern" + } + case .callout(_): + `class` = "charclass" + category = "charclasses" + key = "callout" + case .backtrackingDirective(let directive): + `class` = "charclass" + + switch directive.kind.value { + case .accept: + category = "charclass" + key = "accept" + case .fail: + category = "charclass" + key = "fail" + case .mark: + category = "charclass" + key = "mark" + case .commit: + category = "charclass" + key = "commit" + case .prune: + category = "charclass" + key = "skip" + case .skip: + category = "charclass" + key = "skip" + case .then: + category = "charclass" + key = "then" + } + case .changeMatchingOptions(let matchingOptionSequence): + var set = Set() + let removing = matchingOptionSequence.removing.filter { set.insert($0).inserted } + + set = Set() + let adding = matchingOptionSequence.adding.filter { set.insert($0).inserted }.filter { !removing.contains($0) } + + let enable = adding.map { pattern[$0.location.range] } + let disable = removing.map { pattern[$0.location.range] } + var modes = "" + if !enable.isEmpty { + modes += #" Enable "\#(enable.joined())"."# + } + if !disable.isEmpty { + modes += #" Disable "\#(disable.joined())"."# + } + + `class` = "special" + category = "other" + key = "mode" + substitution = [ + "{{~getDesc()}}": "Enables or disables modes for the remainder of the expression.", + "{{~getModes()}}": modes, + ] + case .invalid: + `class` = "charclass" + category = "charclasses" + key = "invalid" } - private mutating func emitInterpolation(_ interpolation: AST.Interpolation) { - tokens.append( - Token( - classes: ["interpolation"], - location: Location( - start: interpolation.startPosition.utf16Offset(in: pattern), - end: interpolation.endPosition.utf16Offset(in: pattern) - ), - selection: Location( - start: interpolation.startPosition.utf16Offset(in: pattern), - end: interpolation.endPosition.utf16Offset(in: pattern) - ), - tooltip: Tooltip(category: "other", key: "interpolation") - ) - ) + tokens.append( + Token( + classes: [`class`], + location: Location( + start: atom.startPosition.utf16Offset(in: pattern), + end: atom.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: atom.startPosition.utf16Offset(in: pattern), + end: atom.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: category, key: key, substitution: substitution) + ) + ) + } + + private mutating func emitCustomCharacterClass(_ ccc: AST.CustomCharacterClass) { + let category: String + let key: String + + switch ccc.start.value { + case .normal: + category = "charclasses" + key = "set" + case .inverted: + category = "charclasses" + key = "setnot" } - private mutating func emitAtom(_ atom: AST.Atom) { - let `class`: String - let category: String - let key: String - var substitution = [String: String]() - - switch atom.kind { + tokens.append( + Token( + classes: ["set"], + location: Location( + start: ccc.start.location.start.utf16Offset(in: pattern), + end: ccc.start.location.end.utf16Offset(in: pattern) + ), + selection: Location( + start: ccc.startPosition.utf16Offset(in: pattern), + end: ccc.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: category, key: key) + ) + ) + + tokens.append( + Token( + classes: ["group-set"], + location: Location( + start: ccc.startPosition.utf16Offset(in: pattern), + end: ccc.endPosition.utf16Offset(in: pattern) + ), + selection: Location( + start: ccc.startPosition.utf16Offset(in: pattern), + end: ccc.endPosition.utf16Offset(in: pattern) + ), + tooltip: Tooltip(category: category, key: key) + ) + ) + + for member in ccc.members { + switch member { + case .custom(let custom): + emitCustomCharacterClass(custom) + case .range(let range): + let lhs: String + let rhs: String + let dash = String(pattern[range.dashLoc.range]) + + switch range.lhs.kind { case .char(let c): - let charcode = c.unicodeScalars.map { String(format: "U+%X", $0.value) }.joined(separator: " ") - - let value = String(pattern[atom.location.range]) - if value.hasPrefix("\\") { - `class` = "esc" - category = "misc" - key = "escchar" - substitution = [ - "{{getChar()}}": #""\#(c)""#, - "{{code}}": charcode, - "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" - ] - } else { - `class` = "char" - category = "misc" - key = "char" - substitution = [ - "{{getChar()}}": #""\#(c)""#, - "{{code}}": charcode, - "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" - ] - } + lhs = String(c) case .scalar(let scalar): - `class` = "char" - category = "misc" - key = "char" - substitution = [ - "{{getChar()}}": #""\#(String(scalar.value))""#, - "{{code}}": String(format: "U+%X", scalar.value.value), - "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" - ] + lhs = String(scalar.value) case .scalarSequence(let scalarSequence): - let scalars = scalarSequence.scalars - let value = scalars.map { String($0.value) }.joined() - let charcode = scalars.map { String(format: "U+%X", $0.value.value) }.joined(separator: " ") - - `class` = "char" - category = "misc" - key = "char" - substitution = [ - "{{getChar()}}": #""\#(value)""#, - "{{code}}": charcode, - "{{getInsensitive()}}": "Case \(insensitive ? "in" : "")sensitive" - ] - case .property(let prop): - `class` = "charclass" - - switch prop.kind { - case .any: - category = "misc" - key = "any" - case .assigned: - category = "misc" - key = "assigned" - case .ascii: - category = "misc" - key = "ascii" - case .generalCategory(let cat): - let uniCat: String - switch cat { - case .other: - uniCat = "Other" - case .control: - uniCat = "Control" - case .format: - uniCat = "Format" - case .unassigned: - uniCat = "Unassigned" - case .privateUse: - uniCat = "Private use" - case .surrogate: - uniCat = "Surrogate" - case .letter: - uniCat = "Letter" - case .casedLetter: - uniCat = "Cased letter" - case .lowercaseLetter: - uniCat = "Lower case letter" - case .modifierLetter: - uniCat = "Modifier letter" - case .otherLetter: - uniCat = "Other letter" - case .titlecaseLetter: - uniCat = "Title case letter" - case .uppercaseLetter: - uniCat = "Upper case letter" - case .mark: - uniCat = "Mark" - case .spacingMark: - uniCat = "Spacing mark" - case .enclosingMark: - uniCat = "Enclosing mark" - case .nonspacingMark: - uniCat = "Non-spacing mark" - case .number: - uniCat = "Number" - case .decimalNumber: - uniCat = "Decimal number" - case .letterNumber: - uniCat = "Letter number" - case .otherNumber: - uniCat = "Other number" - case .punctuation: - uniCat = "Punctuation" - case .connectorPunctuation: - uniCat = "Connector punctuation" - case .dashPunctuation: - uniCat = "Dash punctuation" - case .closePunctuation: - uniCat = "Close punctuation" - case .finalPunctuation: - uniCat = "Final punctuation" - case .initialPunctuation: - uniCat = "Initial punctuation" - case .otherPunctuation: - uniCat = "Other punctuation" - case .openPunctuation: - uniCat = "Open punctuation" - case .symbol: - uniCat = "Symbol" - case .currencySymbol: - uniCat = "Currency symbol" - case .modifierSymbol: - uniCat = "Modifier symbol" - case .mathSymbol: - uniCat = "Mathematical symbol" - case .otherSymbol: - uniCat = "Other symbol" - case .separator: - uniCat = "Separator" - case .lineSeparator: - uniCat = "Line separator" - case .paragraphSeparator: - uniCat = "Paragraph separator" - case .spaceSeparator: - uniCat = "Space separator" - } - category = "charclasses" - key = "unicodecat" - substitution = ["{{getUniCat()}}": uniCat] - case .binary(let property, value: let value): - switch property { - case .asciiHexDigit: - break - case .alphabetic: - break - case .bidiControl: - break - case .bidiMirrored: - break - case .cased: - break - case .compositionExclusion: - break - case .caseIgnorable: - break - case .changesWhenCasefolded: - break - case .changesWhenCasemapped: - break - case .changesWhenNFKCCasefolded: - break - case .changesWhenLowercased: - break - case .changesWhenTitlecased: - break - case .changesWhenUppercased: - break - case .dash: - break - case .deprecated: - break - case .defaultIgnorableCodePoint: - break - case .diacratic: - break - case .emojiModifierBase: - break - case .emojiComponent: - break - case .emojiModifier: - break - case .emoji: - break - case .emojiPresentation: - break - case .extender: - break - case .extendedPictographic: - break - case .fullCompositionExclusion: - break - case .graphemeBase: - break - case .graphemeExtended: - break - case .graphemeLink: - break - case .hexDigit: - break - case .hyphen: - break - case .idContinue: - break - case .ideographic: - break - case .idStart: - break - case .idsBinaryOperator: - break - case .idsTrinaryOperator: - break - case .joinControl: - break - case .logicalOrderException: - break - case .lowercase: - break - case .math: - break - case .noncharacterCodePoint: - break - case .otherAlphabetic: - break - case .otherDefaultIgnorableCodePoint: - break - case .otherGraphemeExtended: - break - case .otherIDContinue: - break - case .otherIDStart: - break - case .otherLowercase: - break - case .otherMath: - break - case .otherUppercase: - break - case .patternSyntax: - break - case .patternWhitespace: - break - case .prependedConcatenationMark: - break - case .quotationMark: - break - case .radical: - break - case .regionalIndicator: - break - case .softDotted: - break - case .sentenceTerminal: - break - case .terminalPunctuation: - break - case .unifiedIdiograph: - break - case .uppercase: - break - case .variationSelector: - break - case .whitespace: - break - case .xidContinue: - break - case .xidStart: - break - case .expandsOnNFC: - break - case .expandsOnNFD: - break - case .expandsOnNFKC: - break - case .expandsOnNFKD: - break - } - category = "charclasses" - key = "binary" - case .script(_): - category = "charclasses" - key = "script" - case .scriptExtension(_): - category = "charclasses" - key = "scriptextension" - case .named(_): - category = "charclasses" - key = "named" - case .numericType(_): - category = "charclasses" - key = "numerictype" - case .numericValue(_): - category = "charclasses" - key = "numericvalue" - case .mapping(_, _): - category = "charclasses" - key = "mapping" - case .ccc(_): - category = "charclasses" - key = "ccc" - case .age(major: let major, minor: let minor): - category = "charclasses" - key = "age" - case .block(_): - category = "charclasses" - key = "block" - case .posix(let property): - switch property { - case .alnum: - break - case .blank: - break - case .graph: - break - case .print: - break - case .word: - break - case .xdigit: - break - } - category = "charclasses" - key = "posixcharclass" - substitution = ["{{value}}": "\(property)"] - case .pcreSpecial(_): - category = "pcreSpecial" - key = "pcrespecial" - case .javaSpecial(_): - category = "javaSpecial" - key = "javaspecial" - case .invalid(key: let k, value: let v): - category = "charclasses" - key = "invalid" - } - case .escaped(let escaped): - switch escaped { - case .alarm: - `class` = "esc" - category = "misc" - key = "escchar" - substitution = ["{{getChar()}}": "ALARM", "{{code}}": "(bell, 0x07)"] - case .escape: - `class` = "esc" - category = "misc" - key = "escchar" - substitution = ["{{getChar()}}": "ESCAPE", "{{code}}": "(escape, 0x1B)"] - case .formfeed: - `class` = "esc" - category = "misc" - key = "escchar" - substitution = ["{{getChar()}}": "FORM FEED", "{{code}}": "(form feed, 0x0C)"] - case .newline: - `class` = "esc" - category = "misc" - key = "escchar" - substitution = ["{{getChar()}}": "LINE FEED", "{{code}}": "(ASCII 0x0A)"] - case .carriageReturn: - `class` = "esc" - category = "misc" - key = "escchar" - substitution = ["{{getChar()}}": "CARRIAGE RETURN", "{{code}}": "(ASCII 0x0D)"] - case .tab: - `class` = "esc" - category = "misc" - key = "escchar" - substitution = ["{{getChar()}}": "TAB", "{{code}}": "(ASCII 0x09)"] - case .singleDataUnit: - `class` = "esc" - category = "misc" - key = "escchar" - substitution = ["{{getChar()}}": "SINGLE DATA UNIT", "{{code}}": "N/A"] - case .decimalDigit: - `class` = "charclass" - category = "charclasses" - key = "digit" - case .notDecimalDigit: - `class` = "charclass" - category = "charclasses" - key = "notdigit" - case .horizontalWhitespace: - `class` = "charclass" - category = "charclasses" - key = "hwhitespace" - case .notHorizontalWhitespace: - `class` = "charclass" - category = "charclasses" - key = "nothwhitespace" - case .notNewline: - `class` = "charclass" - category = "charclasses" - key = "notlinebreak" - case .newlineSequence: - `class` = "charclass" - category = "charclasses" - key = "linebreak" - case .whitespace: - `class` = "charclass" - category = "charclasses" - key = "whitespace" - case .notWhitespace: - `class` = "charclass" - category = "charclasses" - key = "notwhitespace" - case .verticalTab: - `class` = "charclass" - category = "charclasses" - key = "vwhitespace" - case .notVerticalTab: - `class` = "charclass" - category = "charclasses" - key = "notvwhitespace" - case .wordCharacter: - `class` = "charclass" - category = "charclasses" - key = "word" - case .notWordCharacter: - `class` = "charclass" - category = "charclasses" - key = "notword" - case .backspace: - `class` = "anchor" - category = "charclasses" - key = "wordboundary" - case .graphemeCluster: - `class` = "charclass" - category = "charclasses" - key = "graphemecluster" - case .wordBoundary: - `class` = "anchor" - category = "anchors" - key = "wordboundary" - case .notWordBoundary: - `class` = "anchor" - category = "anchors" - key = "notwordboundary" - case .startOfSubject: - `class` = "anchor" - category = "anchors" - key = "bos" - case .endOfSubjectBeforeNewline: - `class` = "anchor" - category = "anchors" - key = "eos" - case .endOfSubject: - `class` = "anchor" - category = "anchors" - key = "abseos" - case .firstMatchingPositionInSubject: - `class` = "anchor" - category = "anchors" - key = "prevmatchend" - case .resetStartOfMatch: - `class` = "charclass" - category = "lookaround" - key = "keepout" - case .trueAnychar: - `class` = "charclass" - category = "charclass" - key = "trueanychar" - case .textSegment: - `class` = "charclass" - category = "charclass" - key = "textsegment" - case .notTextSegment: - `class` = "charclass" - category = "charclass" - key = "nottextsegment" - } + lhs = String(scalarSequence.scalarValues) + case .property(_): + lhs = String(pattern[range.lhs.startPosition..() - let removing = matchingOptionSequence.removing.filter { set.insert($0).inserted } - - set = Set() - let adding = matchingOptionSequence.adding.filter { set.insert($0).inserted }.filter { !removing.contains($0) } - - let enable = adding.map { pattern[$0.location.range] } - let disable = removing.map { pattern[$0.location.range] } - var modes = "" - if !enable.isEmpty { - modes += #" Enable "\#(enable.joined())"."# - } - if !disable.isEmpty { - modes += #" Disable "\#(disable.joined())"."# - } - - `class` = "special" - category = "other" - key = "mode" - substitution = [ - "{{~getDesc()}}": "Enables or disables modes for the remainder of the expression.", - "{{~getModes()}}": modes, - ] + lhs = String(pattern[range.lhs.startPosition.. [Match] { - let regex = try Regex(pattern) - .anchorsMatchLineEndings(matchingOptions.contains("m")) - .ignoresCase(matchingOptions.contains("i")) - .dotMatchesNewlines(matchingOptions.contains("s")) - .asciiOnlyWordCharacters(matchingOptions.contains("asciiOnlyWordCharacters")) - .asciiOnlyDigits(matchingOptions.contains("asciiOnlyDigits")) - .asciiOnlyWhitespace(matchingOptions.contains("asciiOnlyWhitespace")) - .asciiOnlyCharacterClasses(matchingOptions.contains("asciiOnlyCharacterClasses")) + static func match(pattern: String, text: String, matchingOptions: [String] = []) throws -> [Match] { + let regex = try Regex(pattern) + .anchorsMatchLineEndings(matchingOptions.contains("m")) + .ignoresCase(matchingOptions.contains("i")) + .dotMatchesNewlines(matchingOptions.contains("s")) + .asciiOnlyWordCharacters(matchingOptions.contains("asciiOnlyWordCharacters")) + .asciiOnlyDigits(matchingOptions.contains("asciiOnlyDigits")) + .asciiOnlyWhitespace(matchingOptions.contains("asciiOnlyWhitespace")) + .asciiOnlyCharacterClasses(matchingOptions.contains("asciiOnlyCharacterClasses")) - let matches = matchingOptions.contains("g") ? text.matches(of: regex) : text.firstMatch(of: regex).flatMap { [$0] } ?? [] - return matches.map { - let captures: [Group] = $0.lazy.elements.dropFirst().map { - if let range = $0.range { - return Group( - location: Location( - start: range.lowerBound.utf16Offset(in: text), - end: range.upperBound.utf16Offset(in: text) - ), - value: String(text[range]) - ) - } else { - return Group( - location: nil, - value: nil - ) - } - } - return Match( - location: Location( - start: $0.range.lowerBound.utf16Offset(in: text), - end: $0.range.upperBound.utf16Offset(in: text) - ), - value: String(text[$0.range]), - captures: captures - ) - } + let matches = matchingOptions.contains("g") ? text.matches(of: regex) : text.firstMatch(of: regex).flatMap { [$0] } ?? [] + return matches.map { + let captures: [Group] = $0.lazy.elements.dropFirst().map { + if let range = $0.range { + return Group( + location: Location( + start: range.lowerBound.utf16Offset(in: text), + end: range.upperBound.utf16Offset(in: text) + ), + value: String(text[range]) + ) + } else { + return Group( + location: nil, + value: nil + ) + } + } + return Match( + location: Location( + start: $0.range.lowerBound.utf16Offset(in: text), + end: $0.range.upperBound.utf16Offset(in: text) + ), + value: String(text[$0.range]), + captures: captures + ) } + } } struct Match: Codable { - let location: Location - let value: String + let location: Location + let value: String - let captures: [Group] + let captures: [Group] } struct Group: Codable { - let location: Location? - let value: String? + let location: Location? + let value: String? } struct Location: Codable { - let start: Int - let end: Int + let start: Int + let end: Int } diff --git a/Tests/RegexTests/ConverterTests.swift b/Tests/RegexTests/ConverterTests.swift index ef1f099..905bea4 100644 --- a/Tests/RegexTests/ConverterTests.swift +++ b/Tests/RegexTests/ConverterTests.swift @@ -3,16 +3,16 @@ import XCTest @testable import DSLConverter class ConverterTests: XCTestCase { - func testConvertPattern() throws { - do { - let converter = DSLConverter() - let builderDSL = try converter.convert(#"gray|grey"#) - print(builderDSL) - } - do { - let converter = DSLConverter() - let builderDSL = try converter.convert(#"\b(?:[a-eg-z]|f(?!oo))\w*\b"#) - print(builderDSL) - } + func testConvertPattern() throws { + do { + let converter = DSLConverter() + let builderDSL = try converter.convert(#"gray|grey"#) + print(builderDSL) } + do { + let converter = DSLConverter() + let builderDSL = try converter.convert(#"\b(?:[a-eg-z]|f(?!oo))\w*\b"#) + print(builderDSL) + } + } } diff --git a/Tests/RegexTests/ExpressionParserTests.swift b/Tests/RegexTests/ExpressionParserTests.swift index b4160d4..78ad4e2 100644 --- a/Tests/RegexTests/ExpressionParserTests.swift +++ b/Tests/RegexTests/ExpressionParserTests.swift @@ -3,186 +3,186 @@ import XCTest @testable import ExpressionParser class ParserTests: XCTestCase { - func testParseExpression() throws { - do { - var parser = ExpressionParser(pattern: #"a(?R)?b"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"\d+(?(?=regex)then|else(?(?=regex)then|else))(a)^(START)?\d+(?(1)END|\b)"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"^[^<>]*(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))$"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"hello"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"gray|grey"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"gr(a|e)y"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"gr[ae]y"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"colou?r"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"rege(x(es)?|xps?)"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"go*gle"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"go+gle"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"g(oog)+le"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"z{3}"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"z{3,6}"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"z{3,}"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"[Bb]rainf\*\*k"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"\d"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"\d+"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"\d{5}(-\d{4})?"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"1\d{10}"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"[2-9]|[12]\d|3[0-6]"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"Hello\nworld"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"mi.....ft"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"\d+(\.\d\d)?"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"[^i*&2@]"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"//[^\r\n]*[\r\n]"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"^dog"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"dog$"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"^dog$"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"\w++\d\d\w+"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"<(\w+)>[^<]*"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"Hillary(?=\s+Clinton)"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"q(?!u)"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"(?<=-)\p{L}+"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"[\x41-\x45]{3}"#) - try parser.parse() - print(parser.tokens) - } - do { - var parser = ExpressionParser(pattern: #"(?(?=regex)then|else)"#) - try parser.parse() - print(parser.tokens) - } + func testParseExpression() throws { + do { + var parser = ExpressionParser(pattern: #"a(?R)?b"#) + try parser.parse() + print(parser.tokens) } + do { + var parser = ExpressionParser(pattern: #"\d+(?(?=regex)then|else(?(?=regex)then|else))(a)^(START)?\d+(?(1)END|\b)"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"^[^<>]*(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))$"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"hello"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"gray|grey"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"gr(a|e)y"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"gr[ae]y"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"colou?r"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"rege(x(es)?|xps?)"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"go*gle"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"go+gle"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"g(oog)+le"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"z{3}"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"z{3,6}"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"z{3,}"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"[Bb]rainf\*\*k"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"\d"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"\d+"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"\d{5}(-\d{4})?"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"1\d{10}"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"[2-9]|[12]\d|3[0-6]"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"Hello\nworld"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"mi.....ft"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"\d+(\.\d\d)?"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"[^i*&2@]"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"//[^\r\n]*[\r\n]"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"^dog"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"dog$"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"^dog$"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"\w++\d\d\w+"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"<(\w+)>[^<]*"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"Hillary(?=\s+Clinton)"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"q(?!u)"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"(?<=-)\p{L}+"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"[\x41-\x45]{3}"#) + try parser.parse() + print(parser.tokens) + } + do { + var parser = ExpressionParser(pattern: #"(?(?=regex)then|else)"#) + try parser.parse() + print(parser.tokens) + } + } } diff --git a/Tests/RegexTests/MatcherTests.swift b/Tests/RegexTests/MatcherTests.swift index c162a95..b4bd13d 100644 --- a/Tests/RegexTests/MatcherTests.swift +++ b/Tests/RegexTests/MatcherTests.swift @@ -3,19 +3,19 @@ import XCTest @testable import Matcher class MatchTest: XCTestCase { - func testMatch() throws { - do { - let pattern = #"[A-Z]\w+"# - let text = """ + func testMatch() throws { + do { + let pattern = #"[A-Z]\w+"# + let text = """ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. """ - let matches = try Matcher.match(pattern: pattern, text: text) - print(matches) - } - do { - let pattern = #"\d+"# - let text = """ + let matches = try Matcher.match(pattern: pattern, text: text) + print(matches) + } + do { + let pattern = #"\d+"# + let text = """ KIND DATE INSTITUTION AMOUNT ---------------------------------------------------------------- CREDIT 03/01/2022 Payroll from employer $200.23 @@ -25,17 +25,17 @@ class MatchTest: XCTestCase { DEBIT 06/03/2022 Oxford Comma Supply Ltd. £57.33 """ - let matches = try Matcher.match(pattern: pattern, text: text) - print(matches) - } - do { - let pattern = #"((\d{3})(?:\.|-))?(\d{3})(?:\.|-)(\d{4})"# - let text = """ + let matches = try Matcher.match(pattern: pattern, text: text) + print(matches) + } + do { + let pattern = #"((\d{3})(?:\.|-))?(\d{3})(?:\.|-)(\d{4})"# + let text = """ Call 555-1212 for info """ - let matches = try Matcher.match(pattern: pattern, text: text) - print(matches) - } + let matches = try Matcher.match(pattern: pattern, text: text) + print(matches) } + } }