Skip to content

Instantly share code, notes, and snippets.

@leanderme
Created September 9, 2016 14:03
Show Gist options
  • Save leanderme/9c3585aa65a80402ccc46ab8d98e94d1 to your computer and use it in GitHub Desktop.
Save leanderme/9c3585aa65a80402ccc46ab8d98e94d1 to your computer and use it in GitHub Desktop.
EasySync from Etherpad, Licensed under the Apache License
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