Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Foundation scanner implementation #216

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Sources/XCLogParser/lexer/Index+Offset.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2019 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import Foundation

extension String.Index {
init(compilerSafeOffset offset: Int, in string: String) {
#if swift(>=5.0)
self = String.Index(utf16Offset: offset, in: string)
#else
self = String.Index(encodedOffset: offset)
#endif
}
}
110 changes: 43 additions & 67 deletions Sources/XCLogParser/lexer/Lexer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public final class Lexer {

static let SLFHeader = "SLF"

let typeDelimiters: CharacterSet
let typeDelimiters: Set<Character>
let filePath: String
var classNames = [String]()
var userDirToRedact: String? {
Expand All @@ -38,7 +38,7 @@ public final class Lexer {

public init(filePath: String) {
self.filePath = filePath
self.typeDelimiters = CharacterSet(charactersIn: TokenType.all())
self.typeDelimiters = Set(TokenType.all())
self.redactor = LexRedactor()
}

Expand All @@ -53,15 +53,18 @@ public final class Lexer {
redacted: Bool,
withoutBuildSpecificInformation: Bool) throws -> [Token] {
let scanner = Scanner(string: contents)

guard scanSLFHeader(scanner: scanner) else {
throw XCLogParserError.invalidLogHeader(filePath)
}

var tokens = [Token]()
while !scanner.isAtEnd {

guard let logTokens = scanSLFType(scanner: scanner,
redacted: redacted,
withoutBuildSpecificInformation: withoutBuildSpecificInformation),
logTokens.isEmpty == false else {
logTokens.isEmpty == false else {
print(tokens)
throw XCLogParserError.invalidLine(scanner.approximateLine)
}
Expand All @@ -71,20 +74,15 @@ public final class Lexer {
}

private func scanSLFHeader(scanner: Scanner) -> Bool {
#if os(Linux)
var format: String?
#else
var format: NSString?
#endif
return scanner.scanString(Lexer.SLFHeader, into: &format)
return scanner.scan(string: Lexer.SLFHeader)
}

private func scanSLFType(scanner: Scanner, redacted: Bool, withoutBuildSpecificInformation: Bool) -> [Token]? {
private func scanSLFType(scanner: Scanner,
redacted: Bool,
withoutBuildSpecificInformation: Bool) -> [Token]? {
let payload = self.scanPayload(scanner: scanner)

guard let payload = scanPayload(scanner: scanner) else {
return nil
}
guard let tokenTypes = scanTypeDelimiter(scanner: scanner), tokenTypes.count > 0 else {
guard let tokenTypes = self.scanTypeDelimiter(scanner: scanner), tokenTypes.count > 0 else {
return nil
}

Expand All @@ -97,44 +95,30 @@ public final class Lexer {
}
}

private func scanPayload(scanner: Scanner) -> String? {
var payload: String = ""
#if os(Linux)
var char: String?
#else
var char: NSString?
#endif
private func scanPayload(scanner: Scanner) -> String {
let hexChars = "abcdef0123456789"
while scanner.scanCharacters(from: CharacterSet(charactersIn: hexChars), into: &char),
let char = char as String? {
payload.append(char)
}
return payload
let characterSet = Set(hexChars)
return scanner.scanCharacters(from: characterSet) ?? ""
}

private func scanTypeDelimiter(scanner: Scanner) -> [TokenType]? {
#if os(Linux)
var delimiters: String?
#else
var delimiters: NSString?
#endif
if scanner.scanCharacters(from: typeDelimiters, into: &delimiters), let delimiters = delimiters {
let delimiters = String(delimiters)
if delimiters.count > 1 {
// if we found a string, we discard other type delimiters because there are part of the string
let tokenString = TokenType.string
if let char = delimiters.first, tokenString.rawValue == String(char) {
scanner.scanLocation -= delimiters.count - 1
return [tokenString]
}
}
// sometimes we found one or more nil list (-) next to the type delimiter
// in that case we'll return the delimiter and one or more `Token.null`
return delimiters.compactMap { character -> TokenType? in
TokenType(rawValue: String(character))
guard let delimiters = scanner.scanCharacters(from: self.typeDelimiters) else {
return nil
}

if delimiters.count > 1 {
// if we found a string, we discard other type delimiters because there are part of the string
let tokenString = TokenType.string
if let char = delimiters.first, tokenString.rawValue == String(char) {
scanner.moveOffset(by: -(delimiters.count - 1))
return [tokenString]
}
}
return nil
// sometimes we found one or more nil list (-) next to the type delimiter
// in that case we'll return the delimiter and one or more `Token.null`
return delimiters.compactMap { character -> TokenType? in
TokenType(rawValue: String(character))
}
}

private func scanToken(scanner: Scanner,
Expand Down Expand Up @@ -252,19 +236,12 @@ public final class Lexer {
scanner: Scanner,
redacted: Bool,
withoutBuildSpecificInformation: Bool) -> String? {
guard let value = Int(length) else {
guard let value = Int(length), let scannedResult = scanner.scan(count: value) else {
print("error parsing string")
return nil
}
#if swift(>=5.0)
let start = String.Index(utf16Offset: scanner.scanLocation, in: scanner.string)
let end = String.Index(utf16Offset: scanner.scanLocation + value, in: scanner.string)
#else
let start = String.Index(encodedOffset: scanner.scanLocation)
let end = String.Index(encodedOffset: scanner.scanLocation + value)
#endif
scanner.scanLocation += value
var result = String(scanner.string[start..<end])

var result = scannedResult
if redacted {
result = redactor.redactUserDir(string: result)
}
Expand All @@ -285,19 +262,18 @@ public final class Lexer {
}
}

extension Scanner {
private extension Scanner {
var approximateLine: String {
let endCount = string.count - scanLocation > 21 ? scanLocation + 21 : string.count - scanLocation
#if swift(>=5.0)
let start = String.Index(utf16Offset: scanLocation, in: self.string)
let end = String.Index(utf16Offset: endCount, in: self.string)
#else
let start = String.Index(encodedOffset: scanLocation)
let end = String.Index(encodedOffset: endCount)
#endif
let currentLocation = self.offset
let contentSize = self.string.count

let start = String.Index(compilerSafeOffset: currentLocation, in: self.string)
let endCount = contentSize - currentLocation > 21 ? currentLocation + 21 : contentSize - currentLocation
let end = String.Index(compilerSafeOffset: endCount, in: self.string)

if end <= start {
return String(string[start..<string.endIndex])
return String(self.string[start..<self.stringEndIndex])
}
return String(string[start..<end])
return String(self.string[start..<end])
}
}
80 changes: 80 additions & 0 deletions Sources/XCLogParser/lexer/Scanner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) 2019 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import Foundation

final class Scanner {

let string: String

private(set) var offset: Int
private(set) lazy var stringEndIndex: String.Index = self.string.endIndex

var isAtEnd: Bool {
String.Index(compilerSafeOffset: self.offset, in: self.string) >= self.stringEndIndex
}

init(string: String) {
self.string = string
self.offset = 0
}

func scan(count: Int) -> String? {
let start = String.Index(compilerSafeOffset: self.offset, in: self.string)
let end = String.Index(compilerSafeOffset: self.offset + count, in: self.string)

var result = self.string.substring(with: (start..<end))

guard result.count == count else { return nil }

self.offset = self.offset + count

return String(result)
}

func scan(string value: String) -> Bool {
guard self.string.starts(with: value) else { return false }

self.offset = self.offset + value.count
return true
}

func scanCharacters(from allowedCharacters: Set<Character>) -> String? {
var prefix: String = ""
var characterIndex = String.Index(compilerSafeOffset: self.offset, in: self.string)

while characterIndex < self.stringEndIndex {
let character = self.string[characterIndex]

guard allowedCharacters.contains(character) else {
break
}

prefix.append(character)
self.offset = self.offset + 1
characterIndex = String.Index(utf16Offset: self.offset, in: self.string)
}

return prefix
}

func moveOffset(by value: Int) {
self.offset = self.offset + value
}
}