2015-10-26 23:00:52 +03:00
|
|
|
|
import Cocoa
|
|
|
|
|
import Doubt
|
2015-10-28 01:38:25 +03:00
|
|
|
|
import Prelude
|
2015-10-27 18:04:39 +03:00
|
|
|
|
|
2015-10-29 01:17:01 +03:00
|
|
|
|
extension String: ErrorType {}
|
|
|
|
|
|
2015-10-29 01:12:16 +03:00
|
|
|
|
typealias Term = Cofree<String, Info>
|
2015-10-28 01:37:34 +03:00
|
|
|
|
|
2015-10-29 23:04:10 +03:00
|
|
|
|
struct Source {
|
2015-10-29 23:06:51 +03:00
|
|
|
|
init?(_ argument: String) {
|
|
|
|
|
URL = NSURL(string: argument) ?? NSURL(fileURLWithPath: argument)
|
|
|
|
|
guard let type = URL.pathExtension else { return nil }
|
|
|
|
|
self.type = type
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-29 23:04:10 +03:00
|
|
|
|
let URL: NSURL
|
2015-10-29 23:04:51 +03:00
|
|
|
|
let type: String
|
2015-10-29 23:08:40 +03:00
|
|
|
|
|
|
|
|
|
func load() throws -> String {
|
|
|
|
|
return try NSString(contentsOfURL: URL, encoding: NSUTF8StringEncoding) as String
|
|
|
|
|
}
|
2015-10-29 23:09:31 +03:00
|
|
|
|
|
2015-10-29 23:10:01 +03:00
|
|
|
|
private static let languagesByType: [String:TSLanguage] = [
|
2015-10-29 23:09:31 +03:00
|
|
|
|
"js": ts_language_javascript(),
|
|
|
|
|
"c": ts_language_c(),
|
|
|
|
|
"h": ts_language_c(),
|
|
|
|
|
]
|
2015-10-29 23:04:10 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-29 17:00:56 +03:00
|
|
|
|
|
|
|
|
|
extension String.UTF16View {
|
|
|
|
|
subscript (range: Range<Int>) -> String.UTF16View {
|
|
|
|
|
return self[Index(_offset: range.startIndex)..<Index(_offset: range.endIndex)]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-29 21:59:18 +03:00
|
|
|
|
|
2015-10-29 22:10:30 +03:00
|
|
|
|
let keyedProductions: Set<String> = [ "object" ]
|
2015-10-29 22:11:52 +03:00
|
|
|
|
let fixedProductions: Set<String> = [ "pair", "rel_op", "math_op", "bool_op", "bitwise_op", "type_op", "math_assignment", "assignment", "subscript_access", "member_access", "new_expression", "function_call", "function", "ternary" ]
|
2015-10-29 22:10:30 +03:00
|
|
|
|
|
2015-10-29 22:09:57 +03:00
|
|
|
|
/// Allow predicates to occur in pattern matching.
|
2015-10-29 22:09:52 +03:00
|
|
|
|
func ~= <A> (left: A -> Bool, right: A) -> Bool {
|
|
|
|
|
return left(right)
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-29 17:00:56 +03:00
|
|
|
|
|
2015-10-29 22:20:59 +03:00
|
|
|
|
func termWithInput(language: TSLanguage)(_ string: String) -> Term? {
|
2015-10-27 18:04:18 +03:00
|
|
|
|
let document = ts_document_make()
|
2015-10-28 01:26:39 +03:00
|
|
|
|
defer { ts_document_free(document) }
|
2015-10-28 01:29:44 +03:00
|
|
|
|
return string.withCString {
|
2015-10-29 22:20:59 +03:00
|
|
|
|
ts_document_set_language(document, language)
|
2015-10-28 01:29:44 +03:00
|
|
|
|
ts_document_set_input_string(document, $0)
|
|
|
|
|
ts_document_parse(document)
|
|
|
|
|
let root = ts_document_root_node(document)
|
2015-10-28 01:07:00 +03:00
|
|
|
|
|
2015-10-28 21:20:46 +03:00
|
|
|
|
return try? Cofree
|
|
|
|
|
.ana { node, category in
|
2015-10-28 22:38:20 +03:00
|
|
|
|
let count = node.namedChildren.count
|
2015-10-29 17:10:13 +03:00
|
|
|
|
guard count > 0 else { return try Syntax.Leaf(node.substring(string)) }
|
2015-10-28 22:05:25 +03:00
|
|
|
|
switch category {
|
2015-10-29 22:12:16 +03:00
|
|
|
|
case fixedProductions.contains:
|
2015-10-28 23:20:39 +03:00
|
|
|
|
return try .Fixed(node.namedChildren.map {
|
2015-10-29 00:28:58 +03:00
|
|
|
|
($0, try $0.category(document))
|
2015-10-28 23:20:39 +03:00
|
|
|
|
})
|
2015-10-29 22:10:37 +03:00
|
|
|
|
case keyedProductions.contains:
|
2015-10-28 23:18:38 +03:00
|
|
|
|
return try .Keyed(Dictionary(elements: node.namedChildren.map {
|
2015-10-29 01:32:34 +03:00
|
|
|
|
switch try $0.category(document) {
|
|
|
|
|
case "pair":
|
2015-10-29 17:03:53 +03:00
|
|
|
|
return try ($0.namedChildren[0].substring(string), ($0, "pair"))
|
2015-10-29 01:32:34 +03:00
|
|
|
|
default:
|
2015-10-29 01:33:04 +03:00
|
|
|
|
// We might have a comment inside an object literal. It should still be assigned a key, however.
|
2015-10-29 17:10:13 +03:00
|
|
|
|
return try (try node.substring(string), ($0, $0.category(document)))
|
2015-10-29 01:32:34 +03:00
|
|
|
|
}
|
2015-10-28 23:18:38 +03:00
|
|
|
|
}))
|
2015-10-28 22:05:25 +03:00
|
|
|
|
default:
|
2015-10-28 22:31:08 +03:00
|
|
|
|
return try .Indexed(node.namedChildren.map {
|
2015-10-29 00:28:58 +03:00
|
|
|
|
($0, try $0.category(document))
|
2015-10-28 22:05:25 +03:00
|
|
|
|
})
|
|
|
|
|
}
|
2015-10-29 00:48:51 +03:00
|
|
|
|
} (root, "program")
|
2015-10-28 21:20:46 +03:00
|
|
|
|
.map { node, category in
|
2015-10-29 01:12:16 +03:00
|
|
|
|
Info(range: node.range, categories: [ category ])
|
2015-10-28 01:10:17 +03:00
|
|
|
|
}
|
2015-10-28 01:29:44 +03:00
|
|
|
|
}
|
2015-10-28 01:26:39 +03:00
|
|
|
|
}
|
2015-10-28 01:10:17 +03:00
|
|
|
|
|
2015-10-28 01:26:39 +03:00
|
|
|
|
let arguments = BoundsCheckedArray(array: Process.arguments)
|
2015-10-29 22:59:34 +03:00
|
|
|
|
guard let aURL = arguments[1].map(NSURL.init) else { throw "need path for state A" }
|
|
|
|
|
guard let bURL = arguments[2].map(NSURL.init) else { throw "need path for state B" }
|
2015-10-29 22:38:06 +03:00
|
|
|
|
let aString = try NSString(contentsOfURL: aURL, encoding: NSUTF8StringEncoding) as String
|
|
|
|
|
let bString = try NSString(contentsOfURL: bURL, encoding: NSUTF8StringEncoding) as String
|
2015-10-29 22:39:12 +03:00
|
|
|
|
guard let jsonPath = arguments[3] else { throw "need json path" }
|
|
|
|
|
guard let uiPath = arguments[4] else { throw "need ui path" }
|
2015-10-29 22:39:26 +03:00
|
|
|
|
guard let aType = aURL.pathExtension, bType = bURL.pathExtension else { throw "can’t tell what type we have here" }
|
|
|
|
|
guard aType == bType else { throw "can’t compare files of different types" }
|
2015-10-29 23:10:01 +03:00
|
|
|
|
guard let language = Source.languagesByType[aType] else { throw "don’t know how to parse files of type \(aType)" }
|
2015-10-29 22:39:32 +03:00
|
|
|
|
|
2015-10-29 22:39:26 +03:00
|
|
|
|
let parser: String -> Term? = termWithInput(language)
|
2015-10-29 22:40:36 +03:00
|
|
|
|
guard let a = parser(aString) else { throw "couldn’t parse \(aURL)" }
|
|
|
|
|
guard let b = parser(bString) else { throw "couldn’t parse \(bURL)" }
|
|
|
|
|
let diff = Interpreter<Term>(equal: Term.equals(annotation: const(true), leaf: ==), comparable: Interpreter<Term>.comparable { $0.extract.categories }, cost: Free.sum(Patch.sum)).run(a, b)
|
|
|
|
|
let JSON: Doubt.JSON = [
|
|
|
|
|
"before": .String(aString),
|
|
|
|
|
"after": .String(bString),
|
|
|
|
|
"diff": diff.JSON(pure: { $0.JSON { $0.JSON(annotation: { $0.range.JSON }, leaf: Doubt.JSON.String) } }, leaf: Doubt.JSON.String, annotation: {
|
|
|
|
|
[
|
|
|
|
|
"before": $0.range.JSON,
|
|
|
|
|
"after": $1.range.JSON,
|
|
|
|
|
]
|
|
|
|
|
}),
|
|
|
|
|
]
|
|
|
|
|
let data = JSON.serialize()
|
|
|
|
|
try data.writeToFile(jsonPath, options: .DataWritingAtomic)
|
2015-10-28 01:53:48 +03:00
|
|
|
|
|
2015-10-29 22:40:36 +03:00
|
|
|
|
let components = NSURLComponents()
|
|
|
|
|
components.scheme = "file"
|
|
|
|
|
components.path = uiPath
|
|
|
|
|
components.query = jsonPath
|
|
|
|
|
if let URL = components.URL {
|
|
|
|
|
NSWorkspace.sharedWorkspace().openURL(URL)
|
2015-10-27 18:04:18 +03:00
|
|
|
|
}
|