Created
September 9, 2016 14:03
-
-
Save leanderme/9c3585aa65a80402ccc46ab8d98e94d1 to your computer and use it in GitHub Desktop.
EasySync from Etherpad, Licensed under the Apache License
This file contains hidden or 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
import Foundation | |
enum OpParseError: Error { | |
case Empty | |
case Short | |
} | |
class Easysync2Support: NSObject { | |
let opAssembler = OpAssembler() | |
let newOp = Op(opcode: "", chars: 0, lines: 0, attribs: "") | |
let mergingOpAssembler = MergingOpAssembler() | |
public func numToString(d: Int) -> String { | |
return String(d).lowercased() | |
} | |
public func stringToNum(s: String) -> Int { | |
if let safeInt = Int(s) { | |
return safeInt | |
} else { | |
return 0 | |
} | |
} | |
func isAlphanum(c: Character) -> Bool { | |
return (c >= "0" && c <= "9" || c >= "a" && c <= "z"); | |
} | |
func lookingAt(c: Character, index: Int, str: String) -> Bool { | |
return (index < str.characters.count && str[index] == c); | |
} | |
func lookingAtAlphanum(i: Int, str: String) -> Bool { | |
return (i < str.characters.count && isAlphanum(c: str[i])) | |
} | |
func atEnd(i: Int, str: String) -> Bool { | |
return (i >= str.characters.count) | |
} | |
func readAlphanum(index: Int, str: String) throws -> Int { | |
var i = index | |
let start = i | |
var string = str | |
if (lookingAtAlphanum(i: i, str: string)) != true { | |
throw OpParseError.Short | |
} | |
while (lookingAtAlphanum(i: i, str: string)) { | |
i = i + 1 | |
} | |
let end = i | |
let range = str.index(str.startIndex, offsetBy: start)..<str.index(str.startIndex, offsetBy: end) | |
string.removeSubrange(range) | |
return stringToNum(s: string) | |
} | |
func nextOpInString(str: String, startIndex: Int) throws -> Op { | |
var string = str | |
var i = startIndex | |
while (lookingAt(c: "*", index: i, str: string)) { | |
i += 1; | |
if (lookingAtAlphanum(i: i, str: string)) != true { | |
throw OpParseError.Short | |
} | |
while (lookingAtAlphanum(i: i, str: string)) { | |
i += 1; | |
} | |
} | |
let attribsEnd = i | |
var lines_ = 0 | |
if (lookingAt(c: "|", index: i, str: string)) { | |
i += 1; | |
lines_ = try readAlphanum(index: i, str: string) | |
} | |
if (lookingAt(c: "?", index: i, str: string)) { | |
// return { var opcode = "?" } | |
} | |
if ((lookingAt(c: "+", index: i, str: string) || lookingAt(c: "-", index: i, str: string) || lookingAt(c: "=", index: i, str: string))) != true { | |
throw OpParseError.Short | |
} | |
let opcodeRange = string.index(string.startIndex, offsetBy: i)..<string.index(str.startIndex, offsetBy: i+1) | |
string.removeSubrange(opcodeRange) | |
let opcode_ = string | |
i += 1 | |
let chars_ = try readAlphanum(index: i, str: string) | |
let opRange = string.startIndex..<string.index(str.startIndex, offsetBy: attribsEnd) | |
string.removeSubrange(opRange) | |
return Op(opcode: opcode_, chars: chars_, lines: lines_, attribs: string) | |
} | |
} | |
class OpAssembler: Easysync2Support { | |
var buf = String(1000) | |
func clearOp(op: Op) { | |
op.opcode = "" | |
op.chars = 0 | |
op.lines = 0 | |
op.attribs = "" | |
} | |
func append(op: Op) { | |
append(opcode: op.opcode, chars: op.chars, lines: op.lines, attribs: op.attribs); | |
} | |
func append(opcode: String, chars: Int, lines: Int, attribs: String) { | |
buf.append(attribs); | |
if (lines > 0) { | |
buf.append("|"); | |
buf.append(numToString(d: lines)); | |
} | |
buf.append(opcode); | |
buf.append(numToString(d: chars)); | |
} | |
func toString() ->String { | |
return self.buf | |
} | |
func clear() { | |
self.buf = "" | |
} | |
} | |
class Op { | |
var opcode: String | |
var chars: Int | |
var lines: Int | |
var attribs: String | |
init(opcode: String, chars: Int, lines: Int, attribs: String) { | |
self.opcode = opcode | |
self.chars = chars | |
self.lines = lines | |
self.attribs = attribs | |
} | |
} | |
// ported from easysync2.js | |
class MergingOpAssembler: OpAssembler { | |
var bufOpAdditionalCharsAfterNewline = 0; | |
var assem = OpAssembler() | |
var bufOp = Op(opcode: "", chars: 0, lines: 0, attribs: "") | |
func flush(isEndDocument: Bool) { | |
if (bufOp.opcode.characters.count > 0) { | |
if (isEndDocument && bufOp.opcode == "=" && bufOp.attribs.characters.count == 0) { | |
// final merged keep, leave it implicit | |
} | |
else { | |
assem.append(op: bufOp) | |
if (bufOpAdditionalCharsAfterNewline > 0) { | |
bufOp.chars = bufOpAdditionalCharsAfterNewline; | |
bufOp.lines = 0; | |
assem.append(op: bufOp); | |
bufOpAdditionalCharsAfterNewline = 0; | |
} | |
} | |
bufOp.opcode = ""; | |
} | |
} | |
override func append(opcode: String, chars: Int, lines: Int, attribs: String) { | |
if (chars > 0) { | |
if (bufOp.opcode == opcode && bufOp.attribs == attribs) { | |
if (lines > 0) { | |
// bufOp and additional chars are all mergeable into a multi-line op | |
bufOp.chars += bufOpAdditionalCharsAfterNewline + chars | |
bufOp.lines += lines | |
bufOpAdditionalCharsAfterNewline = 0 | |
} | |
else if (bufOp.lines == 0) { | |
// both bufOp and op are in-line | |
bufOp.chars += chars | |
} | |
else { | |
// append in-line text to multi-line bufOp | |
bufOpAdditionalCharsAfterNewline += chars | |
} | |
} | |
else { | |
flush(isEndDocument: false) | |
bufOp = Op(opcode: opcode, chars: chars, lines: lines, attribs: attribs) | |
} | |
} | |
} | |
func endDocument() { | |
flush(isEndDocument: true) | |
} | |
override func toString() -> String { | |
flush(isEndDocument: false) | |
return assem.toString() | |
} | |
override func clear() { | |
assem.clear() | |
clearOp(op: bufOp) | |
} | |
} | |
extension String { | |
subscript (i: Int) -> Character { | |
return self[self.index(self.startIndex, offsetBy: i)] | |
} | |
subscript (i: Int) -> String { | |
return String(self[i] as Character) | |
} | |
subscript (r: Range<Int>) -> String { | |
let start = index(startIndex, offsetBy: r.lowerBound) | |
let end = index(endIndex, offsetBy: r.upperBound) | |
return self[Range(start ..< end)] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment