Skip to content

Instantly share code, notes, and snippets.

@draveness
Created April 10, 2017 01:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save draveness/327ef3f94c6f15baba155eeda17525ff to your computer and use it in GitHub Desktop.
Save draveness/327ef3f94c6f15baba155eeda17525ff to your computer and use it in GitHub Desktop.
//
// 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