Created
April 10, 2017 01:22
-
-
Save draveness/327ef3f94c6f15baba155eeda17525ff to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Token.swift | |
// XProject | |
// | |
// Created by Draveness on 05/04/2017. | |
// Copyright © 2017 Draveness. All rights reserved. | |
// | |
import Foundation | |
import RbSwift | |
public indirect enum Token { | |
case leftBracket | |
case rightBracket | |
case leftParen | |
case rightParen | |
case colon | |
case comma | |
case equal | |
case plain(String) | |
case value(Token) | |
case dict(exps: Token?) | |
case exps(exps: Token, exp: Token?) | |
case exp(token: Token, value: Token) | |
case arr(exps: Token?) | |
case arrExps(exps: Token, exp: Token?) | |
case arrExp(token: Token) | |
} | |
extension Token: Equatable { | |
public static func ==(lhs: Token, rhs: Token) -> Bool { | |
switch (lhs, rhs) { | |
case (let .plain(lstr), let .plain(rstr)): | |
return lstr == rstr | |
case (.leftBracket, leftBracket): | |
return true | |
case (.rightBracket, rightBracket): | |
return true | |
case (.leftParen, leftParen): | |
return true | |
case (.rightParen, rightParen): | |
return true | |
case (.colon, colon): | |
return true | |
case (.comma, comma): | |
return true | |
case (.equal, equal): | |
return true | |
default: | |
return false | |
} | |
} | |
} | |
extension Token: CustomStringConvertible { | |
public var description: String { | |
switch self { | |
case .dict: | |
return "Dict" | |
case .arr: | |
return "Array" | |
case .exps: | |
return "Exps" | |
case .exp: | |
return "Exp" | |
case .arrExps: | |
return "ArrayExps" | |
case .arrExp: | |
return "ArrayExp" | |
case .value: | |
return "Value" | |
case .plain: | |
return "Token" | |
case .leftBracket: | |
return "{" | |
case .rightBracket: | |
return "}" | |
case .leftParen: | |
return "(" | |
case .rightParen: | |
return ")" | |
case .colon: | |
return ";" | |
case .comma: | |
return "," | |
case .equal: | |
return "=" | |
} | |
} | |
} | |
extension Token { | |
public var toJSON: Any { | |
switch self { | |
case .dict(let exps): | |
return exps?.toJSON ?? [:] | |
case .arr(let exps): | |
return exps?.toJSON ?? [] | |
case .exps(let exps, let exp): | |
var results: [String: Any] = [:] | |
if let json = exps.toJSON as? [String: Any] { results = results + json } | |
if let json = exp?.toJSON as? [String: Any] { results = results + json } | |
return results | |
case .exp(let token, let value): | |
return [token.toJSON as! String: value.toJSON] | |
case .arrExps(let exps, let exp): | |
var results: [Any] = [] | |
if let json = exps.toJSON as? [Any] { results += json } | |
if let json = exp?.toJSON as? [Any] { results += json } | |
return results | |
case .arrExp(let token): | |
return [token.toJSON as! String] | |
case .value(let token): | |
return token.toJSON | |
case .plain(let str): | |
return str | |
default: | |
return "" | |
} | |
} | |
} | |
import Foundation | |
import RbSwift | |
import CoreFoundation | |
public struct Parser { | |
let content: String | |
var buffer: [Token] = [] | |
var lookahead: Token? | |
public init(content: String) { | |
self.content = content | |
} | |
public mutating func parse() -> [String: Any] { | |
var tokens = Tokenizer.tokenize(content) | |
while tokens.count > 0 { | |
buffer.append(tokens.shift()!) | |
lookahead = tokens.first | |
var results = [true] | |
while results.UUIDRepresentableny(closure: { $0 }) { | |
results = [ | |
matchDictionary(), | |
matchEmptyDictionary(), | |
matchExpression(), | |
matchExpressions(), | |
matchSingleExpressions(), | |
matchArray(), | |
matchEmptyArray(), | |
matchArrayExp(), | |
matchArrayExps(), | |
matchSingleArrayExps(), | |
matchValue() | |
] | |
} | |
} | |
return buffer.first!.toJSON as! [String: Any] | |
} | |
public mutating func matchEmptyDictionary() -> Bool { | |
guard buffer.count >= 2 else { return false } | |
let elems = buffer.last(2) | |
switch (elems[0], elems[1]) { | |
case (.leftBracket, .rightBracket): | |
replace(last: 2, with: Token.dict(exps: nil)) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchDictionary() -> Bool { | |
guard buffer.count >= 3 else { return false } | |
let elems = buffer.last(3) | |
switch (elems[0], elems[1], elems[2]) { | |
case (.leftBracket, .exps, .rightBracket): | |
replace(last: 3, with: Token.dict(exps: elems[1])) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchExpressions() -> Bool { | |
guard buffer.count >= 2 else { return false } | |
let elems = buffer.last(2) | |
switch (elems[0], elems[1]) { | |
case (.exps, .exp): | |
replace(last: 2, with: Token.exps(exps: elems[0], exp: elems[1])) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchSingleExpressions() -> Bool { | |
guard buffer.count >= 1 else { return false } | |
let elems = buffer.last(1) | |
switch elems[0] { | |
case .exp: | |
replace(last: 1, with: Token.exps(exps: elems[0], exp: nil)) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchExpression() -> Bool { | |
guard buffer.count >= 4 else { return false } | |
let elems = buffer.last(4) | |
switch (elems[0], elems[1], elems[2], elems[3]) { | |
case (.plain, .equal, .value, .colon): | |
replace(last: 4, with: Token.exp(token: elems[0], value: elems[2])) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchValue() -> Bool { | |
guard buffer.count >= 1 else { return false } | |
let elems = buffer.last(1) | |
switch elems[0] { | |
case .dict, .arr: | |
replace(last: 1, with: Token.value(elems[0])) | |
return true | |
case .plain: | |
if let lookahead = lookahead, lookahead == .equal || lookahead == .comma { | |
return false | |
} else { | |
replace(last: 1, with: Token.value(elems[0])) | |
return true | |
} | |
default: break | |
} | |
return false | |
} | |
public mutating func matchArray() -> Bool { | |
guard buffer.count >= 3 else { return false } | |
let elems = buffer.last(3) | |
switch (elems[0], elems[1], elems[2]) { | |
case (.leftParen, .arrExps, .rightParen): | |
replace(last: 3, with: Token.arr(exps: elems[1])) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchEmptyArray() -> Bool { | |
guard buffer.count >= 2 else { return false } | |
let elems = buffer.last(2) | |
switch (elems[0], elems[1]) { | |
case (.leftParen, .rightParen): | |
replace(last: 2, with: Token.arr(exps: nil)) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchArrayExps() -> Bool { | |
guard buffer.count >= 2 else { return false } | |
let elems = buffer.last(2) | |
switch (elems[0], elems[1]) { | |
case (.arrExps, .arrExp): | |
replace(last: 2, with: Token.arrExps(exps: elems[0], exp: elems[1])) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchSingleArrayExps() -> Bool { | |
guard buffer.count >= 1 else { return false } | |
let elems = buffer.last(1) | |
switch (elems[0]) { | |
case .arrExp: | |
replace(last: 1, with: Token.arrExps(exps: elems[0], exp: nil)) | |
return true | |
default: break | |
} | |
return false | |
} | |
public mutating func matchArrayExp() -> Bool { | |
guard buffer.count >= 2 else { return false } | |
let elems = buffer.last(2) | |
switch (elems[0], elems[1]) { | |
case (.plain, .comma): | |
replace(last: 2, with: Token.arrExp(token: elems[0])) | |
return true | |
default: break | |
} | |
return false | |
} | |
fileprivate mutating func replace(last count: Int, with token: Token) { | |
buffer.removeLast(count) | |
buffer.append(token) | |
// print("reduce:\t\(buffer)") | |
} | |
} | |
struct Tokenizer { | |
static func tokenize(_ content: String) -> [Token] { | |
var content = content | |
content = removeComments(content) | |
let result = replaceStr(content) | |
content = result.0 | |
var matches = result.1 | |
content = removeWhiteChars(content) | |
var tokens: [String] = [] | |
var latestToken = "" | |
let chars = content.chars | |
chars.each { char in | |
switch char { | |
case "{", "}", "(", ")", "=", ",", ";": | |
tokens.append(latestToken) | |
tokens.append(char) | |
latestToken = "" | |
break | |
default: | |
latestToken += char | |
break | |
} | |
} | |
tokens = tokens | |
.filter { !$0.isEmpty } | |
.map { token in | |
if token == "com.draveness.draveness.xproject.temp.string" { | |
return matches.shift()! | |
} else { | |
return token | |
} | |
} | |
let results: [Token] = tokens.map { | |
switch $0 { | |
case "{": | |
return .leftBracket | |
case "}": | |
return .rightBracket | |
case "(": | |
return .leftParen | |
case ")": | |
return .rightParen | |
case "=": | |
return .equal | |
case ",": | |
return .comma | |
case ";": | |
return .colon | |
default: | |
return .plain($0) | |
} | |
} | |
return results | |
} | |
static func removeComments(_ content: String) -> String { | |
var content = content | |
content.gsubed("/\\*.*?\\*/", "") | |
content.gsubed("//.*\n", "") | |
return content | |
} | |
static func replaceStr(_ content: String) -> (String, [String]) { | |
var content = content | |
let regex = "\"(\\\\.|[^\"])*\"".regex | |
let matches = regex.scan_fixed(content).map { $0.match } | |
content = content.gsubed(regex, "com.draveness.draveness.xproject.temp.string") | |
return (content, matches) | |
} | |
static func removeWhiteChars(_ content: String) -> String { | |
var content = content | |
content.gsubed("[\n\t ]", "") | |
return content | |
} | |
} | |
extension Regex { | |
// A werid problem when match large amount of data, the matching result may return an invalid range, this | |
// removed the substring process. | |
@discardableResult public func scan_fixed(_ str: String, closure: ((MatchData) -> Void)? = nil) -> [MatchData] { | |
let matches = regexp.matches(in: str, options: [], range: NSMakeRange(0, str.length)) | |
let str = str as NSString | |
var matchDatas: [MatchData] = [] | |
for match in matches { | |
let substr = str.substring(with: match.range) | |
let matchData = MatchData(match: substr, range: match.range, captures: [], ranges: []) | |
if let closure = closure { closure(matchData) } | |
matchDatas.append(matchData) | |
} | |
return matchDatas | |
} | |
} | |
Parser(content: xxx).parse() -> [String: Any] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment