From 904094281b062aff3445ca41fec57e4cfd0f563d Mon Sep 17 00:00:00 2001 From: Matthew Sotoudeh Date: Tue, 10 Nov 2020 14:06:35 -0800 Subject: Initial code release --- examples/program_analysis/BUILD | 62 +++++ examples/program_analysis/README.md | 25 ++ examples/program_analysis/analyzelib.py | 244 +++++++++++++++++ examples/program_analysis/api_migration.py | 72 +++++ examples/program_analysis/lazy_structure.py | 305 +++++++++++++++++++++ .../program_analysis/paper_demos/api1.after.txt | 14 + .../program_analysis/paper_demos/api1.before.txt | 14 + .../program_analysis/paper_demos/api2.after.txt | 14 + .../program_analysis/paper_demos/api2.before.txt | 14 + .../program_analysis/paper_demos/api3.after.txt | 0 .../program_analysis/paper_demos/api3.before.txt | 14 + examples/program_analysis/paper_demos/bash.txt | 17 ++ .../program_analysis/paper_demos/docs.after.txt | 9 + .../program_analysis/paper_demos/docs.before.txt | 9 + examples/program_analysis/paper_demos/fish.txt | 13 + .../program_analysis/paper_demos/gemm1.after.txt | 6 + .../program_analysis/paper_demos/gemm1.before.txt | 6 + .../program_analysis/paper_demos/gemm2.after.txt | 10 + .../program_analysis/paper_demos/gemm2.before.txt | 10 + .../program_analysis/paper_demos/gemm3.after.txt | 0 .../program_analysis/paper_demos/gemm3.before.txt | 6 + .../program_analysis/paper_demos/gemm4.bad.txt | 6 + examples/program_analysis/program_understanding.py | 72 +++++ examples/program_analysis/transform_learning.py | 61 +++++ examples/program_analysis/ui/BUILD | 12 + examples/program_analysis/ui/index.html | 13 + examples/program_analysis/ui/index.js | 74 +++++ .../program_analysis/ui/lazy_structure_parser.py | 68 +++++ examples/program_analysis/ui/leader-line.min.js | 2 + .../program_analysis/ui/plain-draggable.min.js | 2 + examples/program_analysis/ui/serve.py | 58 ++++ examples/program_analysis/ui/style.css | 35 +++ 32 files changed, 1267 insertions(+) create mode 100644 examples/program_analysis/BUILD create mode 100644 examples/program_analysis/README.md create mode 100644 examples/program_analysis/analyzelib.py create mode 100644 examples/program_analysis/api_migration.py create mode 100644 examples/program_analysis/lazy_structure.py create mode 100644 examples/program_analysis/paper_demos/api1.after.txt create mode 100644 examples/program_analysis/paper_demos/api1.before.txt create mode 100644 examples/program_analysis/paper_demos/api2.after.txt create mode 100644 examples/program_analysis/paper_demos/api2.before.txt create mode 100644 examples/program_analysis/paper_demos/api3.after.txt create mode 100644 examples/program_analysis/paper_demos/api3.before.txt create mode 100644 examples/program_analysis/paper_demos/bash.txt create mode 100644 examples/program_analysis/paper_demos/docs.after.txt create mode 100644 examples/program_analysis/paper_demos/docs.before.txt create mode 100644 examples/program_analysis/paper_demos/fish.txt create mode 100644 examples/program_analysis/paper_demos/gemm1.after.txt create mode 100644 examples/program_analysis/paper_demos/gemm1.before.txt create mode 100644 examples/program_analysis/paper_demos/gemm2.after.txt create mode 100644 examples/program_analysis/paper_demos/gemm2.before.txt create mode 100644 examples/program_analysis/paper_demos/gemm3.after.txt create mode 100644 examples/program_analysis/paper_demos/gemm3.before.txt create mode 100644 examples/program_analysis/paper_demos/gemm4.bad.txt create mode 100644 examples/program_analysis/program_understanding.py create mode 100644 examples/program_analysis/transform_learning.py create mode 100644 examples/program_analysis/ui/BUILD create mode 100644 examples/program_analysis/ui/index.html create mode 100644 examples/program_analysis/ui/index.js create mode 100644 examples/program_analysis/ui/lazy_structure_parser.py create mode 100644 examples/program_analysis/ui/leader-line.min.js create mode 100644 examples/program_analysis/ui/plain-draggable.min.js create mode 100644 examples/program_analysis/ui/serve.py create mode 100644 examples/program_analysis/ui/style.css (limited to 'examples/program_analysis') diff --git a/examples/program_analysis/BUILD b/examples/program_analysis/BUILD new file mode 100644 index 0000000..6843d35 --- /dev/null +++ b/examples/program_analysis/BUILD @@ -0,0 +1,62 @@ +py_binary( + name = "api_migration", + srcs = ["api_migration.py"], + deps = [ + ":analyzelib", + ":lazy_structure", + "//:tactic_utils", + "//examples/program_analysis/ui:serve", + "//runtime", + ], +) + +py_binary( + name = "transform_learning", + srcs = ["transform_learning.py"], + deps = [ + ":analyzelib", + ":lazy_structure", + "//:tactic_utils", + "//examples/program_analysis/ui:serve", + "//runtime", + ], +) + +py_binary( + name = "program_understanding", + srcs = ["program_understanding.py"], + deps = [ + ":analyzelib", + ":lazy_structure", + "//:tactic_utils", + "//examples/program_analysis/ui:serve", + "//runtime", + ], +) + +py_library( + name = "lazy_structure", + srcs = ["lazy_structure.py"], + deps = [ + "//:mapper", + "//:tactic_utils", + "//:ts_lib", + "//:ts_utils", + "//examples/program_analysis/ui:serve", + "//runtime", + ], +) + +py_library( + name = "analyzelib", + srcs = ["analyzelib.py"], + deps = [ + "//:analogy_utils", + "//:mapper", + "//:tactic_utils", + "//:ts_lib", + "//:ts_utils", + "//examples/program_analysis/ui:serve", + "//runtime", + ], +) diff --git a/examples/program_analysis/README.md b/examples/program_analysis/README.md new file mode 100644 index 0000000..ab99d4b --- /dev/null +++ b/examples/program_analysis/README.md @@ -0,0 +1,25 @@ +# Program Analysis Example +This directory contains code for the Sifter demos presented in our Onward! +2020 paper. You can run the examples from the root of the Sifter repository +like +so: +```bash +bazel run examples/program_analysis:program_understanding +bazel run examples/program_analysis:transform_learning +bazel run examples/program_analysis:api_migration +``` +Each example will run, then prompt you to visit `http://localhost:8001` in a +web browser which will show the result. + +#### Files +* `program_understanding.py`: Section 3.1 demonstration of comparative program + understanding. +* `transform_learning.py`: Section 3.2 demonstration of learning to generalize + a program optimization. +* `api_migration.py`: Section 3.3 demonstration of learning to generalize + API migration examples. +* `lazy_structure.py`: Classes to interface between source code files and + triplet structures. +* `analyzelib.py`: Helper methods and tactics for the demos. +* `ui/`: Interactive UI for displaying the result of the analogy-making + demonstrations. diff --git a/examples/program_analysis/analyzelib.py b/examples/program_analysis/analyzelib.py new file mode 100644 index 0000000..b7177f3 --- /dev/null +++ b/examples/program_analysis/analyzelib.py @@ -0,0 +1,244 @@ +"""Helper methods for analyzing program source code.""" +from collections import defaultdict +from tactic_utils import ApplyRulesMatching, SearchRules, GetMatcher, Fix +from mapper import MapperCodelet +from lazy_structure import LazyTextDocument, SPECIAL_CHARACTERS +from ts_utils import RegisterRule, AssertNodesEqual +from analogy_utils import Analogy + +def LoadDocument(path, extra_chunks=None, extra_special=None): + """Load a document from the file system into a LazyTextDocument. + + @extra_chunks: list of ranges of characters in the document that should be + chunked together. + @extra_special: list of special tokens that should be chunked together. + """ + extra_special = extra_special or [] + with open(path, "r") as in_file: + text = in_file.readlines() + chunks = [] + for start, length in (extra_chunks or []): + chunks.append("".join(text[start:(start + length)]).lstrip(" ")) + text = "".join(text) + return LazyTextDocument(text, chunks + extra_special + SPECIAL_CHARACTERS) + +def CompleteAnalogyTactic(structure, sources): + """Heuristic for completing an analogy involving code transformations. + + @sources should be a list of before/after LazyTextDocuments. + @sources[0][0] and @sources[1][0] should be the before-code of + @sources[0][1] and @sources[1][1] respectively. + @sources[2][0] should be the prompt before code, and @sources[2][1] should + be a currently-empty LazyTextDocument to be generated by Sifter. + """ + structure.ts.commit() + print("Identifying word pairs...") + ApplyRulesMatching(structure.rt, "SameWord", dict()) + + print("Mapping the examples...") + analogy = Analogy.Begin(structure.rt, dict({ + "/:Mapper:NoSlipRules:A": + structure.NodeOfChunk(sources[0][0], sources[0][0].chunks[0]), + "/:Mapper:NoSlipRules:B": + structure.NodeOfChunk(sources[1][0], sources[1][0].chunks[0]), + "/:Mapper:NoSlipRules:C": "/:Chunk", + }), extend_here=["/:Document"]) + ExtendAnalogyTactic(structure, analogy) + + print("Mapping the prompt...") + analogy = Analogy.Begin(structure.rt, dict({ + "/:Mapper:NoSlipRules:A": + structure.NodeOfChunk(sources[0][0], sources[0][0].chunks[0]), + "/:Mapper:NoSlipRules:B": + structure.NodeOfChunk(sources[2][0], sources[2][0].chunks[0]), + "/:Mapper:NoSlipRules:C": "/:Chunk", + }), exists=True, extend_here=["/:Document"]) + ExtendAnalogyTactic(structure, analogy) + + print("Solving the prompt...") + analogy.state = "concretize" + Fix(analogy.ExtendMap, ["/:Document"]) + Fix(analogy.ExtendMap) + Fix(analogy.ExtendMap, no_follow=True) + Fix(ApplyRulesMatching, structure.rt, "SolveWord", dict()) + +def ExtendAnalogyTactic(structure, analogy): + """Tactic to extend an analogy involving source code. + + See AnalogyUtils.md for the basic idea. This method describes a certain + ordering of nodes in the breadth-first-search. We first connect the + before-code to its documentation and after-code. Then we follow tokens that + are adjacent to each other. Finally, we look at any facts about words being + the same as each other. CrossDoc is used to identify associated chunks + across documents. + """ + Fix(analogy.ExtendMap, ["/:TransformPair:Before", "/:Documentation:Code"]) + Fix(analogy.ExtendMap, ["/:Follower:Before", "/:Follower:After"]) + CrossDoc(structure, analogy) + Fix(analogy.ExtendMap, ["/:Follower:Before", "/:Follower:After"]) + CrossDoc(structure, analogy) + Fix(analogy.ExtendMap, ["/:Follower:Before", "/:Follower:After"]) + Fix(analogy.ExtendMap, ["/:Chunk", "/:SameWord"], no_follow=True) + # If we have no_top=False, then it will map Documents:...:_IsMember before + # we start concretizing. But then the concretization pass will completely + # ignore _IsMember, so it will never concretize the contents. Basically, we + # just want to deal with the TOP nodes later, when concretizing. + Fix(analogy.ExtendMap, no_follow=True, no_top=True) + +FOLLOW_RELATIONS = [ + "/:Follower:Before", "/:Follower:After", "/:Document", + "/:TransformPair:After", +] + +def CrossDoc(structure, analogy): + """Tactic to bootstrap an analogy on a new document. + + Basically, the idea here is if you've already marked two tokens as + corresponding between Documents 0 and 2, we may want to use that + information to mark two different tokens as corresponding in Document 1 and + 3. We would usually want to do this by following facts like 'SameWord.' But + these facts may not be unique (there could be 100 instances of the same + token), so CrossDoc looks to only follow the facts that are unique. See + AnalogyUtils.md for a more complete description. + """ + ts, rt = structure.ts, structure.rt + scope = ts.scope("/:Rules:TagCrossDoc:MustMap", protect=True) + no_slip = rt.ts.scope("/:Mapper:NoSlipRules", protect=True) + + rule = SearchRules(rt, "Tag")[0] + new_partial = dict({ + "/:Rules:TagCrossDoc:MustMap:MAlphaA": analogy.MAlphaA, + "/:Rules:TagCrossDoc:MustMap:MAlphaB": analogy.MAlphaB, + }) + matcher = GetMatcher(rt, rule, new_partial) + matcher.sync() + found = defaultdict(set) + for assign in matcher.assignments(): + assigned = assign.assignment + key = (assigned[scope[":ChunkA"]], assigned[scope[":ChunkMapA"]], assigned[scope[":DocA'"]]) + found[key].add(assigned[scope[":ChunkMapB"]]) + for assign in matcher.assignments(): + assigned = assign.assignment + key = (assigned[scope[":ChunkA"]], assigned[scope[":ChunkMapA"]], assigned[scope[":DocA'"]]) + if len(found[key]) != 1: + continue + analogy.ExtendMap(partial=dict({ + no_slip[":A"]: assigned[scope[":ChunkA"]], + no_slip[":B"]: assigned[scope[":ChunkB"]], + no_slip[":MA"]: assigned[scope[":ChunkMapA"]], + no_slip[":MB"]: assigned[scope[":ChunkMapB"]], + no_slip[":C"]: assigned[scope[":ChunkType"]], + })) + +def AnalyzeCodelets(ts): + """Adds helper codelets to identify patterns used in the tactics above.""" + mapper = MapperCodelet(ts) + ts.add_node("/:TransformPair:Before") + ts.add_node("/:TransformPair:After") + ts.add_node("/:Documentation:Code") + ts.add_node("/:Documentation:Docs") + with ts.scope("/:Rules:SameWord"): + with ts.scope(":MustMap") as exists: + ts[":MA"].map({ts[":A"]: ts[":Word"]}) + ts[":MB"].map({ts[":B"]: ts[":Word"]}) + ts[":IsWord"].map({ts[":Word"]: ts["/:Word"]}) + ts[":AMember"].map({ + ts[":A"]: ts["/:Chunk"], + ts[":DocA"]: ts["/:Document"], + }) + ts[":BMember"].map({ + ts[":B"]: ts["/:Chunk"], + ts[":DocB"]: ts["/:Document"], + }) + ts[":AreRelated"].map({ + ts[":DocA"]: ts[":Doc1Relation"], + ts[":DocB"]: ts[":Doc2Relation"], + }) + with ts.scope(":NoMap:Insert"): + ts[":AreSame"].map({ + exists[":A"]: ts["/:SameWord"], + exists[":B"]: ts["/:SameWord"], + }) + # Some of these may be unnecessary, they seem to slow things down as + # well. For the transformation ones we don't need relations within a + # doc. + ts[":??"].map({ + ts[":_"]: ts["/RULE"], + exists[":AMember"]: ts["/MAYBE="], + exists[":BMember"]: ts["/MAYBE="], + exists[":AreRelated"]: ts["/MAYBE="], + }) + ts[":??"].map({ + ts[":_"]: ts["/RULE"], + exists[":DocA"]: ts["/MAYBE="], + exists[":DocB"]: ts["/MAYBE="], + }) + ts[":??"].map({ + ts[":_"]: ts["/RULE"], + exists[":Doc1Relation"]: ts["/MAYBE="], + exists[":Doc2Relation"]: ts["/MAYBE="], + }) + RegisterRule(ts) + with ts.scope("/:Rules:SolveWord"): + with ts.scope(":MustMap") as exists: + ts[":MA"].map({ts[":A"]: ts[":Word"]}) + ts[":IsWord"].map({ts[":Word"]: ts["/:Word"]}) + ts[":AreSame"].map({ + ts[":A"]: ts["/:SameWord"], + ts[":B"]: ts["/:SameWord"], + }) + with ts.scope(":NoMap:Insert"): + ts[":MB"].map({exists[":B"]: exists[":Word"]}) + RegisterRule(ts) + + """Crossdoc tagger. + + The idea here is to find chunks ChunkA in document A and ChunkB in document + B which are currently mapped together. We then want to find documents A', + B' with chunks ChunkA' and ChunkB' such that A and A' are related in the + same way as B and B', and ChunkA and ChunkA' are related in the same ways + as ChunkB and ChunkB'. + + Furthermore, we don't want there to be any _other_ ChunkA', ChunkB' which + are related in that way. I don't think it's actually possible to express + that as a TS rule using the current set up, so we'll enforce that later in + the Python tactic. + """ + mapper = ts.scope("/:Mapper", protect=True) + with ts.scope("/:Rules:TagCrossDoc"): + with ts.scope(":MustMap") as exists: + for x in ("A", "B"): + ts[f":IsAbstraction{x}"].map({ + ts[f":MAlpha{x}"]: ts[mapper[":Abstraction"]] + }) + for x in ("A", "A'", "B", "B'"): + ts[f":Doc{x}IsDoc"].map({ + ts[f":Doc{x}"]: ts["/:Document"], + ts[f":Chunk{x}"]: ts["/:Chunk"], + }) + for x in ("A", "B"): + ts[f":DocMap{x}"].map({ + ts[f":Doc{x}"]: ts[":DocType"], + ts[f":Doc{x}'"]: ts[":Doc'Type"], + }) + ts[f":ChunkMap{x}"].map({ + ts[f":Chunk{x}"]: ts[":ChunkType"], + ts[f":Chunk{x}'"]: ts[":Chunk'Type"], + }) + ts[f":MAlpha{x}"].map({ + ts[f":Doc{x}"]: ts[":AlphaDoc"], + ts[f":Doc{x}'"]: ts[":AlphaDoc'"], + ts[f":Chunk{x}"]: ts[":AlphaChunk"], + }) + RegisterRule(ts) + equivalence_classes = [ + [":ChunkType", ":Chunk'Type"], + [":DocType", ":Doc'Type"], + [":DocA", ":DocB", ":DocA'", ":DocB'"], + [":DocMapA", ":DocMapB"], + [":DocAIsDoc", ":DocBIsDoc", ":DocA'IsDoc", ":DocB'IsDoc"], + [":ChunkA", ":ChunkB", ":ChunkA'", ":ChunkB'"], + ] + for equivalence_class in equivalence_classes: + equivalence_class = [exists[name] for name in equivalence_class] + AssertNodesEqual(ts, equivalence_class, "", equal_type="/MAYBE=") diff --git a/examples/program_analysis/api_migration.py b/examples/program_analysis/api_migration.py new file mode 100644 index 0000000..ddc3245 --- /dev/null +++ b/examples/program_analysis/api_migration.py @@ -0,0 +1,72 @@ +"""Learning to generalize an API migration.""" +import random +import os +from timeit import default_timer as timer +from ui import serve +from lazy_structure import LazyStructure +from analyzelib import LoadDocument, AnalyzeCodelets, CompleteAnalogyTactic + +def Main(): + """Runs the analogy-maker and displays the output.""" + start = timer() + print("Setting up the structure...") + random.seed(24) + demo_path = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + demo_path += "/examples/program_analysis/paper_demos" + chunks = [(0, 7), (9, 5)] + sources = [] + for i in range(1, 4): + before = LoadDocument(f"{demo_path}/api{i}.before.txt", chunks) + after = LoadDocument(f"{demo_path}/api{i}.after.txt", + chunks if i != 3 else []) + sources.append((before, after)) + + chunks = [] + docs_before = LoadDocument(f"{demo_path}/docs.before.txt") + docs_after = LoadDocument(f"{demo_path}/docs.after.txt") + + structure = LazyStructure( + [source for sourcelist in sources for source in sourcelist] + + [docs_before, docs_after], AnalyzeCodelets) + + for document in structure.documents[:-2]: + for chunk in document.chunks: + structure.ChunkToNode(document, chunk) + + def insertChunks(doc, chunk_texts): + for chunk in doc.chunks: + if doc.ChunkWord(chunk) in chunk_texts: + structure.ChunkToNode(doc, chunk) + + doc_words = set({ + "cam_record_video", "cam_record_audio", "cam_record_frame", + "On", "error", "failure", "returns", + "-1", "-2", "-3", "-4", "-5", "-6", + }) + insertChunks(docs_before, doc_words) + insertChunks(docs_after, doc_words) + + for i in range(0, 8, 2): + structure.AnnotateDocuments(dict({ + i: "/:TransformPair:Before", + (i + 1): "/:TransformPair:After", + })) + if i != 6: + structure.AnnotateDocuments(dict({ + i: "/:Documentation:Code", + 6: "/:Documentation:Docs", + })) + structure.AnnotateDocuments(dict({ + (i + 1): "/:Documentation:Code", + 7: "/:Documentation:Docs", + })) + + structure.MarkDocumentGenerated(5) + CompleteAnalogyTactic(structure, sources) + structure.GetGeneratedDocument(5) + + print(timer() - start) + serve.start_server(structure) + +if __name__ == "__main__": + Main() diff --git a/examples/program_analysis/lazy_structure.py b/examples/program_analysis/lazy_structure.py new file mode 100644 index 0000000..682767a --- /dev/null +++ b/examples/program_analysis/lazy_structure.py @@ -0,0 +1,305 @@ +"""Helper methods for encoding text documents in Triplet Structures. + +Here I will use: + 1. 'Word' to refer to an *abstract word*, such a 'hello'. + 2. 'Chunk' to refer to an instance of a word in a document. + 3. 'Node' or 'Symbol' refers to a node in the structure. + +Currently only implementing 'flat' reads (i.e., no ASTs), but the goal is to +have the interface simple enough to support ASTs in a straight-forward way. +""" +from ts_lib import TripletStructure +from runtime.runtime import TSRuntime + +SPECIAL_CHARACTERS = [ + "(", ")", "[", "]", "{", "}", ".", ";", "*", "/", "+", + "&", '"', ",", "`", "\n", +] + +class LazyStructure: + """Structure representing multiple LazyDocuments. + """ + def __init__(self, documents, codelet): + """Initialize a LazyStructure given a collection of LazyDocuments. + + @codelet(ts) is a callback that should initialize the TripletStructure. + """ + self.ts = TripletStructure() + + self.documents = documents + self.document_scopes = dict({ + document: self.ts.scope(f"/:Documents:{i}") + for i, document in enumerate(documents) + }) + + # Set-union trick from: https://stackoverflow.com/questions/30773911 + self.words = sorted(set().union(*(doc.words for doc in documents))) + # Maps words to symbol names in the structure. + self.dictionary = dict({ + word: self.ts[f"/:Dictionary:{i}"] + for i, word in enumerate(self.words) + }) + + for symbol in self.dictionary.values(): + self.ts[":IsWordMap"].map({symbol: self.ts["/:Word"]}) + + for scope in self.document_scopes.values(): + scope[":_IsMember"].map({scope[":_"]: self.ts["/:Document"]}) + + self.ts.add_node("/:Chunk") + + for document in self.documents: + document.InitializeWorkspace(self.ts) + + codelet(self.ts) + self.rt = TSRuntime(self.ts) + + def ChunkToNode(self, document, chunk): + """Adds a chunk explicitly into the workspace, if it's not already.""" + assert chunk in document.chunks + scope = self.document_scopes[document] + chunk_node = document.ChunkToNode(chunk, self, scope) + if chunk_node is not None: + scope[":_IsMember"].map({chunk_node: self.ts["/:Chunk"]}) + self.ts.commit(False) + + def NodeOfChunk(self, document, chunk): + """Find the node in the workspace corresponding to a chunk.""" + scope = self.document_scopes[document] + return document.NodeOfChunk(scope, chunk) + + def GetGeneratedDocument(self, index): + """Parses a document (created by Sifter) out of the workspace. + + Specifically, it updates self.documents[index] to point to a + LazyGeneratedTextDocument that describes the textual contents parsed + from the workspace. Used after Sifter completes an analogy to get the + corresponding code. + """ + old_document = self.documents[index] + document = LazyGeneratedTextDocument(self, index) + self.documents[index] = document + self.document_scopes[document] = self.document_scopes[old_document] + self.document_scopes.pop(old_document) + + def AnnotateDocuments(self, fact_map): + """Annotates the document. + + @fact_map should be a map dict({doc_index: node}). + """ + fact_node = self.ts["/:DocumentAnnotations:??"] + for doc_id, annotation in fact_map.items(): + fact_node.map({ + self.ts[f"/:Documents:{doc_id}:_"]: self.ts[annotation], + }) + + def MarkDocumentGenerated(self, index): + """Indicates that the @index document should be generated by Sifter.""" + self.ts[f"/:DocumentAnnotations:??"].map({ + self.ts[f"/:Documents:{index}:_"]: self.ts["/:Mapper:TOP"], + self.ts[f"/:Documents:{index}:_IsMember"]: self.ts["/:Mapper:TOP"], + }) + +class LazyTextDocument: + """Represents a single text document.""" + def __init__(self, text, special=None): + """Initializes the LazyTextDocument, including tokenization. + + @special can contain a list of document-specific tokens. + """ + self.text = text + # [(start, length)] + self.chunks = self.ChunkText(text, special) + self.words = set(map(self.ChunkWord, self.chunks)) + self.annotations = [] + + def AnnotateChunks(self, fact_map): + """Annotates the document. + + - @fact_map should be a map dict({chunk: node}). + Each node referenced in the map will be created when the structure is + initialized. The corresponding fact node will be created at runtime + when the first referenced chunk is created. Only supports one type per + concrete. + """ + self.annotations.append(fact_map) + + def InitializeWorkspace(self, ts): + """Add an initial set of facts to the workspace.""" + for node in set({"/:Follower:Before", "/:Follower:After"}): + ts.add_node(node) + self.annotations = [ + dict({key: ts[value] for key, value in fact_map.items()}) + for fact_map in self.annotations] + + def ChunkToNode(self, chunk, structure, scope): + """Returns a delta adding @chunk to @structure.ts. + + Also returns the NodeWrapper corresponding to the chunk. + """ + ts = structure.ts + chunk_start, _ = chunk + local_name = f":Chunks:{chunk_start}" + if local_name in scope: + return None + + # (1) Add a node for the chunk. + chunk_node = scope[local_name] + # (2) Assert that it is an instance of the corresponding word. + word = structure.dictionary[self.ChunkWord(chunk)] + scope[f":IsWord:{chunk_start}"].map({chunk_node: word}) + # (3) If the immediately-prior or immediately-following chunk is + # already in the structure, connect it. TODO(masotoud): Refactor this, + # also maybe add partial facts anyways?. + chunk_index = self.chunks.index(chunk) + if chunk_index > 0: + left_start, _ = self.chunks[chunk_index - 1] + left_node = scope.protected()[f":Chunks:{left_start}"] + if ts.has_node(left_node): + scope[f":Following:{left_start}:{chunk_start}"].map({ + ts[left_node]: ts["/:Follower:Before"], + chunk_node: ts["/:Follower:After"], + }) + if (chunk_index + 1) < len(self.chunks): + right_start, _ = self.chunks[chunk_index + 1] + right_node = scope.protected()[f":Chunks:{right_start}"] + if ts.has_node(right_node): + scope[f":Following:{right_start}:{chunk_start}"].map({ + chunk_node: ts["/:Follower:Before"], + ts[right_node]: ts["/:Follower:After"], + }) + # (4) If the chunk is annotated, include its annotation. + for i, fact_map in enumerate(self.annotations): + if chunk in fact_map: + scope[f":Annotations:{i}"].map({chunk_node: fact_map[chunk]}) + + return chunk_node + + @staticmethod + def ChunkText(text, special): + """Tokenizes @text based on standard delimiters and @special. + + Returns a list of (start_index, length) pairs. For example, + ChunkText("Hi 5+-2", ["-2"]) + = [(0, 2), (2, 1), (3, 1), (4, 1), (5, 2)] + """ + chunks = [] + # Read characters from @text until either a space is reached or a + # special word. + start_chunk = None + i = 0 + def maybe_wrap_chunk(): + if start_chunk is not None: + chunks.append((start_chunk, i - start_chunk)) + return None + + while i < len(text): + if text[i] in (' ', '\n'): + start_chunk = maybe_wrap_chunk() + i += 1 + else: + try: + word = next(word for word in special + if text[i:].startswith(word)) + # If there was an existing chunk we were reading, wrap it + # up. + start_chunk = maybe_wrap_chunk() + # Then this word forms a new chunk. + chunks.append((i, len(word))) + i += len(word) + except StopIteration: + if start_chunk is None: + start_chunk = i + i += 1 + start_chunk = maybe_wrap_chunk + return chunks + + def ChunkWord(self, chunk): + """Returns the string corresponding to @chunk=(start_index, length).""" + start, length = chunk + return self.text[start:(start + length)] + + def NodeOfChunk(self, scope, chunk): + """Returns the workspace node corresponding to @chunk.""" + start, _ = chunk + return scope.protected()[f":Chunks:{start}"] + + def FindChunk(self, chunk_word): + """Returns the first chunk with its string being @ChunkWord.""" + for chunk in self.chunks: + if self.ChunkWord(chunk) == chunk_word: + return chunk + return None + + def FindChunks(self, chunk_word): + """Returns all chunks with corresponding string being @ChunkWord.""" + return [chunk for chunk in self.chunks + if self.ChunkWord(chunk) == chunk_word] + +class LazyGeneratedTextDocument(LazyTextDocument): + """Like LazyTextDocument, except the contents are read from the workspace. + + The basic use for this is when Sifter _completes_ an analogy, and so + creates new code in the workspace. A LazyGeneratedTextDocument can read + that generated code out of the workspace. + """ + def __init__(self, structure, index): + """Initialize the LazyGeneratedTextDocument.""" + self.structure = structure + self.scope = structure.ts.scope(f"/:Documents:{index}", protect=True) + self.ExtractChunks() + + def ExtractChunks(self): + """Parses the nodes in the workspace into a string representation.""" + ts, scope = self.structure.ts, self.scope + chunks = set( + fact[1] + for fact in ts.lookup(scope[":_IsMember"], None, "/:Chunk")) + # (1) Get the poset of the nodes in this document. + orders = set() + for fact in ts.lookup(None, None, "/:Follower:Before"): + map_node, before_node, _ = fact + if before_node not in chunks: + continue + for fact in ts.lookup(map_node, None, "/:Follower:After"): + after_node = fact[1] + if after_node in chunks: + orders.add((before_node, after_node)) + # (2) Extend the poset into a toset. + sorted_chunks = [] + while chunks: + sorted_chunks.append([ + chunk for chunk in sorted(chunks) + if not any(order[1] == chunk for order in orders)]) + if not sorted_chunks[-1]: + sorted_chunks[-1] = sorted(chunks) # Cut loops. + chunks = chunks - set(sorted_chunks[-1]) + orders = set(order for order in orders if set(order) <= chunks) + linear_chunks = [] + for chunk_layer in sorted_chunks: + linear_chunks.extend(chunk_layer) + # (3) Translate the chunks to words. + chunk_words = [] + for chunk in linear_chunks: + for word, node in self.structure.dictionary.items(): + if ts.lookup(None, chunk, node.full_name): + chunk_words.append(word) + break + else: + chunk_words.append(f"[Chunk: {chunk}]") + for i, chunk_word in enumerate(chunk_words): + if chunk_word[-1] in (";", "{", "}"): + chunk_words[i] += "\n" + chunk_words[i] = chunk_words[i] + " " + self.text = "" + self.chunks = [] + for word in chunk_words: + self.chunks.append((len(self.text), len(word))) + self.text += word + self.chunk_to_node = dict(zip(self.chunks, linear_chunks)) + + def ChunkToNode(self, chunk, structure, scope): + raise NotImplementedError + + def NodeOfChunk(self, scope, chunk): + return self.chunk_to_node[chunk] diff --git a/examples/program_analysis/paper_demos/api1.after.txt b/examples/program_analysis/paper_demos/api1.after.txt new file mode 100644 index 0000000..d4f6957 --- /dev/null +++ b/examples/program_analysis/paper_demos/api1.after.txt @@ -0,0 +1,14 @@ +#include +#include "cam.h" + +#define BUFFER_SIZE (1024 * 1024) +char buffer[BUFFER_SIZE]; + +void try_record_video() { + int result = cam_record_video(buffer, BUFFER_SIZE); + if (result == -4) { + printf("Could not record video.\n"); + } else { + printf("Recording video worked!\n"); + } +} diff --git a/examples/program_analysis/paper_demos/api1.before.txt b/examples/program_analysis/paper_demos/api1.before.txt new file mode 100644 index 0000000..0703da3 --- /dev/null +++ b/examples/program_analysis/paper_demos/api1.before.txt @@ -0,0 +1,14 @@ +#include +#include "cam.h" + +#define BUFFER_SIZE (1024 * 1024) +char buffer[BUFFER_SIZE]; + +void try_record_video() { + int result = cam_record_video(buffer, BUFFER_SIZE, RES_AUTO); + if (result == -1) { + printf("Could not record video.\n"); + } else { + printf("Recording video worked!\n"); + } +} diff --git a/examples/program_analysis/paper_demos/api2.after.txt b/examples/program_analysis/paper_demos/api2.after.txt new file mode 100644 index 0000000..340e681 --- /dev/null +++ b/examples/program_analysis/paper_demos/api2.after.txt @@ -0,0 +1,14 @@ +#include +#include "cam.h" + +#define BUFFER_SIZE (1024 * 1024) +char buffer[BUFFER_SIZE]; + +void try_record_audio() { + int result = cam_record_audio(buffer, BUFFER_SIZE); + if (result == -2) { + printf("Could not record audio.\n"); + } else { + printf("Recorded audio!\n"); + } +} diff --git a/examples/program_analysis/paper_demos/api2.before.txt b/examples/program_analysis/paper_demos/api2.before.txt new file mode 100644 index 0000000..f74c538 --- /dev/null +++ b/examples/program_analysis/paper_demos/api2.before.txt @@ -0,0 +1,14 @@ +#include +#include "cam.h" + +#define BUFFER_SIZE (1024 * 1024) +char buffer[BUFFER_SIZE]; + +void try_record_audio() { + int result = cam_record_audio(buffer, BUFFER_SIZE, RES_AUTO); + if (result == -5) { + printf("Could not record audio.\n"); + } else { + printf("Recorded audio!\n"); + } +} diff --git a/examples/program_analysis/paper_demos/api3.after.txt b/examples/program_analysis/paper_demos/api3.after.txt new file mode 100644 index 0000000..e69de29 diff --git a/examples/program_analysis/paper_demos/api3.before.txt b/examples/program_analysis/paper_demos/api3.before.txt new file mode 100644 index 0000000..cb8d731 --- /dev/null +++ b/examples/program_analysis/paper_demos/api3.before.txt @@ -0,0 +1,14 @@ +#include +#include "cam.h" + +#define BUFFER_SIZE (1024) +char buffer[BUFFER_SIZE]; + +void try_record_still() { + int result = cam_record_frame(buffer, BUFFER_SIZE, RES_AUTO); + if (result == -3) { + printf("Could not record audio.\n"); + } else { + printf("Recorded audio!\n"); + } +} diff --git a/examples/program_analysis/paper_demos/bash.txt b/examples/program_analysis/paper_demos/bash.txt new file mode 100644 index 0000000..083401c --- /dev/null +++ b/examples/program_analysis/paper_demos/bash.txt @@ -0,0 +1,17 @@ +int cd_builtin (list) WORD_LIST *list; /*@\color{red}{//B1} @*/ +{ + char *dirname, *cdpath, *path, *temp; +... + +struct builtin static_shell_builtins[] = { +... + { "cd", cd_builtin, ... }, /*@\color{red}{//B2} @*/ +... +struct builtin *shell_builtins = static_shell_builtins; /*@\color{red}{//B3} @*/ + +struct builtin * builtin_address_internal + (name, disabled_okay) + char *name; int disabled_okay; { /*@\color{red}{//B4} @*/ +... + j = shell_builtins[mid].name[0] - name[0]; +... diff --git a/examples/program_analysis/paper_demos/docs.after.txt b/examples/program_analysis/paper_demos/docs.after.txt new file mode 100644 index 0000000..e162420 --- /dev/null +++ b/examples/program_analysis/paper_demos/docs.after.txt @@ -0,0 +1,9 @@ +# CameraLib v2.0 +### `cam_record_video(buffer, buffer_size)` +Records video from the main camera into `buffer` until `buffer_size` bytes are reached. On error returns -4. + +### `cam_record_audio(buffer, buffer_size)` +Uses the main camera's microphone to record audio into `buffer` until `buffer_size` bytes have been recorded. On error returns -2. + +### `cam_record_frame(buffer, buffer_size)` +Uses the main camera to record a single image to `buffer`. Automatically sets the resolution to fit in `buffer_size`. On failure returns -6. diff --git a/examples/program_analysis/paper_demos/docs.before.txt b/examples/program_analysis/paper_demos/docs.before.txt new file mode 100644 index 0000000..1f10feb --- /dev/null +++ b/examples/program_analysis/paper_demos/docs.before.txt @@ -0,0 +1,9 @@ +# CameraLib v1.0 +### `cam_record_video(buffer, buffer_size, resolution)` +Records video from the main camera into `buffer` until `buffer_size` bytes are reached. On error returns -1. + +### `cam_record_audio(buffer, buffer_size, resolution)` +Uses the main camera's microphone to record audio into `buffer` until `buffer_size` bytes have been recorded. On error returns -5. + +### `cam_record_frame(buffer, buffer_size, resolution)` +Uses the main camera to record a single image to `buffer`. Automatically sets the resolution to fit in `buffer_size`. On failure returns -3. diff --git a/examples/program_analysis/paper_demos/fish.txt b/examples/program_analysis/paper_demos/fish.txt new file mode 100644 index 0000000..2b933f0 --- /dev/null +++ b/examples/program_analysis/paper_demos/fish.txt @@ -0,0 +1,13 @@ +int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { /*@\color{red}{//F1} @*/ + const wchar_t *cmd = argv[0]; + int argc = builtin_count_args(argv); +... + +static const builtin_data_t builtin_datas[] = { +... + {L"cd", &builtin_cd, ...}, /*@\color{red}{//F2} @*/ +... + +static const builtin_data_t *builtin_lookup(const wcstring &name) { /*@\color{red}{//F3} @*/ + const builtin_data_t *array_end = builtin_datas + BUILTIN_COUNT; +... diff --git a/examples/program_analysis/paper_demos/gemm1.after.txt b/examples/program_analysis/paper_demos/gemm1.after.txt new file mode 100644 index 0000000..5d49a4c --- /dev/null +++ b/examples/program_analysis/paper_demos/gemm1.after.txt @@ -0,0 +1,6 @@ +assert(k > 0); +int outer = k * 100; +int inner = k * 10; +read_mat(outer, inner, &A); +read_mat(inner, outer, &B); +gemm_skinny(A, B, &C, outer, inner, outer); diff --git a/examples/program_analysis/paper_demos/gemm1.before.txt b/examples/program_analysis/paper_demos/gemm1.before.txt new file mode 100644 index 0000000..5ff9d2b --- /dev/null +++ b/examples/program_analysis/paper_demos/gemm1.before.txt @@ -0,0 +1,6 @@ +assert(k > 0); +int outer = k * 100; +int inner = k * 10; +read_mat(outer, inner, &A); +read_mat(inner, outer, &B); +gemm_large(A, B, &C, outer, inner, outer); diff --git a/examples/program_analysis/paper_demos/gemm2.after.txt b/examples/program_analysis/paper_demos/gemm2.after.txt new file mode 100644 index 0000000..19bb8aa --- /dev/null +++ b/examples/program_analysis/paper_demos/gemm2.after.txt @@ -0,0 +1,10 @@ +assert(k > 1); +int outer = k, A_cols = k / 2; +read_mat(outer, A_cols, &A); +read_mat(A_cols, outer, &B); +while (!done(A, B)) { + read_row(&A); + read_col(&B); + outer++; +} +gemm_skinny(A, B, &C, outer, A_cols, outer); diff --git a/examples/program_analysis/paper_demos/gemm2.before.txt b/examples/program_analysis/paper_demos/gemm2.before.txt new file mode 100644 index 0000000..6e0ab1c --- /dev/null +++ b/examples/program_analysis/paper_demos/gemm2.before.txt @@ -0,0 +1,10 @@ +assert(k > 1); +int outer = k, A_cols = k / 2; +read_mat(outer, A_cols, &A); +read_mat(A_cols, outer, &B); +while (!done(A, B)) { + read_row(&A); + read_col(&B); + outer++; +} +gemm_large(A, B, &C, outer, A_cols, outer); diff --git a/examples/program_analysis/paper_demos/gemm3.after.txt b/examples/program_analysis/paper_demos/gemm3.after.txt new file mode 100644 index 0000000..e69de29 diff --git a/examples/program_analysis/paper_demos/gemm3.before.txt b/examples/program_analysis/paper_demos/gemm3.before.txt new file mode 100644 index 0000000..3cd1072 --- /dev/null +++ b/examples/program_analysis/paper_demos/gemm3.before.txt @@ -0,0 +1,6 @@ +assert(k > 5); +int AB_rowcol = k; +int inner = k * k; +read_mat(AB_rowcol, inner, &A); +read_mat(inner, AB_rowcol, &B); +gemm_large(A, B, &C, AB_rowcol, inner, AB_rowcol); diff --git a/examples/program_analysis/paper_demos/gemm4.bad.txt b/examples/program_analysis/paper_demos/gemm4.bad.txt new file mode 100644 index 0000000..a57dc33 --- /dev/null +++ b/examples/program_analysis/paper_demos/gemm4.bad.txt @@ -0,0 +1,6 @@ +assert(k > 0); +int outer = k * 10; +int inner = k * 10; +read_mat(outer, inner, &A); +read_mat(inner, outer, &B); +gemm_large(A, B, &C); diff --git a/examples/program_analysis/program_understanding.py b/examples/program_analysis/program_understanding.py new file mode 100644 index 0000000..2b1e141 --- /dev/null +++ b/examples/program_analysis/program_understanding.py @@ -0,0 +1,72 @@ +"""Comparative program understanding between shell implementation snippets.""" +import random +import os +from timeit import default_timer as timer +from tactic_utils import Fix, ApplyRulesMatching +from ui import serve +from lazy_structure import LazyStructure +from analyzelib import LoadDocument, AnalyzeCodelets +from analogy_utils import Analogy + +def Main(): + """Runs the analogy-maker and displays the output.""" + start = timer() + print("Setting up the structure...") + random.seed(24) + demo_path = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + demo_path += "/examples/program_analysis/paper_demos" + + extra_special = [ + "static_shell_builtins", "shell_builtins", "builtin_address_internal", + "builtin_datas", "builtin_lookup", "_builtin", "builtin_"] + bash = LoadDocument(f"{demo_path}/bash.txt", extra_special=extra_special) + fish = LoadDocument(f"{demo_path}/fish.txt", extra_special=extra_special) + + # These could be provided, eg., by an AST, or we could even have Sifter + # rewrite rules which identify them. We can also make each the membor of + # its sub-struct then make the sub-struct a member of the parent struct. + bash.AnnotateChunks(dict({ + bash.FindChunks("shell_builtins")[0]: "/:Semantics:Collection", + bash.FindChunks("cd")[2]: "/:Semantics:CollectionMember", + })) + fish.AnnotateChunks(dict({ + fish.FindChunks("builtin_datas")[0]: "/:Semantics:Collection", + fish.FindChunks("cd")[2]: "/:Semantics:CollectionMember", + })) + bash.AnnotateChunks(dict({ + bash.FindChunks("shell_builtins")[-1]: "/:Semantics:FunctionBody", + bash.FindChunks("builtin_address_internal")[-1]: "/:Semantics:Function", + })) + fish.AnnotateChunks(dict({ + fish.FindChunks("builtin_datas")[-1]: "/:Semantics:FunctionBody", + fish.FindChunks("builtin_lookup")[-1]: "/:Semantics:Function", + })) + + structure = LazyStructure([bash, fish], AnalyzeCodelets) + for document in structure.documents: + for chunk in document.chunks: + structure.ChunkToNode(document, chunk) + + print("Identifying word pairs...") + for word in ["cd", "shell_builtins", "builtin_datas"]: + ApplyRulesMatching(structure.rt, "SameWord", dict({ + "/:Rules:SameWord:MustMap:Word": structure.dictionary[word].full_name, + })) + + print("Finding the analogy...") + analogy = Analogy.Begin(structure.rt, dict({ + "/:Mapper:NoSlipRules:A": + structure.NodeOfChunk(bash, bash.FindChunk("cd")), + "/:Mapper:NoSlipRules:B": + structure.NodeOfChunk(fish, fish.FindChunk("cd")), + }), extend_here=False) + + Fix(analogy.ExtendMap, + ["/:Semantics:CollectionMember", "/:Semantics:Function", + "/:Semantics:FunctionBody", "/:SameWord"]) + + print(timer() - start) + serve.start_server(structure) + +if __name__ == "__main__": + Main() diff --git a/examples/program_analysis/transform_learning.py b/examples/program_analysis/transform_learning.py new file mode 100644 index 0000000..a790d84 --- /dev/null +++ b/examples/program_analysis/transform_learning.py @@ -0,0 +1,61 @@ +"""Learning to generalize a program optimization.""" +import random +import os +from timeit import default_timer as timer +from ui import serve +from lazy_structure import LazyStructure +from analyzelib import LoadDocument, AnalyzeCodelets, CompleteAnalogyTactic + +def Main(): + """Runs the analogy-maker and displays the output.""" + start = timer() + print("Setting up the structure...") + random.seed(24) + demo_path = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + demo_path += "/examples/program_analysis/paper_demos" + sources = [] + for i in range(1, 4): + with open(f"{demo_path}/gemm{i}.before.txt", "r") as peek: + n_lines = len(peek.readlines()) + chunks = [(0, n_lines - 1)] + before = LoadDocument(f"{demo_path}/gemm{i}.before.txt", chunks) + after = LoadDocument(f"{demo_path}/gemm{i}.after.txt", chunks) + sources.append((before, after)) + + def annotateGTHalf(doc, outer_name, inner_name): + outer_chunks = list(doc.FindChunks(outer_name))[-2:] + inner_chunk = list(doc.FindChunks(inner_name))[-1] + for outer_chunk in outer_chunks: + doc.AnnotateChunks(dict({ + outer_chunk: "/:Semantics:Greater", + inner_chunk: "/:Semantics:LTHalf", + })) + + # This could be provided, eg., by an abstract interpreter. + annotateGTHalf(sources[0][0], "outer", "inner") + annotateGTHalf(sources[1][0], "outer", "A_cols") + annotateGTHalf(sources[2][0], "AB_rowcol", "inner") + + structure = LazyStructure( + [source for sourcelist in sources for source in sourcelist], + AnalyzeCodelets) + + for document in structure.documents: + for chunk in document.chunks: + structure.ChunkToNode(document, chunk) + + for i in range(0, 6, 2): + structure.AnnotateDocuments(dict({ + i: "/:TransformPair:Before", + (i + 1): "/:TransformPair:After", + })) + + structure.MarkDocumentGenerated(5) + CompleteAnalogyTactic(structure, sources) + structure.GetGeneratedDocument(5) + + print(timer() - start) + serve.start_server(structure) + +if __name__ == "__main__": + Main() diff --git a/examples/program_analysis/ui/BUILD b/examples/program_analysis/ui/BUILD new file mode 100644 index 0000000..9d28957 --- /dev/null +++ b/examples/program_analysis/ui/BUILD @@ -0,0 +1,12 @@ +py_library( + name = "serve", + srcs = ["serve.py"], + visibility = ["//:__subpackages__"], + deps = [":lazy_structure_parser"], +) + +py_library( + name = "lazy_structure_parser", + srcs = ["lazy_structure_parser.py"], + visibility = ["//:__subpackages__"], +) diff --git a/examples/program_analysis/ui/index.html b/examples/program_analysis/ui/index.html new file mode 100644 index 0000000..82cc511 --- /dev/null +++ b/examples/program_analysis/ui/index.html @@ -0,0 +1,13 @@ + + + + Sifter + + + + + + + + + diff --git a/examples/program_analysis/ui/index.js b/examples/program_analysis/ui/index.js new file mode 100644 index 0000000..4f4c49c --- /dev/null +++ b/examples/program_analysis/ui/index.js @@ -0,0 +1,74 @@ +var base = "http://127.0.0.1:8001" + +var friends = new Map(); +var chunk_containers = new Map(); +function load_structure(structure) { + console.log(structure); + var documents = structure["documents"]; + for (var i = 0; i < documents.length; i++) { + var document_container = $("
").appendTo($("body")); + console.log(documents[i]); + var chunks = documents[i].chunks; + for (var j = 0; j < chunks.length; j++) { + var chunk_container = $("").appendTo(document_container); + var text = documents[i].text.substr(chunks[j][1], chunks[j][2]); + chunk_container.text(text); + if (chunks[j][0] != false) { + chunk_container.addClass("chunk-in-structure"); + chunk_container.attr("data-chunk-gid", chunks[j][0]); + chunk_containers.set(chunks[j][0], chunk_container); + } else { + chunk_container.attr("data-chunk-gid", ""); + } + } + document_container.addClass("document"); + if (documents[i].generated) { + document_container.addClass("generated"); + } + var dragger = new PlainDraggable(document_container.get()[0]); + } + var maps = structure["maps"]; + for (var i = 0; i < maps.length; i++) { + var map = maps[i]; + for (var j = 0; j < map.length; j++) { + if (!friends.has(map[j])) { + friends.set(map[j], []); + } + chunk_containers.get(map[j]).addClass("chunk-in-map"); + for (var k = 0; k < map.length; k++) { + friends.get(map[j]).push(map[k]); + } + } + } + console.log(chunk_containers); +} + +$("body").on("mouseover", ".chunk-in-structure", function() { + if ($(this).attr("data-chunk-gid") == "") { + return; + } + var my_friends = friends.get($(this).attr("data-chunk-gid")); + if (my_friends === undefined) { + return; + } + for (var i = 0; i < my_friends.length; i++) { + chunk_containers.get(my_friends[i]).addClass("highlight"); + } +}); + +$("body").on("mouseout", ".chunk-in-structure", function() { + if ($(this).attr("data-chunk-gid") == "") { + return; + } + var my_friends = friends.get($(this).attr("data-chunk-gid")); + if (my_friends === undefined) { + return; + } + for (var i = 0; i < my_friends.length; i++) { + chunk_containers.get(my_friends[i]).removeClass("highlight"); + } +}); + +$.get(base + "/Structure", function (data) { + load_structure(data); +}); diff --git a/examples/program_analysis/ui/lazy_structure_parser.py b/examples/program_analysis/ui/lazy_structure_parser.py new file mode 100644 index 0000000..bae2dea --- /dev/null +++ b/examples/program_analysis/ui/lazy_structure_parser.py @@ -0,0 +1,68 @@ +"""Parse a LazyStructure to extract abstractions for the UI.""" +from collections import defaultdict +from lazy_structure import LazyGeneratedTextDocument + +def parse_lazy_structure(structure): + """Returns a dict representation of a LazyStructure. + + Returns a dictionary with + { + "documents": [ + { + "text": str, + "chunks": [(global_id, start, length)], + } + ], + "maps": [ + [chunk_1_gid, chunk_2_gid, ...] + ], + } + + maps[i][j] lists all chunks corresponding to the jth node in abstraction i. + """ + ts = structure.ts + parsed = dict({"documents": [], "maps": []}) + # (1) Add the documents. + all_chunk_nodes = set() + for document in structure.documents: + parsed_doc = dict({ + "text": document.text, + "chunks": [], + "generated": isinstance(document, LazyGeneratedTextDocument), + }) + for chunk in document.chunks: + chunk_node = structure.NodeOfChunk(document, chunk) + if ts.has_node(chunk_node): + start, length = chunk + parsed_doc["chunks"].append((chunk_node, start, length)) + all_chunk_nodes.add(chunk_node) + # Add fake chunks for the rest of the document. + parsed_doc["chunks"] = pad_chunks(parsed_doc["chunks"], document) + parsed["documents"].append(parsed_doc) + # (2) Add the maps. + abstract_nodes = defaultdict(set) + for fact in ts.lookup(None, None, "/:Mapper:Abstraction"): + for other_fact in ts.lookup(fact[1], None, None): + if other_fact[1] in all_chunk_nodes: + abstract_nodes[other_fact[2]].add(other_fact[1]) + parsed["maps"] = sorted(map(sorted, abstract_nodes.values())) + return parsed + +def pad_chunks(chunks, document): + """Fills in gaps in @chunks. + + For example, @chunks may only contain chunks of the document which have + actually been added to the structure. This iterates over @chunks, and + anywhere a gap is found it inserts a new chunk with the node name as False. + + @chunks should be a list of triples (node_name, start, length). The return + value is of the same format. + """ + padded = [(False, 0, 0)] + chunks = chunks + [(False, len(document.text), 0)] + for (global_id, start, length) in chunks: + last_end = padded[-1][1] + padded[-1][2] + if last_end < start: + padded.append((False, last_end, start - last_end)) + padded.append((global_id, start, length)) + return padded[1:-1] diff --git a/examples/program_analysis/ui/leader-line.min.js b/examples/program_analysis/ui/leader-line.min.js new file mode 100644 index 0000000..81a798e --- /dev/null +++ b/examples/program_analysis/ui/leader-line.min.js @@ -0,0 +1,2 @@ +/*! LeaderLine v1.0.5 (c) anseki https://anseki.github.io/leader-line/ */ +var LeaderLine=function(){"use strict";var te,g,y,S,_,o,t,h,f,p,a,i,l,v="leader-line",M=1,I=2,C=3,L=4,n={top:M,right:I,bottom:C,left:L},A=1,V=2,P=3,N=4,T=5,m={straight:A,arc:V,fluid:P,magnet:N,grid:T},ne="behind",r=v+"-defs",s='',ae={disc:{elmId:"leader-line-disc",noRotate:!0,bBox:{left:-5,top:-5,width:10,height:10,right:5,bottom:5},widthR:2.5,heightR:2.5,bCircle:5,sideLen:5,backLen:5,overhead:0,outlineBase:1,outlineMax:4},square:{elmId:"leader-line-square",noRotate:!0,bBox:{left:-5,top:-5,width:10,height:10,right:5,bottom:5},widthR:2.5,heightR:2.5,bCircle:5,sideLen:5,backLen:5,overhead:0,outlineBase:1,outlineMax:4},arrow1:{elmId:"leader-line-arrow1",bBox:{left:-8,top:-8,width:16,height:16,right:8,bottom:8},widthR:4,heightR:4,bCircle:8,sideLen:8,backLen:8,overhead:8,outlineBase:2,outlineMax:1.5},arrow2:{elmId:"leader-line-arrow2",bBox:{left:-7,top:-8,width:11,height:16,right:4,bottom:8},widthR:2.75,heightR:4,bCircle:8,sideLen:8,backLen:7,overhead:4,outlineBase:1,outlineMax:1.75},arrow3:{elmId:"leader-line-arrow3",bBox:{left:-4,top:-5,width:12,height:10,right:8,bottom:5},widthR:3,heightR:2.5,bCircle:8,sideLen:5,backLen:4,overhead:8,outlineBase:1,outlineMax:2.5},hand:{elmId:"leader-line-hand",bBox:{left:-3,top:-12,width:40,height:24,right:37,bottom:12},widthR:10,heightR:6,bCircle:37,sideLen:12,backLen:3,overhead:37},crosshair:{elmId:"leader-line-crosshair",noRotate:!0,bBox:{left:-96,top:-96,width:192,height:192,right:96,bottom:96},widthR:48,heightR:48,bCircle:96,sideLen:96,backLen:96,overhead:0}},E={behind:ne,disc:"disc",square:"square",arrow1:"arrow1",arrow2:"arrow2",arrow3:"arrow3",hand:"hand",crosshair:"crosshair"},ie={disc:"disc",square:"square",arrow1:"arrow1",arrow2:"arrow2",arrow3:"arrow3",hand:"hand",crosshair:"crosshair"},W=[M,I,C,L],x="auto",oe={x:"left",y:"top",width:"width",height:"height"},B=80,R=4,F=5,G=120,D=8,z=3.75,j=10,H=30,U=.5522847,Z=.25*Math.PI,u=/^\s*(\-?[\d\.]+)\s*(\%)?\s*$/,b="http://www.w3.org/2000/svg",e="-ms-scroll-limit"in document.documentElement.style&&"-ms-ime-align"in document.documentElement.style&&!window.navigator.msPointerEnabled,le=!e&&!!document.uniqueID,re="MozAppearance"in document.documentElement.style,se=!(e||re||!window.chrome||!window.CSS),ue=!e&&!le&&!re&&!se&&!window.chrome&&"WebkitAppearance"in document.documentElement.style,he=le||e?.2:.1,pe={path:P,lineColor:"coral",lineSize:4,plugSE:[ne,"arrow1"],plugSizeSE:[1,1],lineOutlineEnabled:!1,lineOutlineColor:"indianred",lineOutlineSize:.25,plugOutlineEnabledSE:[!1,!1],plugOutlineSizeSE:[1,1]},k=(a={}.toString,i={}.hasOwnProperty.toString,l=i.call(Object),function(e){var t,n;return e&&"[object Object]"===a.call(e)&&(!(t=Object.getPrototypeOf(e))||(n=t.hasOwnProperty("constructor")&&t.constructor)&&"function"==typeof n&&i.call(n)===l)}),w=Number.isFinite||function(e){return"number"==typeof e&&window.isFinite(e)},c=function(){var e,x={ease:[.25,.1,.25,1],linear:[0,0,1,1],"ease-in":[.42,0,1,1],"ease-out":[0,0,.58,1],"ease-in-out":[.42,0,.58,1]},b=1e3/60/2,t=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||function(e){setTimeout(e,b)},n=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame||function(e){clearTimeout(e)},a=Number.isFinite||function(e){return"number"==typeof e&&window.isFinite(e)},k=[],w=0;function l(){var i=Date.now(),o=!1;e&&(n.call(window,e),e=null),k.forEach(function(e){var t,n,a;if(e.framesStart){if((t=i-e.framesStart)>=e.duration&&e.count&&e.loopsLeft<=1)return a=e.frames[e.lastFrame=e.reverse?0:e.frames.length-1],e.frameCallback(a.value,!0,a.timeRatio,a.outputRatio),void(e.framesStart=null);if(t>e.duration){if(n=Math.floor(t/e.duration),e.count){if(n>=e.loopsLeft)return a=e.frames[e.lastFrame=e.reverse?0:e.frames.length-1],e.frameCallback(a.value,!0,a.timeRatio,a.outputRatio),void(e.framesStart=null);e.loopsLeft-=n}e.framesStart+=e.duration*n,t=i-e.framesStart}e.reverse&&(t=e.duration-t),a=e.frames[e.lastFrame=Math.round(t/b)],!1!==e.frameCallback(a.value,!1,a.timeRatio,a.outputRatio)?o=!0:e.framesStart=null}}),o&&(e=t.call(window,l))}function O(e,t){e.framesStart=Date.now(),null!=t&&(e.framesStart-=e.duration*(e.reverse?1-t:t)),e.loopsLeft=e.count,e.lastFrame=null,l()}return{add:function(n,e,t,a,i,o,l){var r,s,u,h,p,c,d,f,y,S,m,g,_,v=++w;function E(e,t){return{value:n(t),timeRatio:e,outputRatio:t}}if("string"==typeof i&&(i=x[i]),n=n||function(){},t=this._endIndex||this._string[this._currentIndex]<"0"||"9"=this._endIndex||this._string[this._currentIndex]<"0"||"9"=this._endIndex)return null;var e=null,t=this._string[this._currentIndex];if(this._currentIndex+=1,"0"===t)e=0;else{if("1"!==t)return null;e=1}return this._skipOptionalSpacesOrDelimiter(),e}};var a=function(e){if(!e||0===e.length)return[];var t=new o(e),n=[];if(t.initialCommandIsMoveTo())for(;t.hasMoreData();){var a=t.parseSegment();if(null===a)break;n.push(a)}return n},n=e.SVGPathElement.prototype.setAttribute,r=e.SVGPathElement.prototype.removeAttribute,d=e.Symbol?e.Symbol():"__cachedPathData",f=e.Symbol?e.Symbol():"__cachedNormalizedPathData",U=function(e,t,n,a,i,o,l,r,s,u){var h,p,c,d,f,y=function(e,t,n){return{x:e*Math.cos(n)-t*Math.sin(n),y:e*Math.sin(n)+t*Math.cos(n)}},S=(h=l,Math.PI*h/180),m=[];if(u)p=u[0],c=u[1],d=u[2],f=u[3];else{var g=y(e,t,-S);e=g.x,t=g.y;var _=y(n,a,-S),v=(e-(n=_.x))/2,E=(t-(a=_.y))/2,x=v*v/(i*i)+E*E/(o*o);1120*Math.PI/180){var C=c,L=n,A=a;c=s&&p=Math.abs(n)?0<=t?I:L:0<=n?C:M))})),E.position_path!==x.position_path||E.position_lineStrokeWidth!==x.position_lineStrokeWidth||[0,1].some(function(e){return E.position_plugOverheadSE[e]!==x.position_plugOverheadSE[e]||(i=b[e],o=x.position_socketXYSE[e],i.x!==o.x||i.y!==o.y||i.socketId!==o.socketId)||(t=_[e],n=x.position_socketGravitySE[e],(a=null==t?"auto":Array.isArray(t)?"array":"number")!==(null==n?"auto":Array.isArray(n)?"array":"number")||("array"===a?t[0]!==n[0]||t[1]!==n[1]:t!==n));var t,n,a,i,o})){switch(u.pathList.baseVal=v=[],u.pathList.animVal=null,E.position_path){case A:v.push([O(b[0]),O(b[1])]);break;case V:t="number"==typeof _[0]&&0<_[0]||"number"==typeof _[1]&&0<_[1],o=Z*(t?-1:1),l=Math.atan2(b[1].y-b[0].y,b[1].x-b[0].x),r=-l+o,c=Math.PI-l-o,d=_e(b[0],b[1])/Math.sqrt(2)*U,S={x:b[0].x+Math.cos(r)*d,y:b[0].y+Math.sin(r)*d*-1},m={x:b[1].x+Math.cos(c)*d,y:b[1].y+Math.sin(c)*d*-1},v.push([O(b[0]),S,m,O(b[1])]);break;case P:case N:s=[_[0],E.position_path===N?0:_[1]],h=[],p=[],b.forEach(function(e,t){var n,a,i,o,l,r=s[t];Array.isArray(r)?n={x:r[0],y:r[1]}:"number"==typeof r?n=e.socketId===M?{x:0,y:-r}:e.socketId===I?{x:r,y:0}:e.socketId===C?{x:0,y:r}:{x:-r,y:0}:(a=b[t?0:1],o=0<(i=E.position_plugOverheadSE[t])?G+(DR?(E.position_lineStrokeWidth-R)*F:0),e.socketId===M?((l=(e.y-a.y)/2)=t.x:t.dirId===r?e.y>=t.y:e.x<=t.x}function y(e,t){return t.dirId===o||t.dirId===r?e.x===t.x:e.y===t.y}function S(e){return e[0]?{contain:0,notContain:1}:{contain:1,notContain:0}}function m(e,t,n){return Math.abs(t[n]-e[n])}function g(e,t,n){return"x"===n?e.x=H?g(h[t.notContain],h[t.contain],o[t.contain]):h[t.contain].dirId)):(i=[{x:h[0].x,y:h[0].y},{x:h[1].x,y:h[1].y}],u.forEach(function(e,t){var n=0===t?1:0,a=m(i[t],i[n],o[t]);aj&&(y[a]-ej&&(y[a]-ea.outlineMax&&(t=a.outlineMax),t*=2*a.outlineBase,v=We(m,_.plugOutline_strokeWidthSE,e,t)||v,v=We(m,_.plugOutline_inStrokeWidthSE,e,_.plugOutline_colorTraSE[e]?t-he/(_.line_strokeWidth/pe.lineSize)/g.plugSizeSE[e]*2:t/2)||v)}),v)),(t.faces||ee.line||ee.plug||ee.lineOutline||ee.plugOutline)&&(ee.faces=(b=(E=e).curStats,k=E.aplStats,w=E.events,O=!1,!b.line_altColor&&We(E,k,"line_color",x=b.line_color,w.apl_line_color)&&(E.lineFace.style.stroke=x,O=!0),We(E,k,"line_strokeWidth",x=b.line_strokeWidth,w.apl_line_strokeWidth)&&(E.lineShape.style.strokeWidth=x+"px",O=!0,(re||le)&&(Ae(E,E.lineShape),le&&(Ae(E,E.lineFace),Ae(E,E.lineMaskCaps)))),We(E,k,"lineOutline_enabled",x=b.lineOutline_enabled,w.apl_lineOutline_enabled)&&(E.lineOutlineFace.style.display=x?"inline":"none",O=!0),b.lineOutline_enabled&&(We(E,k,"lineOutline_color",x=b.lineOutline_color,w.apl_lineOutline_color)&&(E.lineOutlineFace.style.stroke=x,O=!0),We(E,k,"lineOutline_strokeWidth",x=b.lineOutline_strokeWidth,w.apl_lineOutline_strokeWidth)&&(E.lineOutlineMaskShape.style.strokeWidth=x+"px",O=!0,le&&(Ae(E,E.lineOutlineMaskCaps),Ae(E,E.lineOutlineFace))),We(E,k,"lineOutline_inStrokeWidth",x=b.lineOutline_inStrokeWidth,w.apl_lineOutline_inStrokeWidth)&&(E.lineMaskShape.style.strokeWidth=x+"px",O=!0,le&&(Ae(E,E.lineOutlineMaskCaps),Ae(E,E.lineOutlineFace)))),We(E,k,"plug_enabled",x=b.plug_enabled,w.apl_plug_enabled)&&(E.plugsFace.style.display=x?"inline":"none",O=!0),b.plug_enabled&&[0,1].forEach(function(n){var e=b.plug_plugSE[n],t=e!==ne?ae[ie[e]]:null,a=Ne(n,t);We(E,k.plug_enabledSE,n,x=b.plug_enabledSE[n],w.apl_plug_enabledSE)&&(E.plugsFace.style[a.prop]=x?"url(#"+E.plugMarkerIdSE[n]+")":"none",O=!0),b.plug_enabledSE[n]&&(We(E,k.plug_plugSE,n,e,w.apl_plug_plugSE)&&(E.plugFaceSE[n].href.baseVal="#"+t.elmId,Pe(E,E.plugMarkerSE[n],a.orient,t.bBox,E.svg,E.plugMarkerShapeSE[n],E.plugsFace),O=!0,re&&Ae(E,E.plugsFace)),We(E,k.plug_colorSE,n,x=b.plug_colorSE[n],w.apl_plug_colorSE)&&(E.plugFaceSE[n].style.fill=x,O=!0,(se||ue||le)&&!b.line_colorTra&&Ae(E,le?E.lineMaskCaps:E.capsMaskLine)),["markerWidth","markerHeight"].forEach(function(e){var t="plug_"+e+"SE";We(E,k[t],n,x=b[t][n],w["apl_"+t])&&(E.plugMarkerSE[n][e].baseVal.value=x,O=!0)}),We(E,k.plugOutline_enabledSE,n,x=b.plugOutline_enabledSE[n],w.apl_plugOutline_enabledSE)&&(x?(E.plugFaceSE[n].style.mask="url(#"+E.plugMaskIdSE[n]+")",E.plugOutlineFaceSE[n].style.display="inline"):(E.plugFaceSE[n].style.mask="none",E.plugOutlineFaceSE[n].style.display="none"),O=!0),b.plugOutline_enabledSE[n]&&(We(E,k.plugOutline_plugSE,n,e,w.apl_plugOutline_plugSE)&&(E.plugOutlineFaceSE[n].href.baseVal=E.plugMaskShapeSE[n].href.baseVal=E.plugOutlineMaskShapeSE[n].href.baseVal="#"+t.elmId,[E.plugMaskSE[n],E.plugOutlineMaskSE[n]].forEach(function(e){e.x.baseVal.value=t.bBox.left,e.y.baseVal.value=t.bBox.top,e.width.baseVal.value=t.bBox.width,e.height.baseVal.value=t.bBox.height}),O=!0),We(E,k.plugOutline_colorSE,n,x=b.plugOutline_colorSE[n],w.apl_plugOutline_colorSE)&&(E.plugOutlineFaceSE[n].style.fill=x,O=!0,le&&(Ae(E,E.lineMaskCaps),Ae(E,E.lineOutlineMaskCaps))),We(E,k.plugOutline_strokeWidthSE,n,x=b.plugOutline_strokeWidthSE[n],w.apl_plugOutline_strokeWidthSE)&&(E.plugOutlineMaskShapeSE[n].style.strokeWidth=x+"px",O=!0),We(E,k.plugOutline_inStrokeWidthSE,n,x=b.plugOutline_inStrokeWidthSE[n],w.apl_plugOutline_inStrokeWidthSE)&&(E.plugMaskShapeSE[n].style.strokeWidth=x+"px",O=!0)))}),O)),(t.position||ee.line||ee.plug)&&(ee.position=Fe(e)),(t.path||ee.position)&&(ee.path=(C=(M=e).curStats,L=M.aplStats,A=M.pathList.animVal||M.pathList.baseVal,V=C.path_edge,P=!1,A&&(V.x1=V.x2=A[0][0].x,V.y1=V.y2=A[0][0].y,C.path_pathData=I=we(A,function(e){e.xV.x2&&(V.x2=e.x),e.y>V.y2&&(V.y2=e.y)}),Me(I,L.path_pathData)&&(M.linePath.setPathData(I),L.path_pathData=I,P=!0,le?(Ae(M,M.plugsFace),Ae(M,M.lineMaskCaps)):re&&Ae(M,M.linePath),M.events.apl_path&&M.events.apl_path.forEach(function(e){e(M,I)}))),P)),ee.viewBox=(B=(N=e).curStats,R=N.aplStats,F=B.path_edge,G=B.viewBox_bBox,D=R.viewBox_bBox,z=N.svg.viewBox.baseVal,j=N.svg.style,H=!1,T=Math.max(B.line_strokeWidth/2,B.viewBox_plugBCircleSE[0]||0,B.viewBox_plugBCircleSE[1]||0),W={x1:F.x1-T,y1:F.y1-T,x2:F.x2+T,y2:F.y2+T},N.events.new_edge4viewBox&&N.events.new_edge4viewBox.forEach(function(e){e(N,W)}),G.x=B.lineMask_x=B.lineOutlineMask_x=B.maskBGRect_x=W.x1,G.y=B.lineMask_y=B.lineOutlineMask_y=B.maskBGRect_y=W.y1,G.width=W.x2-W.x1,G.height=W.y2-W.y1,["x","y","width","height"].forEach(function(e){var t;(t=G[e])!==D[e]&&(z[e]=D[e]=t,j[oe[e]]=t+("x"===e||"y"===e?N.bodyOffset[e]:0)+"px",H=!0)}),H),ee.mask=(Y=(U=e).curStats,X=U.aplStats,q=!1,Y.plug_enabled?[0,1].forEach(function(e){Y.capsMaskMarker_enabledSE[e]=Y.plug_enabledSE[e]&&Y.plug_colorTraSE[e]||Y.plugOutline_enabledSE[e]&&Y.plugOutline_colorTraSE[e]}):Y.capsMaskMarker_enabledSE[0]=Y.capsMaskMarker_enabledSE[1]=!1,Y.capsMaskMarker_enabled=Y.capsMaskMarker_enabledSE[0]||Y.capsMaskMarker_enabledSE[1],Y.lineMask_outlineMode=Y.lineOutline_enabled,Y.caps_enabled=Y.capsMaskMarker_enabled||Y.capsMaskAnchor_enabledSE[0]||Y.capsMaskAnchor_enabledSE[1],Y.lineMask_enabled=Y.caps_enabled||Y.lineMask_outlineMode,(Y.lineMask_enabled&&!Y.lineMask_outlineMode||Y.lineOutline_enabled)&&["x","y"].forEach(function(e){var t="maskBGRect_"+e;We(U,X,t,Z=Y[t])&&(U.maskBGRect[e].baseVal.value=Z,q=!0)}),We(U,X,"lineMask_enabled",Z=Y.lineMask_enabled)&&(U.lineFace.style.mask=Z?"url(#"+U.lineMaskId+")":"none",q=!0,ue&&Ae(U,U.lineMask)),Y.lineMask_enabled&&(We(U,X,"lineMask_outlineMode",Z=Y.lineMask_outlineMode)&&(Z?(U.lineMaskBG.style.display="none",U.lineMaskShape.style.display="inline"):(U.lineMaskBG.style.display="inline",U.lineMaskShape.style.display="none"),q=!0),["x","y"].forEach(function(e){var t="lineMask_"+e;We(U,X,t,Z=Y[t])&&(U.lineMask[e].baseVal.value=Z,q=!0)}),We(U,X,"caps_enabled",Z=Y.caps_enabled)&&(U.lineMaskCaps.style.display=U.lineOutlineMaskCaps.style.display=Z?"inline":"none",q=!0,ue&&Ae(U,U.capsMaskLine)),Y.caps_enabled&&([0,1].forEach(function(e){var t;We(U,X.capsMaskAnchor_enabledSE,e,Z=Y.capsMaskAnchor_enabledSE[e])&&(U.capsMaskAnchorSE[e].style.display=Z?"inline":"none",q=!0,ue&&Ae(U,U.lineMask)),Y.capsMaskAnchor_enabledSE[e]&&(Me(t=Y.capsMaskAnchor_pathDataSE[e],X.capsMaskAnchor_pathDataSE[e])&&(U.capsMaskAnchorSE[e].setPathData(t),X.capsMaskAnchor_pathDataSE[e]=t,q=!0),We(U,X.capsMaskAnchor_strokeWidthSE,e,Z=Y.capsMaskAnchor_strokeWidthSE[e])&&(U.capsMaskAnchorSE[e].style.strokeWidth=Z+"px",q=!0))}),We(U,X,"capsMaskMarker_enabled",Z=Y.capsMaskMarker_enabled)&&(U.capsMaskLine.style.display=Z?"inline":"none",q=!0),Y.capsMaskMarker_enabled&&[0,1].forEach(function(n){var e=Y.capsMaskMarker_plugSE[n],t=e!==ne?ae[ie[e]]:null,a=Ne(n,t);We(U,X.capsMaskMarker_enabledSE,n,Z=Y.capsMaskMarker_enabledSE[n])&&(U.capsMaskLine.style[a.prop]=Z?"url(#"+U.lineMaskMarkerIdSE[n]+")":"none",q=!0),Y.capsMaskMarker_enabledSE[n]&&(We(U,X.capsMaskMarker_plugSE,n,e)&&(U.capsMaskMarkerShapeSE[n].href.baseVal="#"+t.elmId,Pe(U,U.capsMaskMarkerSE[n],a.orient,t.bBox,U.svg,U.capsMaskMarkerShapeSE[n],U.capsMaskLine),q=!0,re&&(Ae(U,U.capsMaskLine),Ae(U,U.lineFace))),["markerWidth","markerHeight"].forEach(function(e){var t="capsMaskMarker_"+e+"SE";We(U,X[t],n,Z=Y[t][n])&&(U.capsMaskMarkerSE[n][e].baseVal.value=Z,q=!0)}))}))),Y.lineOutline_enabled&&["x","y"].forEach(function(e){var t="lineOutlineMask_"+e;We(U,X,t,Z=Y[t])&&(U.lineOutlineMask[e].baseVal.value=Z,q=!0)}),q),t.effect&&(J=(Q=e).curStats,$=Q.aplStats,Object.keys(te).forEach(function(e){var t=te[e],n=e+"_enabled",a=e+"_options",i=J[a];We(Q,$,n,K=J[n])?(K&&($[a]=de(i)),t[K?"init":"remove"](Q)):K&&ce(i,$[a])&&(t.remove(Q),$[n]=!0,$[a]=de(i),t.init(Q))})),(se||ue)&&ee.line&&!ee.path&&Ae(e,e.lineShape),se&&ee.plug&&!ee.line&&Ae(e,e.plugsFace),Ve(e)}function ze(e,t){return{duration:w(e.duration)&&0i.x2&&(i.x2=t.x2),t.y2>i.y2&&(i.y2=t.y2),["x","y"].forEach(function(e){var t,n="dropShadow_"+e;o[n]=t=i[e+"1"],We(a,l,n,t)&&(a.efc_dropShadow_elmFilter[e].baseVal.value=t)}))}}},Object.keys(te).forEach(function(e){var t=te[e],n=t.stats;n[e+"_enabled"]={iniValue:!1},n[e+"_options"]={hasProps:!0},t.anim&&(n[e+"_animOptions"]={},n[e+"_animId"]={})}),g={none:{defaultAnimOptions:{},init:function(e,t){var n=e.curStats;n.show_animId&&(c.remove(n.show_animId),n.show_animId=null),g.none.start(e,t)},start:function(e,t){g.none.stop(e,!0)},stop:function(e,t,n){var a=e.curStats;return n=null!=n?n:e.aplStats.show_on,a.show_inAnim=!1,t&&Ge(e,n),n?1:0}},fade:{defaultAnimOptions:{duration:300,timing:"linear"},init:function(n,e){var t=n.curStats,a=n.aplStats;t.show_animId&&c.remove(t.show_animId),t.show_animId=c.add(function(e){return e},function(e,t){t?g.fade.stop(n,!0):(n.svg.style.opacity=e+"",le&&(Ae(n,n.svg),Ve(n)))},a.show_animOptions.duration,1,a.show_animOptions.timing,null,!1),g.fade.start(n,e)},start:function(e,t){var n,a=e.curStats;a.show_inAnim&&(n=c.stop(a.show_animId)),Ge(e,1),a.show_inAnim=!0,c.start(a.show_animId,!e.aplStats.show_on,null!=t?t:n)},stop:function(e,t,n){var a,i=e.curStats;return n=null!=n?n:e.aplStats.show_on,a=i.show_inAnim?c.stop(i.show_animId):n?1:0,i.show_inAnim=!1,t&&(e.svg.style.opacity=n?"":"0",Ge(e,n)),a}},draw:{defaultAnimOptions:{duration:500,timing:[.58,0,.42,1]},init:function(n,e){var t=n.curStats,a=n.aplStats,l=n.pathList.baseVal,i=Oe(l),r=i.segsLen,s=i.lenAll;t.show_animId&&c.remove(t.show_animId),t.show_animId=c.add(function(e){var t,n,a,i,o=-1;if(0===e)n=[[l[0][0],l[0][0]]];else if(1===e)n=l;else{for(t=s*e,n=[];t>=r[++o];)n.push(l[o]),t-=r[o];t&&(2===(a=l[o]).length?n.push([a[0],ve(a[0],a[1],t/r[o])]):(i=xe(a[0],a[1],a[2],a[3],ke(a[0],a[1],a[2],a[3],t)),n.push([a[0],i.fromP1,i.fromP2,i])))}return n},function(e,t){t?g.draw.stop(n,!0):(n.pathList.animVal=e,De(n,{path:!0}))},a.show_animOptions.duration,1,a.show_animOptions.timing,null,!1),g.draw.start(n,e)},start:function(e,t){var n,a=e.curStats;a.show_inAnim&&(n=c.stop(a.show_animId)),Ge(e,1),a.show_inAnim=!0,Ie(e,"apl_position",g.draw.update),c.start(a.show_animId,!e.aplStats.show_on,null!=t?t:n)},stop:function(e,t,n){var a,i=e.curStats;return n=null!=n?n:e.aplStats.show_on,a=i.show_inAnim?c.stop(i.show_animId):n?1:0,i.show_inAnim=!1,t&&(e.pathList.animVal=n?null:[[e.pathList.baseVal[0][0],e.pathList.baseVal[0][0]]],De(e,{path:!0}),Ge(e,n)),a},update:function(e){Ce(e,"apl_position",g.draw.update),e.curStats.show_inAnim?g.draw.init(e,g.draw.stop(e)):e.aplStats.show_animOptions={}}}},function(){function r(n){return function(e){var t={};t[n]=e,this.setOptions(t)}}[["start","anchorSE",0],["end","anchorSE",1],["color","lineColor"],["size","lineSize"],["startSocketGravity","socketGravitySE",0],["endSocketGravity","socketGravitySE",1],["startPlugColor","plugColorSE",0],["endPlugColor","plugColorSE",1],["startPlugSize","plugSizeSE",0],["endPlugSize","plugSizeSE",1],["outline","lineOutlineEnabled"],["outlineColor","lineOutlineColor"],["outlineSize","lineOutlineSize"],["startPlugOutline","plugOutlineEnabledSE",0],["endPlugOutline","plugOutlineEnabledSE",1],["startPlugOutlineColor","plugOutlineColorSE",0],["endPlugOutlineColor","plugOutlineColorSE",1],["startPlugOutlineSize","plugOutlineSizeSE",0],["endPlugOutlineSize","plugOutlineSizeSE",1]].forEach(function(e){var t=e[0],n=e[1],a=e[2];Object.defineProperty(Ye.prototype,t,{get:function(){var e=null!=a?K[this._id].options[n][a]:n?K[this._id].options[n]:K[this._id].options[t];return null==e?x:de(e)},set:r(t),enumerable:!0})}),[["path",m],["startSocket",n,"socketSE",0],["endSocket",n,"socketSE",1],["startPlug",E,"plugSE",0],["endPlug",E,"plugSE",1]].forEach(function(e){var a=e[0],i=e[1],o=e[2],l=e[3];Object.defineProperty(Ye.prototype,a,{get:function(){var t,n=null!=l?K[this._id].options[o][l]:o?K[this._id].options[o]:K[this._id].options[a];return n?Object.keys(i).some(function(e){return i[e]===n&&(t=e,!0)})?t:new Error("It's broken"):x},set:r(a),enumerable:!0})}),Object.keys(te).forEach(function(n){var a=te[n];Object.defineProperty(Ye.prototype,n,{get:function(){var u,e,t=K[this._id].options[n];return k(t)?(u=t,e=a.optionsConf.reduce(function(e,t){var n,a=t[0],i=t[1],o=t[2],l=t[3],r=t[4],s=null!=r?u[l][r]:l?u[l]:u[i];return e[i]="id"===a?s?Object.keys(o).some(function(e){return o[e]===s&&(n=e,!0)})?n:new Error("It's broken"):x:null==s?x:de(s),e},{}),a.anim&&(e.animation=de(u.animation)),e):t},set:r(n),enumerable:!0})}),["startLabel","endLabel","middleLabel"].forEach(function(e,n){Object.defineProperty(Ye.prototype,e,{get:function(){var e=K[this._id],t=e.options;return t.labelSEM[n]&&!e.optionIsAttach.labelSEM[n]?$[t.labelSEM[n]._id].text:t.labelSEM[n]||""},set:r(e),enumerable:!0})})}(),Ye.prototype.setOptions=function(e){return Ze(K[this._id],e),this},Ye.prototype.position=function(){return De(K[this._id],{position:!0}),this},Ye.prototype.remove=function(){var t=K[this._id],n=t.curStats;Object.keys(te).forEach(function(e){var t=e+"_animId";n[t]&&c.remove(n[t])}),n.show_animId&&c.remove(n.show_animId),t.attachments.slice().forEach(function(e){Ue(t,e)}),t.baseWindow&&t.svg&&t.baseWindow.document.body.removeChild(t.svg),delete K[this._id]},Ye.prototype.show=function(e,t){return je(K[this._id],!0,e,t),this},Ye.prototype.hide=function(e,t){return je(K[this._id],!1,e,t),this},o=function(t){t&&$[t._id]&&(t.boundTargets.slice().forEach(function(e){Ue(e.props,t,!0)}),t.conf.remove&&t.conf.remove(t),delete $[t._id])},S=function(){function e(e,t){var n,a={conf:e,curStats:{},aplStats:{},boundTargets:[]},i={};e.argOptions.every(function(e){return!(!t.length||("string"==typeof e.type?typeof t[0]!==e.type:"function"!=typeof e.type||!e.type(t[0])))&&(i[e.optionName]=t.shift(),!0)}),n=t.length&&k(t[0])?de(t[0]):{},Object.keys(i).forEach(function(e){n[e]=i[e]}),e.stats&&(Te(a.curStats,e.stats),Te(a.aplStats,e.stats)),Object.defineProperty(this,"_id",{value:++ee}),Object.defineProperty(this,"isRemoved",{get:function(){return!$[this._id]}}),a._id=this._id,e.init&&!e.init(a,n)||($[this._id]=a)}return e.prototype.remove=function(){var t=this,n=$[t._id];n&&(n.boundTargets.slice().forEach(function(e){n.conf.removeOption(n,e)}),Le(function(){var e=$[t._id];e&&(console.error("LeaderLineAttachment was not removed by removeOption"),o(e))}))},e}(),window.LeaderLineAttachment=S,_=function(e,t){return e instanceof S&&(!(e.isRemoved||t&&$[e._id].conf.type!==t)||null)},y={pointAnchor:{type:"anchor",argOptions:[{optionName:"element",type:ye}],init:function(e,t){return e.element=y.pointAnchor.checkElement(t.element),e.x=y.pointAnchor.parsePercent(t.x,!0)||[.5,!0],e.y=y.pointAnchor.parsePercent(t.y,!0)||[.5,!0],!0},removeOption:function(e,t){var n=t.props,a={},i=e.element,o=n.options.anchorSE["start"===t.optionName?1:0];i===o&&(i=o===document.body?new S(y.pointAnchor,[i]):document.body),a[t.optionName]=i,Ze(n,a)},getBBoxNest:function(e,t){var n=ge(e.element,t.baseWindow),a=n.width,i=n.height;return n.width=n.height=0,n.left=n.right=n.left+e.x[0]*(e.x[1]?a:1),n.top=n.bottom=n.top+e.y[0]*(e.y[1]?i:1),n},parsePercent:function(e,t){var n,a,i=!1;return w(e)?a=e:"string"==typeof e&&(n=u.exec(e))&&n[2]&&(i=0!==(a=parseFloat(n[1])/100)),null!=a&&(t||0<=a)?[a,i]:null},checkElement:function(e){if(null==e)e=document.body;else if(!ye(e))throw new Error("`element` must be Element");return e}},areaAnchor:{type:"anchor",argOptions:[{optionName:"element",type:ye},{optionName:"shape",type:"string"}],stats:{color:{},strokeWidth:{},elementWidth:{},elementHeight:{},elementLeft:{},elementTop:{},pathListRel:{},bBoxRel:{},pathData:{},viewBoxBBox:{hasProps:!0},dashLen:{},dashGap:{}},init:function(i,e){var t,n,a,o=[];return i.element=y.pointAnchor.checkElement(e.element),"string"==typeof e.color&&(i.color=e.color.trim()),"string"==typeof e.fillColor&&(i.fill=e.fillColor.trim()),w(e.size)&&0<=e.size&&(i.size=e.size),e.dash&&(i.dash=!0,w(e.dash.len)&&0i.right&&(i.right=t),ni.bottom&&(i.bottom=n)):i={left:t,right:t,top:n,bottom:n},o?P.pathListRel.push([o,{x:t,y:n}]):P.pathListRel=[],o={x:t,y:n}}),P.pathListRel.push([]),e=P.strokeWidth/2,l=[{x:i.left-e,y:i.top-e},{x:i.right+e,y:i.bottom+e}],P.bBoxRel={left:l[0].x,top:l[0].y,right:l[1].x,bottom:l[1].y,width:l[1].x-l[0].x,height:l[1].y-l[0].y}}W.pathListRel=W.bBoxRel=!0}return(W.pathListRel||W.elementLeft||W.elementTop)&&(P.pathData=we(P.pathListRel,function(e){e.x+=a.left,e.y+=a.top})),We(t,N,"strokeWidth",n=P.strokeWidth)&&(t.path.style.strokeWidth=n+"px"),Me(n=P.pathData,N.pathData)&&(t.path.setPathData(n),N.pathData=n,W.pathData=!0),t.dash&&(!W.pathData&&(!W.strokeWidth||t.dashLen&&t.dashGap)||(P.dashLen=t.dashLen||2*P.strokeWidth,P.dashGap=t.dashGap||P.strokeWidth),W.dash=We(t,N,"dashLen",P.dashLen)||W.dash,W.dash=We(t,N,"dashGap",P.dashGap)||W.dash,W.dash&&(t.path.style.strokeDasharray=N.dashLen+","+N.dashGap)),C=P.viewBoxBBox,L=N.viewBoxBBox,A=t.svg.viewBox.baseVal,V=t.svg.style,C.x=P.bBoxRel.left+a.left,C.y=P.bBoxRel.top+a.top,C.width=P.bBoxRel.width,C.height=P.bBoxRel.height,["x","y","width","height"].forEach(function(e){(n=C[e])!==L[e]&&(A[e]=L[e]=n,V[oe[e]]=n+("x"===e||"y"===e?t.bodyOffset[e]:0)+"px")}),W.strokeWidth||W.pathListRel||W.bBoxRel}},mouseHoverAnchor:{type:"anchor",argOptions:[{optionName:"element",type:ye},{optionName:"showEffectName",type:"string"}],style:{backgroundImage:"url('data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cG9seWdvbiBwb2ludHM9IjI0LDAgMCw4IDgsMTEgMCwxOSA1LDI0IDEzLDE2IDE2LDI0IiBmaWxsPSJjb3JhbCIvPjwvc3ZnPg==')",backgroundSize:"",backgroundRepeat:"no-repeat",backgroundColor:"#f8f881",cursor:"default"},hoverStyle:{backgroundImage:"none",backgroundColor:"#fadf8f"},padding:{top:1,right:15,bottom:1,left:2},minHeight:15,backgroundPosition:{right:2,top:2},backgroundSize:{width:12,height:12},dirKeys:[["top","Top"],["right","Right"],["bottom","Bottom"],["left","Left"]],init:function(a,i){var o,t,e,n,l,r,s,u,h,p,c,d=y.mouseHoverAnchor,f={};if(a.element=y.pointAnchor.checkElement(i.element),u=a.element,!((p=u.ownerDocument)&&(h=p.defaultView)&&h.HTMLElement&&u instanceof h.HTMLElement))throw new Error("`element` must be HTML element");return d.style.backgroundSize=d.backgroundSize.width+"px "+d.backgroundSize.height+"px",["style","hoverStyle"].forEach(function(e){var n=d[e];a[e]=Object.keys(n).reduce(function(e,t){return e[t]=n[t],e},{})}),"inline"===(o=a.element.ownerDocument.defaultView.getComputedStyle(a.element,"")).display?a.style.display="inline-block":"none"===o.display&&(a.style.display="block"),y.mouseHoverAnchor.dirKeys.forEach(function(e){var t=e[0],n="padding"+e[1];parseFloat(o[n])e.x2&&(e.x2=a.x2),a.y2>e.y2&&(e.y2=a.y2)},newText:function(e,t,n,a,i){var o,l,r,s,u,h;return(o=t.createElementNS(b,"text")).textContent=e,[o.x,o.y].forEach(function(e){var t=n.createSVGLength();t.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX,0),e.baseVal.initialize(t)}),"boolean"!=typeof f&&(f="paintOrder"in o.style),i&&!f?(r=t.createElementNS(b,"defs"),o.id=a,r.appendChild(o),(u=(l=t.createElementNS(b,"g")).appendChild(t.createElementNS(b,"use"))).href.baseVal="#"+a,(s=l.appendChild(t.createElementNS(b,"use"))).href.baseVal="#"+a,(h=u.style).strokeLinejoin="round",{elmPosition:o,styleText:o.style,styleFill:s.style,styleStroke:h,styleShow:l.style,elmsAppend:[r,l]}):(h=o.style,i&&(h.strokeLinejoin="round",h.paintOrder="stroke"),{elmPosition:o,styleText:h,styleFill:h,styleStroke:i?h:null,styleShow:h,elmsAppend:[o]})},getMidPoint:function(e,t){var n,a,i,o=Oe(e),l=o.segsLen,r=o.lenAll,s=-1;if((n=r/2+(t||0))<=0)return 2===(a=e[0]).length?ve(a[0],a[1],0):xe(a[0],a[1],a[2],a[3],0);if(r<=n)return 2===(a=e[e.length-1]).length?ve(a[0],a[1],1):xe(a[0],a[1],a[2],a[3],1);for(i=[];n>l[++s];)i.push(e[s]),n-=l[s];return 2===(a=e[s]).length?ve(a[0],a[1],n/l[s]):xe(a[0],a[1],a[2],a[3],ke(a[0],a[1],a[2],a[3],n))},initSvg:function(t,n){var e,a,i=y.captionLabel.newText(t.text,n.baseWindow.document,n.svg,v+"-captionLabel-"+t._id,t.outlineColor);["elmPosition","styleFill","styleShow","elmsAppend"].forEach(function(e){t[e]=i[e]}),t.isShown=!1,t.styleShow.visibility="hidden",y.captionLabel.textStyleProps.forEach(function(e){null!=t[e]&&(i.styleText[e]=t[e])}),i.elmsAppend.forEach(function(e){n.svg.appendChild(e)}),e=i.elmPosition.getBBox(),t.width=e.width,t.height=e.height,t.outlineColor&&(a=10<(a=e.height/9)?10:a<2?2:a,i.styleStroke.strokeWidth=a+"px",i.styleStroke.stroke=t.outlineColor),t.strokeWidth=a||0,Te(t.aplStats,y.captionLabel.stats),t.updateColor(n),t.refSocketXY?t.updateSocketXY(n):t.updatePath(n),ue&&De(n,{}),t.updateShow(n)},bind:function(e,t){var n=t.props;return e.color||Ie(n,"cur_line_color",e.updateColor),(e.refSocketXY="startLabel"===t.optionName||"endLabel"===t.optionName)?(e.socketIndex="startLabel"===t.optionName?0:1,Ie(n,"apl_position",e.updateSocketXY),e.offset||(Ie(n,"cur_attach_plugSideLenSE",e.updateSocketXY),Ie(n,"cur_line_strokeWidth",e.updateSocketXY))):Ie(n,"apl_path",e.updatePath),Ie(n,"svgShow",e.updateShow),ue&&Ie(n,"new_edge4viewBox",e.adjustEdge),y.captionLabel.initSvg(e,n),!0},unbind:function(e,t){var n=t.props;e.elmsAppend&&(e.elmsAppend.forEach(function(e){n.svg.removeChild(e)}),e.elmPosition=e.styleFill=e.styleShow=e.elmsAppend=null),Te(e.curStats,y.captionLabel.stats),Te(e.aplStats,y.captionLabel.stats),e.color||Ce(n,"cur_line_color",e.updateColor),e.refSocketXY?(Ce(n,"apl_position",e.updateSocketXY),e.offset||(Ce(n,"cur_attach_plugSideLenSE",e.updateSocketXY),Ce(n,"cur_line_strokeWidth",e.updateSocketXY))):Ce(n,"apl_path",e.updatePath),Ce(n,"svgShow",e.updateShow),ue&&(Ce(n,"new_edge4viewBox",e.adjustEdge),De(n,{}))},removeOption:function(e,t){var n=t.props,a={};a[t.optionName]="",Ze(n,a)},remove:function(t){t.boundTargets.length&&(console.error("LeaderLineAttachment was not unbound by remove"),t.boundTargets.forEach(function(e){y.captionLabel.unbind(t,e)}))}},pathLabel:{type:"label",argOptions:[{optionName:"text",type:"string"}],stats:{color:{},startOffset:{},pathData:{}},init:function(s,t){return"string"==typeof t.text&&(s.text=t.text.trim()),!!s.text&&("string"==typeof t.color&&(s.color=t.color.trim()),s.outlineColor="string"==typeof t.outlineColor?t.outlineColor.trim():"#fff",w(t.lineOffset)&&(s.lineOffset=t.lineOffset),y.captionLabel.textStyleProps.forEach(function(e){null!=t[e]&&(s[e]=t[e])}),s.updateColor=function(e){y.captionLabel.updateColor(s,e)},s.updatePath=function(e){var t,n=s.curStats,a=s.aplStats,i=e.curStats,o=e.pathList.animVal||e.pathList.baseVal;o&&(n.pathData=t=y.pathLabel.getOffsetPathData(o,i.line_strokeWidth/2+s.strokeWidth/2+s.height/4,1.25*s.height),Me(t,a.pathData)&&(s.elmPath.setPathData(t),a.pathData=t,s.bBox=s.elmPosition.getBBox(),s.updateStartOffset(e)))},s.updateStartOffset=function(e){var t,n,a,i,o=s.curStats,l=s.aplStats,r=e.curStats;o.pathData&&((2!==s.semIndex||s.lineOffset)&&(t=o.pathData.reduce(function(e,t){var n,a=t.values;switch(t.type){case"M":i={x:a[0],y:a[1]};break;case"L":n={x:a[0],y:a[1]},i&&(e+=_e(i,n)),i=n;break;case"C":n={x:a[4],y:a[5]},i&&(e+=be(i,{x:a[0],y:a[1]},{x:a[2],y:a[3]},n)),i=n}return e},0),a=0===s.semIndex?0:1===s.semIndex?t:t/2,2!==s.semIndex&&(n=Math.max(r.attach_plugBackLenSE[s.semIndex]||0,r.line_strokeWidth/2)+s.strokeWidth/2+s.height/4,a=(a+=0===s.semIndex?n:-n)<0?0:tx?((t=b.points)[1]=Ee(t[0],t[1],-x),b.len=_e(t[0],t[1])):(b.points=null,b.len=0),e.len>x+n?((t=e.points)[0]=Ee(t[1],t[0],-(x+n)),e.len=_e(t[0],t[1])):(e.points=null,e.len=0)),b=e):b=null}),k.reduce(function(t,e){var n=e.points;return n&&(a&&w(n[0],a)||t.push({type:"M",values:[n[0].x,n[0].y]}),"line"===e.type?t.push({type:"L",values:[n[1].x,n[1].y]}):(n.shift(),n.forEach(function(e){t.push({type:"L",values:[e.x,e.y]})})),a=n[n.length-1]),t},[])},newText:function(e,t,n,a){var i,o,l,r,s,u,h,p,c,d;return(r=(l=t.createElementNS(b,"defs")).appendChild(t.createElementNS(b,"path"))).id=i=n+"-path",(u=(s=t.createElementNS(b,"text")).appendChild(t.createElementNS(b,"textPath"))).href.baseVal="#"+i,u.startOffset.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX,0),u.textContent=e,"boolean"!=typeof f&&(f="paintOrder"in s.style),a&&!f?(s.id=o=n+"-text",l.appendChild(s),(c=(h=t.createElementNS(b,"g")).appendChild(t.createElementNS(b,"use"))).href.baseVal="#"+o,(p=h.appendChild(t.createElementNS(b,"use"))).href.baseVal="#"+o,(d=c.style).strokeLinejoin="round",{elmPosition:s,elmPath:r,elmOffset:u,styleText:s.style,styleFill:p.style,styleStroke:d,styleShow:h.style,elmsAppend:[l,h]}):(d=s.style,a&&(d.strokeLinejoin="round",d.paintOrder="stroke"),{elmPosition:s,elmPath:r,elmOffset:u,styleText:d,styleFill:d,styleStroke:a?d:null,styleShow:d,elmsAppend:[l,s]})},initSvg:function(t,n){var e,a,i=y.pathLabel.newText(t.text,n.baseWindow.document,v+"-pathLabel-"+t._id,t.outlineColor);["elmPosition","elmPath","elmOffset","styleFill","styleShow","elmsAppend"].forEach(function(e){t[e]=i[e]}),t.isShown=!1,t.styleShow.visibility="hidden",y.captionLabel.textStyleProps.forEach(function(e){null!=t[e]&&(i.styleText[e]=t[e])}),i.elmsAppend.forEach(function(e){n.svg.appendChild(e)}),i.elmPath.setPathData([{type:"M",values:[0,100]},{type:"h",values:[100]}]),e=i.elmPosition.getBBox(),i.styleText.textAnchor=["start","end","middle"][t.semIndex],2!==t.semIndex||t.lineOffset||i.elmOffset.startOffset.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PERCENTAGE,50),t.height=e.height,t.outlineColor&&(a=10<(a=e.height/9)?10:a<2?2:a,i.styleStroke.strokeWidth=a+"px",i.styleStroke.stroke=t.outlineColor),t.strokeWidth=a||0,Te(t.aplStats,y.pathLabel.stats),t.updateColor(n),t.updatePath(n),t.updateStartOffset(n),ue&&De(n,{}),t.updateShow(n)},bind:function(e,t){var n=t.props;return e.color||Ie(n,"cur_line_color",e.updateColor),Ie(n,"cur_line_strokeWidth",e.updatePath),Ie(n,"apl_path",e.updatePath),e.semIndex="startLabel"===t.optionName?0:"endLabel"===t.optionName?1:2,(2!==e.semIndex||e.lineOffset)&&Ie(n,"cur_attach_plugBackLenSE",e.updateStartOffset),Ie(n,"svgShow",e.updateShow),ue&&Ie(n,"new_edge4viewBox",e.adjustEdge),y.pathLabel.initSvg(e,n),!0},unbind:function(e,t){var n=t.props;e.elmsAppend&&(e.elmsAppend.forEach(function(e){n.svg.removeChild(e)}),e.elmPosition=e.elmPath=e.elmOffset=e.styleFill=e.styleShow=e.elmsAppend=null),Te(e.curStats,y.pathLabel.stats),Te(e.aplStats,y.pathLabel.stats),e.color||Ce(n,"cur_line_color",e.updateColor),Ce(n,"cur_line_strokeWidth",e.updatePath),Ce(n,"apl_path",e.updatePath),(2!==e.semIndex||e.lineOffset)&&Ce(n,"cur_attach_plugBackLenSE",e.updateStartOffset),Ce(n,"svgShow",e.updateShow),ue&&(Ce(n,"new_edge4viewBox",e.adjustEdge),De(n,{}))},removeOption:function(e,t){var n=t.props,a={};a[t.optionName]="",Ze(n,a)},remove:function(t){t.boundTargets.length&&(console.error("LeaderLineAttachment was not unbound by remove"),t.boundTargets.forEach(function(e){y.pathLabel.unbind(t,e)}))}}},Object.keys(y).forEach(function(e){Ye[e]=function(){return new S(y[e],Array.prototype.slice.call(arguments))}}),Ye.positionByWindowResize=!0,window.addEventListener("resize",O.add(function(){Ye.positionByWindowResize&&Object.keys(K).forEach(function(e){De(K[e],{position:!0})})}),!1),Ye}(); \ No newline at end of file diff --git a/examples/program_analysis/ui/plain-draggable.min.js b/examples/program_analysis/ui/plain-draggable.min.js new file mode 100644 index 0000000..8615cf0 --- /dev/null +++ b/examples/program_analysis/ui/plain-draggable.min.js @@ -0,0 +1,2 @@ +/*! PlainDraggable v2.5.12 (c) anseki https://anseki.github.io/plain-draggable/ */ +var PlainDraggable=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";n.r(e);var r=500,o=[],i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return setTimeout(t,1e3/60)},a=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return clearTimeout(t)},l=Date.now(),u=void 0;function s(){var t=void 0,e=void 0;u&&(a.call(window,u),u=null),o.forEach(function(e){var n;(n=e.event)&&(e.event=null,e.listener(n),t=!0)}),t?(l=Date.now(),e=!0):Date.now()-l-1&&(o.splice(e,1),!o.length&&u&&(a.call(window,u),u=null))}},f=function(){function t(t,e){for(var n=0;nn.min:in.max&&(a=n.max),Ot(Ct,e,a),n.lastValue=a}n.lastFrameTime=t}})},Tt=function t(){St.call(window,Ht),Et(),Ht=bt.call(window,t)},Bt={},Ct=void 0,Ot=void 0,Ht=void 0;function kt(t,e,n){return null!=n&&("x"===e?t.scrollTo(n,t.pageYOffset):t.scrollTo(t.pageXOffset,n)),"x"===e?t.pageXOffset:t.pageYOffset}function Pt(t,e,n){var r="x"===e?"scrollLeft":"scrollTop";return null!=n&&(t[r]=n),t[r]}function It(t){return t?Q(t)?Object.keys(t).reduce(function(e,n){return e[n]=It(t[n]),e},{}):Array.isArray(t)?t.map(It):t:t}function _t(t,e){var n=void 0,r=void 0;return(void 0===t?"undefined":L(t))!==(void 0===e?"undefined":L(e))||(n=Q(t)?"obj":Array.isArray(t)?"array":"")!=(Q(e)?"obj":Array.isArray(e)?"array":"")||("obj"===n?_t(r=Object.keys(t).sort(),Object.keys(e).sort())||r.some(function(n){return _t(t[n],e[n])}):"array"===n?t.length!==e.length||t.some(function(t,n){return _t(t,e[n])}):t!==e)}function Dt(t){return!(!t||t.nodeType!==Node.ELEMENT_NODE||"function"!=typeof t.getBoundingClientRect||t.compareDocumentPosition(document)&Node.DOCUMENT_POSITION_DISCONNECTED)}function Xt(t){if(!Q(t))return null;var e=void 0;if(!tt(e=t.left)&&!tt(e=t.x))return null;if(t.left=t.x=e,!tt(e=t.top)&&!tt(e=t.y))return null;if(t.top=t.y=e,tt(t.width)&&t.width>=0)t.right=t.left+t.width;else{if(!(tt(t.right)&&t.right>=t.left))return null;t.width=t.right-t.left}if(tt(t.height)&&t.height>=0)t.bottom=t.top+t.height;else{if(!(tt(t.bottom)&&t.bottom>=t.top))return null;t.height=t.bottom-t.top}return t}function Yt(t){return tt(t)?{value:t,isRatio:!1}:"string"==typeof t?function(t){var e=/^(.+?)(%)?$/.exec(t),n=void 0,r=void 0;return e&&tt(n=parseFloat(e[1]))?{value:(r=!(!e[2]||!n))?n/100:n,isRatio:r}:null}(t.replace(/\s/g,"")):null}function Lt(t){return t.isRatio?100*t.value+"%":t.value}function At(t,e,n){return"number"==typeof t?t:e+t.value*(t.isRatio?n:1)}function Ft(t){if(!Q(t))return null;var e=void 0;if(!(e=Yt(t.left))&&!(e=Yt(t.x)))return null;if(t.left=t.x=e,!(e=Yt(t.top))&&!(e=Yt(t.y)))return null;if(t.top=t.y=e,(e=Yt(t.width))&&e.value>=0)t.width=e,delete t.right;else{if(!(e=Yt(t.right)))return null;t.right=e,delete t.width}if((e=Yt(t.height))&&e.value>=0)t.height=e,delete t.bottom;else{if(!(e=Yt(t.bottom)))return null;t.bottom=e,delete t.height}return t}function Wt(t){return Object.keys(t).reduce(function(e,n){return e[n]=Lt(t[n]),e},{})}function jt(t,e){var n={left:"x",right:"x",x:"x",width:"x",top:"y",bottom:"y",y:"y",height:"y"},r={x:e.left,y:e.top},o={x:e.width,y:e.height};return Xt(Object.keys(t).reduce(function(e,i){return e[i]=At(t[i],"width"===i||"height"===i?0:r[n[i]],o[n[i]]),e},{}))}function Rt(t,e){var n=t.getBoundingClientRect(),r={left:n.left,top:n.top,width:n.width,height:n.height};if(r.left+=window.pageXOffset,r.top+=window.pageYOffset,e){var o=window.getComputedStyle(t,""),i=parseFloat(o.borderTopWidth)||0,a=parseFloat(o.borderRightWidth)||0,l=parseFloat(o.borderBottomWidth)||0,u=parseFloat(o.borderLeftWidth)||0;r.left+=u,r.top+=i,r.width-=u+a,r.height-=i+l}return Xt(r)}function Mt(t,e){null==ut&&(!1!==ht&&(ut=P.getValue("cursor",ht)),null==ut&&(ut=!1)),t.style.cursor=!1===ut?e:ut}function zt(t){null==st&&(!1!==mt&&(st=P.getValue("cursor",mt)),null==st&&(st=!1)),!1!==st&&(t.style.cursor=st)}function Nt(t,e,n){var r=t.svgPoint;return r.x=e,r.y=n,r.matrixTransform(t.svgCtmElement.getScreenCTM().inverse())}function Vt(t,e){var n=t.elementBBox;if(e.left!==n.left||e.top!==n.top){var r=t.htmlOffset;return t.elementStyle[ft]="translate("+(e.left+r.left)+"px, "+(e.top+r.top)+"px)",!0}return!1}function Gt(t,e){var n=t.elementBBox,r=t.elementStyle,o=t.htmlOffset,i=!1;return e.left!==n.left&&(r.left=e.left+o.left+"px",i=!0),e.top!==n.top&&(r.top=e.top+o.top+"px",i=!0),i}function qt(t,e){var n=t.elementBBox;if(e.left!==n.left||e.top!==n.top){var r=t.svgOffset,o=t.svgOriginBBox,i=Nt(t,e.left-window.pageXOffset,e.top-window.pageYOffset);return t.svgTransform.setTranslate(i.x+r.x-o.x,i.y+r.y-o.y),!0}return!1}function Ut(t,e,n){var r=t.elementBBox;function o(){t.minLeft>=t.maxLeft?e.left=r.left:e.leftt.maxLeft&&(e.left=t.maxLeft),t.minTop>=t.maxTop?e.top=r.top:e.topt.maxTop&&(e.top=t.maxTop)}if(o(),n){if(!1===n(e))return!1;o()}var i=t.moveElm(t,e);return i&&(t.elementBBox=Xt({left:e.left,top:e.top,width:r.width,height:r.height})),i}function Zt(t){var e=t.element,n=t.elementStyle,r=Rt(e),o=["display","marginTop","marginBottom","width","height"];o.unshift(ft);var i=n[ct];n[ct]="none";var a=Rt(e);t.orgStyle?o.forEach(function(e){null!=t.lastStyle[e]&&n[e]!==t.lastStyle[e]||(n[e]=t.orgStyle[e])}):(t.orgStyle=o.reduce(function(t,e){return t[e]=n[e]||"",t},{}),t.lastStyle={});var l=Rt(e),u=window.getComputedStyle(e,"");"inline"===u.display&&(n.display="inline-block",["Top","Bottom"].forEach(function(t){var e=parseFloat(u["padding"+t]);n["margin"+t]=e?"-"+e+"px":"0"})),n[ft]="translate(0, 0)";var s=Rt(e),d=t.htmlOffset={left:s.left?-s.left:0,top:s.top?-s.top:0};return n[ft]="translate("+(r.left+d.left)+"px, "+(r.top+d.top)+"px)",["width","height"].forEach(function(r){s[r]!==l[r]&&(n[r]=l[r]+"px",(s=Rt(e))[r]!==l[r]&&(n[r]=l[r]-(s[r]-l[r])+"px")),t.lastStyle[r]=n[r]}),e.offsetWidth,n[ct]=i,a.left===r.left&&a.top===r.top||(n[ft]="translate("+(a.left+d.left)+"px, "+(a.top+d.top)+"px)"),a}function $t(t){var e=t.element,n=t.elementStyle,r=Rt(e),o=["position","marginTop","marginRight","marginBottom","marginLeft","width","height"],i=n[ct];n[ct]="none";var a=Rt(e);t.orgStyle?o.forEach(function(e){null!=t.lastStyle[e]&&n[e]!==t.lastStyle[e]||(n[e]=t.orgStyle[e])}):(t.orgStyle=o.reduce(function(t,e){return t[e]=n[e]||"",t},{}),t.lastStyle={});var l=Rt(e);n.position="absolute",n.left=n.top=n.margin="0";var u=Rt(e),s=t.htmlOffset={left:u.left?-u.left:0,top:u.top?-u.top:0};return n.left=r.left+s.left+"px",n.top=r.top+s.top+"px",["width","height"].forEach(function(r){u[r]!==l[r]&&(n[r]=l[r]+"px",(u=Rt(e))[r]!==l[r]&&(n[r]=l[r]-(u[r]-l[r])+"px")),t.lastStyle[r]=n[r]}),e.offsetWidth,n[ct]=i,a.left===r.left&&a.top===r.top||(n.left=a.left+s.left+"px",n.top=a.top+s.top+"px"),a}function Jt(t){var e=t.element,n=t.svgTransform,r=e.getBoundingClientRect(),o=Rt(e);n.setTranslate(0,0);var i=t.svgOriginBBox=e.getBBox(),a=e.getBoundingClientRect(),l=Nt(t,a.left,a.top),u=t.svgOffset={x:i.x-l.x,y:i.y-l.y},s=Nt(t,r.left,r.top);return n.setTranslate(s.x+u.x-i.x,s.y+u.y-i.y),o}function Kt(t,e){var n=Rt(document.documentElement),r=t.elementBBox=t.initElm(t),o=t.containmentBBox=t.containmentIsBBox?jt(t.options.containment,n)||n:Rt(t.options.containment,!0);if(t.minLeft=o.left,t.maxLeft=o.right-r.width,t.minTop=o.top,t.maxTop=o.bottom-r.height,Ut(t,{left:r.left,top:r.top}),t.parsedSnapTargets){var i={x:r.width,y:r.height},a={x:t.minLeft,y:t.minTop},l={x:t.maxLeft,y:t.maxTop},u={left:"x",right:"x",x:"x",width:"x",xStart:"x",xEnd:"x",xStep:"x",top:"y",bottom:"y",y:"y",height:"y",yStart:"y",yEnd:"y",yStep:"y"},s=t.parsedSnapTargets.reduce(function(t,e){var s="containment"===e.base?o:n,d={x:s.left,y:s.top},c={x:s.width,y:s.height};function f(n){if(null==n.center&&(n.center=e.center),null==n.xGravity&&(n.xGravity=e.gravity),null==n.yGravity&&(n.yGravity=e.gravity),null!=n.x&&null!=n.y)n.x=At(n.x,d.x,c.x),n.y=At(n.y,d.y,c.y),n.center&&(n.x-=i.x/2,n.y-=i.y/2,n.corners=["tl"]),(n.corners||e.corners).forEach(function(e){var r=n.x-("tr"===e||"br"===e?i.x:0),o=n.y-("bl"===e||"br"===e?i.y:0);if(r>=a.x&&r<=l.x&&o>=a.y&&o<=l.y){var u={x:r,y:o},s=r-n.xGravity,d=r+n.xGravity,c=o-n.yGravity,f=o+n.yGravity;s>a.x&&(u.gravityXStart=s),da.y&&(u.gravityYStart=c),fn[s]||n[u]>l[o]||n[s]=a[r]&&d<=l[r]){var c={},p=d-n[f],v=d+n[f];c[r]=d,p>a[r]&&(c[h]=p),va[o]&&(c[g]=n[u]),n[s]=s)return a;if(null!=d){if(d<2)return a;var c=d/2;c=e.gravity>c?c:null;for(var f=u;f<=s;f+=d){var p=Object.keys(l).reduce(function(t,e){return e!==n&&e!==r&&e!==o&&(t[e]=l[e]),t},{});p[t]=f,p[i]=c,a.push(p)}}else a.push(l);return a},[])}),v.forEach(function(t){f(t)})}return t},[]);t.snapTargets=s.length?s:null}var d={},c=t.options.autoScroll;if(c){d.isWindow=c.target===window,d.target=c.target;var f="scroll"===e,p=function(t,e,n){var r={},o=void 0,i=void 0,a=void 0;!function(t){r.clientWidth=t.clientWidth,r.clientHeight=t.clientHeight}(e?document.documentElement:t);var l=0,u=0;if(!n){var s=void 0,d=void 0;e?(s=kt(t,"x"),d=kt(t,"y"),o=getComputedStyle(document.documentElement,""),i=getComputedStyle(document.body,""),l=kt(t,"x",document.documentElement.scrollWidth+r.clientWidth+["marginLeft","marginRight","borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"].reduce(function(t,e){return t+(parseFloat(o[e])||0)+(parseFloat(i[e])||0)},0)),u=kt(t,"y",document.documentElement.scrollHeight+r.clientHeight+["marginTop","marginBottom","borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"].reduce(function(t,e){return t+(parseFloat(o[e])||0)+(parseFloat(i[e])||0)},0)),kt(t,"x",s),kt(t,"y",d)):(s=Pt(t,"x"),d=Pt(t,"y"),a=getComputedStyle(t,""),l=Pt(t,"x",t.scrollWidth+r.clientWidth+["marginLeft","marginRight","borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"].reduce(function(t,e){return t+(parseFloat(a[e])||0)},0)),u=Pt(t,"y",t.scrollHeight+r.clientHeight+["marginTop","marginBottom","borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"].reduce(function(t,e){return t+(parseFloat(a[e])||0)},0)),Pt(t,"x",s),Pt(t,"y",d))}r.scrollWidth=r.clientWidth+l,r.scrollHeight=r.clientHeight+u;var c=void 0;return e?r.clientX=r.clientY=0:(c=t.getBoundingClientRect(),a||(a=getComputedStyle(t,"")),r.clientX=c.left+(parseFloat(a.borderLeftWidth)||0),r.clientY=c.top+(parseFloat(a.borderTopWidth)||0)),r}(c.target,d.isWindow,f),v=Xt({left:p.clientX,top:p.clientY,width:p.clientWidth,height:p.clientHeight});f?t.autoScroll&&(d.scrollWidth=t.autoScroll.scrollWidth,d.scrollHeight=t.autoScroll.scrollHeight):(d.scrollWidth=p.scrollWidth,d.scrollHeight=p.scrollHeight),[["X","Width","left","right"],["Y","Height","top","bottom"]].forEach(function(t){var e=t[0],n=t[1],o=t[2],i=t[3],a=(d["scroll"+n]||0)-p["client"+n],l=c["min"+e]||0,u=tt(c["max"+e])?c["max"+e]:a;if(la&&(u=a);for(var s=[],f=r[n.toLowerCase()],h=c.sensitivity.length-1;h>=0;h--){var m=c.sensitivity[h],g=c.speed[h];s.push({dir:-1,speed:g,position:v[o]+m}),s.push({dir:1,speed:g,position:v[i]-m-f})}d[e.toLowerCase()]={min:l,max:u,lines:s}}})}t.autoScroll=d.x||d.y?d:null}function Qt(t){wt.stop(),Mt(t.options.handle,t.orgCursor),lt.style.cursor=dt,!1!==t.options.zIndex&&(t.elementStyle.zIndex=t.orgZIndex),pt&&(lt.style[pt]=vt);var e=X(t.element);xt&&e.remove(xt),yt&&e.remove(yt),it=null,rt.cancel(),t.onDragEnd&&t.onDragEnd({left:t.elementBBox.left,top:t.elementBBox.top})}function te(t,e){var n=t.options,r=void 0;if(e.containment){var o=void 0;Dt(e.containment)?e.containment!==n.containment&&(n.containment=e.containment,t.containmentIsBBox=!1,r=!0):(o=Ft(It(e.containment)))&&_t(o,n.containment)&&(n.containment=o,t.containmentIsBBox=!0,r=!0)}function i(t,e){function n(t){return"string"==typeof t?t.replace(/[, ]+/g," ").trim().toLowerCase():null}tt(e.gravity)&&e.gravity>0&&(t.gravity=e.gravity);var r=n(e.corner);if(r){if("all"!==r){var o={},i=r.split(/\s/).reduce(function(t,e){return(e="tl"===(e=e.trim().replace(/^(.).*?-(.).*$/,"$1$2"))||"lt"===e?"tl":"tr"===e||"rt"===e?"tr":"bl"===e||"lb"===e?"bl":"br"===e||"rb"===e?"br":null)&&!o[e]&&(t.push(e),o[e]=!0),t},[]),a=i.length;r=a?4===a?"all":i.join(" "):null}r&&(t.corner=r)}var l=n(e.side);l&&("start"===l||"end"===l||"both"===l?t.side=l:"start end"!==l&&"end start"!==l||(t.side="both")),"boolean"==typeof e.center&&(t.center=e.center);var u=n(e.edge);u&&("inside"===u||"outside"===u||"both"===u?t.edge=u:"inside outside"!==u&&"outside inside"!==u||(t.edge="both"));var s="string"==typeof e.base?e.base.trim().toLowerCase():null;return!s||"containment"!==s&&"document"!==s||(t.base=s),t}if(null!=e.snap){var a=Q(e.snap)&&null!=e.snap.targets?e.snap:{targets:e.snap},l=[],u=i({targets:l},a);u.gravity||(u.gravity=F),u.corner||(u.corner=W),u.side||(u.side=j),"boolean"!=typeof u.center&&(u.center=!1),u.edge||(u.edge=R),u.base||(u.base=M);var s=(Array.isArray(a.targets)?a.targets:[a.targets]).reduce(function(t,e){if(null==e)return t;var n=Dt(e),r=Ft(It(e)),o=n||r?{boundingBox:e}:Q(e)&&null==e.start&&null==e.end&&null==e.step?e:{x:e,y:e},a=[],s={},d=o.boundingBox,c=void 0;if(n||Dt(d))a.push({element:d}),s.boundingBox=d;else if(c=r||Ft(It(d)))a.push({ppBBox:c}),s.boundingBox=Wt(c);else{var f=void 0,p=["x","y"].reduce(function(t,e){var n,r=o[e];if(n=Yt(r))t[e]=n,s[e]=Lt(n);else{var i=void 0,a=void 0,l=void 0;Q(r)&&(i=Yt(r.start),a=Yt(r.end),l=Yt(r.step),i&&a&&i.isRatio===a.isRatio&&i.value>=a.value&&(f=!0)),i=t[e+"Start"]=i||{value:0,isRatio:!1},a=t[e+"End"]=a||{value:1,isRatio:!0},s[e]={start:Lt(i),end:Lt(a)},l&&((l.isRatio?l.value>0:l.value>=2)?(t[e+"Step"]=l,s[e].step=Lt(l)):f=!0)}return t},{});if(f)return t;p.xStart&&!p.xStep&&p.yStart&&!p.yStep?a.push({xStart:p.xStart,xEnd:p.xEnd,y:p.yStart},{xStart:p.xStart,xEnd:p.xEnd,y:p.yEnd},{x:p.xStart,yStart:p.yStart,yEnd:p.yEnd},{x:p.xEnd,yStart:p.yStart,yEnd:p.yEnd}):a.push(p)}if(a.length){l.push(i(s,o));var v=s.corner||u.corner,h=s.side||u.side,m=s.edge||u.edge,g={gravity:s.gravity||u.gravity,base:s.base||u.base,center:"boolean"==typeof s.center?s.center:u.center,corners:"all"===v?z:v.split(" "),sides:"both"===h?N:[h],edges:"both"===m?V:[m]};a.forEach(function(e){["gravity","corners","sides","center","edges","base"].forEach(function(t){e[t]=g[t]}),t.push(e)})}return t},[]);s.length&&(n.snap=u,_t(s,t.parsedSnapTargets)&&(t.parsedSnapTargets=s,r=!0))}else e.hasOwnProperty("snap")&&t.parsedSnapTargets&&(n.snap=t.parsedSnapTargets=t.snapTargets=void 0);if(e.autoScroll){var d=Q(e.autoScroll)?e.autoScroll:{target:!0===e.autoScroll?window:e.autoScroll},c={};c.target=Dt(d.target)?d.target:window,c.speed=[],(Array.isArray(d.speed)?d.speed:[d.speed]).every(function(t,e){return!!(e<=2&&tt(t))&&(c.speed[e]=t,!0)}),c.speed.length||(c.speed=G);var f=Array.isArray(d.sensitivity)?d.sensitivity:[d.sensitivity];c.sensitivity=c.speed.map(function(t,e){return tt(f[e])?f[e]:q[e]}),["X","Y"].forEach(function(t){var e="min"+t,n="max"+t;tt(d[e])&&d[e]>=0&&(c[e]=d[e]),tt(d[n])&&d[n]>=0&&(!c[e]||d[n]>=c[e])&&(c[n]=d[n])}),_t(c,n.autoScroll)&&(n.autoScroll=c,r=!0)}else e.hasOwnProperty("autoScroll")&&(n.autoScroll&&(r=!0),n.autoScroll=void 0);if(r&&Kt(t),Dt(e.handle)&&e.handle!==n.handle){n.handle&&(n.handle.style.cursor=t.orgCursor,pt&&(n.handle.style[pt]=t.orgUserSelect),rt.removeStartHandler(n.handle,t.pointerEventHandlerId));var p=n.handle=e.handle;t.orgCursor=p.style.cursor,Mt(p,t.orgCursor),pt&&(t.orgUserSelect=p.style[pt],p.style[pt]="none"),rt.addStartHandler(p,t.pointerEventHandlerId)}(tt(e.zIndex)||!1===e.zIndex)&&(n.zIndex=e.zIndex,t===it&&(t.elementStyle.zIndex=!1===n.zIndex?t.orgZIndex:n.zIndex));var v={left:t.elementBBox.left,top:t.elementBBox.top},h=void 0;tt(e.left)&&e.left!==v.left&&(v.left=e.left,h=!0),tt(e.top)&&e.top!==v.top&&(v.top=e.top,h=!0),h&&Ut(t,v),["onDrag","onMove","onDragStart","onMoveStart","onDragEnd"].forEach(function(r){"function"==typeof e[r]?(n[r]=e[r],t[r]=n[r].bind(t.ins)):e.hasOwnProperty(r)&&null==e[r]&&(n[r]=t[r]=void 0)})}wt.move=function(t,e,n){St.call(window,Ht),Et(),Ct===t&&(e.x&&Bt.x&&(e.x.lastValue=Bt.x.lastValue),e.y&&Bt.y&&(e.y.lastValue=Bt.y.lastValue)),Ct=t,Bt=e,Ot=n;var r=Date.now();["x","y"].forEach(function(t){var e=Bt[t];e&&(e.lastFrameTime=r)}),Ht=bt.call(window,Tt)},wt.stop=function(){St.call(window,Ht),Et(),Bt={},Ct=null};var ee=function(){function t(e,n){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t);var r={ins:this,options:{zIndex:A},disabled:!1};if(Object.defineProperty(this,"_id",{value:++ot}),r._id=this._id,et[this._id]=r,!Dt(e)||e===lt)throw new Error("This element is not accepted.");if(n){if(!Q(n))throw new Error("Invalid options.")}else n={};var o=!0,i=void 0;if(e instanceof SVGElement&&(i=e.ownerSVGElement)){if(!e.getBBox)throw new Error("This element is not accepted. (SVGLocatable)");if(!e.transform)throw new Error("This element is not accepted. (SVGAnimatedTransformList)");r.svgTransform=e.transform.baseVal.appendItem(i.createSVGTransform()),r.svgPoint=i.createSVGPoint();var a=e.nearestViewportElement;r.svgCtmElement=$?a.appendChild(document.createElementNS(i.namespaceURI,"rect")):a,o=!1,r.initElm=Jt,r.moveElm=qt}else{var l=P.getName("willChange");l&&(o=!1),!n.leftTop&&ft?(l&&(e.style[l]="transform"),r.initElm=Zt,r.moveElm=Vt):(l&&(e.style[l]="left, top"),r.initElm=$t,r.moveElm=Gt)}if(r.element=function(t,e){var n=t.style;n.webkitTapHighlightColor="transparent";var r=P.getName("boxShadow"),o=window.getComputedStyle(t,"")[r];return o&&"none"!==o||(n[r]="0 0 1px transparent"),e&&ft&&(n[ft]="translateZ(0)"),t}(e,o),r.elementStyle=e.style,r.orgZIndex=r.elementStyle.zIndex,gt&&X(e).add(gt),r.pointerEventHandlerId=rt.regStartHandler(function(t){return function(t,e){return!(t.disabled||t.onDragStart&&!1===t.onDragStart(e)||(it&&Qt(it),zt(t.options.handle),lt.style.cursor=st||window.getComputedStyle(t.options.handle,"").cursor,!1!==t.options.zIndex&&(t.elementStyle.zIndex=t.options.zIndex),pt&&(lt.style[pt]="none"),yt&&X(t.element).add(yt),it=t,at=!1,nt.left=t.elementBBox.left-(e.clientX+window.pageXOffset),nt.top=t.elementBBox.top-(e.clientY+window.pageYOffset),0))}(r,t)}),!n.containment){var u;n.containment=(u=e.parentNode)&&Dt(u)?u:lt}n.handle||(n.handle=e),te(r,n)}return Y(t,[{key:"remove",value:function(){var t=et[this._id];this.disabled=!0,rt.unregStartHandler(rt.removeStartHandler(t.options.handle,t.pointerEventHandlerId)),delete et[this._id]}},{key:"setOptions",value:function(t){return Q(t)&&te(et[this._id],t),this}},{key:"position",value:function(){return Kt(et[this._id]),this}},{key:"disabled",get:function(){return et[this._id].disabled},set:function(t){var e=et[this._id];(t=!!t)!==e.disabled&&(e.disabled=t,e.disabled?(e===it&&Qt(e),e.options.handle.style.cursor=e.orgCursor,pt&&(e.options.handle.style[pt]=e.orgUserSelect),gt&&X(e.element).remove(gt)):(Mt(e.options.handle,e.orgCursor),pt&&(e.options.handle.style[pt]="none"),gt&&X(e.element).add(gt)))}},{key:"element",get:function(){return et[this._id].element}},{key:"rect",get:function(){return It(et[this._id].elementBBox)}},{key:"left",get:function(){return et[this._id].elementBBox.left},set:function(t){te(et[this._id],{left:t})}},{key:"top",get:function(){return et[this._id].elementBBox.top},set:function(t){te(et[this._id],{top:t})}},{key:"containment",get:function(){var t=et[this._id];return t.containmentIsBBox?Wt(t.options.containment):t.options.containment},set:function(t){te(et[this._id],{containment:t})}},{key:"snap",get:function(){return It(et[this._id].options.snap)},set:function(t){te(et[this._id],{snap:t})}},{key:"autoScroll",get:function(){return It(et[this._id].options.autoScroll)},set:function(t){te(et[this._id],{autoScroll:t})}},{key:"handle",get:function(){return et[this._id].options.handle},set:function(t){te(et[this._id],{handle:t})}},{key:"zIndex",get:function(){return et[this._id].options.zIndex},set:function(t){te(et[this._id],{zIndex:t})}},{key:"onDrag",get:function(){return et[this._id].options.onDrag},set:function(t){te(et[this._id],{onDrag:t})}},{key:"onMove",get:function(){return et[this._id].options.onMove},set:function(t){te(et[this._id],{onMove:t})}},{key:"onDragStart",get:function(){return et[this._id].options.onDragStart},set:function(t){te(et[this._id],{onDragStart:t})}},{key:"onMoveStart",get:function(){return et[this._id].options.onMoveStart},set:function(t){te(et[this._id],{onMoveStart:t})}},{key:"onDragEnd",get:function(){return et[this._id].options.onDragEnd},set:function(t){te(et[this._id],{onDragEnd:t})}}],[{key:"draggableCursor",get:function(){return ht},set:function(t){ht!==t&&(ht=t,ut=null,Object.keys(et).forEach(function(t){var e=et[t];e.disabled||e===it&&!1!==st||(Mt(e.options.handle,e.orgCursor),e===it&&(lt.style.cursor=dt,lt.style.cursor=window.getComputedStyle(e.options.handle,"").cursor))}))}},{key:"draggingCursor",get:function(){return mt},set:function(t){mt!==t&&(mt=t,st=null,it&&(zt(it.options.handle),!1===st&&(Mt(it.options.handle,it.orgCursor),lt.style.cursor=dt),lt.style.cursor=st||window.getComputedStyle(it.options.handle,"").cursor))}},{key:"draggableClass",get:function(){return gt},set:function(t){(t=t?t+"":void 0)!==gt&&(Object.keys(et).forEach(function(e){var n=et[e];if(!n.disabled){var r=X(n.element);gt&&r.remove(gt),t&&r.add(t)}}),gt=t)}},{key:"draggingClass",get:function(){return yt},set:function(t){if((t=t?t+"":void 0)!==yt){if(it){var e=X(it.element);yt&&e.remove(yt),t&&e.add(t)}yt=t}}},{key:"movingClass",get:function(){return xt},set:function(t){if((t=t?t+"":void 0)!==xt){if(it&&at){var e=X(it.element);xt&&e.remove(xt),t&&e.add(t)}xt=t}}}]),t}();rt.addMoveHandler(document,function(t){if(it){var e={left:t.clientX+window.pageXOffset+nt.left,top:t.clientY+window.pageYOffset+nt.top};if(Ut(it,e,it.snapTargets?function(t){var e=it.snapTargets.length,n=!1,r=!1,o=void 0;for(o=0;o=i.gravityXStart)&&(null==i.gravityXEnd||t.left<=i.gravityXEnd)&&(null==i.gravityYStart||t.top>=i.gravityYStart)&&(null==i.gravityYEnd||t.top<=i.gravityYEnd)&&(n||null==i.x||(t.left=i.x,n=!0,o=-1),r||null==i.y||(t.top=i.y,r=!0,o=-1))}return t.snapped=n||r,!it.onDrag||it.onDrag(t)}:it.onDrag)){var n={},r=it.autoScroll;if(r){var o={x:it.elementBBox.left-window.pageXOffset,y:it.elementBBox.top-window.pageYOffset};["x","y"].forEach(function(t){if(r[t]){var e=r[t].min,i=r[t].max;r[t].lines.some(function(r){return(-1===r.dir?o[t]<=r.position:o[t]>=r.position)&&(n[t]={dir:r.dir,speed:r.speed/1e3,min:e,max:i},!0)})}})}n.x||n.y?(wt.move(r.target,n,r.isWindow?kt:Pt),e.autoScroll=!0):wt.stop(),at||(at=!0,xt&&X(it.element).add(xt),it.onMoveStart&&it.onMoveStart(e)),it.onMove&&it.onMove(e)}}});var ne=function(){it&&Qt(it)};rt.addEndHandler(document,ne),rt.addCancelHandler(document,ne);var re=function(){ct=P.getName("transitionProperty"),ft=P.getName("transform"),dt=lt.style.cursor,(pt=P.getName("userSelect"))&&(vt=lt.style[pt]);var t={},e=void 0;function n(t,e){t.initElm&&Kt(t,e)}var r=!1,o=c.add(function(o){r||(r=!0,it&&(n(it,o.type),rt.move(),t[it._id]=!0),clearTimeout(e),e=setTimeout(function(){!function(r){clearTimeout(e),Object.keys(et).forEach(function(e){t[e]||n(et[e],r)}),t={}}(o.type)},200),r=!1)});window.addEventListener("resize",o,!0),window.addEventListener("scroll",o,!0)};(lt=document.body)?re():document.addEventListener("DOMContentLoaded",function(){lt=document.body,re()},!0);e.default=ee}]).default; \ No newline at end of file diff --git a/examples/program_analysis/ui/serve.py b/examples/program_analysis/ui/serve.py new file mode 100644 index 0000000..faf7567 --- /dev/null +++ b/examples/program_analysis/ui/serve.py @@ -0,0 +1,58 @@ +"""Starts a server displaying an interactive version of a code analogy.""" +import os +import json +import http.server +import socketserver +from ui.lazy_structure_parser import parse_lazy_structure + +PORT = 8001 + +# https://stackoverflow.com/questions/18444395 +class RequestHandler(http.server.BaseHTTPRequestHandler): + """Server handler for code triplet structures.""" + def do_GET(self): + """Serves static content and parsed structures.""" + + if self.path == "/Structure": + self.send_good_headers("application/json") + structure = parse_lazy_structure(self.structure) + self.wfile.write(json.dumps(structure).encode()) + elif self.path.count("/") == 1: + path = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + path += f"/examples/program_analysis/ui{self.path}" + if self.path == "/": + path += "index.html" + try: + with open(path, "r") as disk_file: + data = disk_file.read().encode() + except OSError: + self.send_response(404) + else: + if ".css" in self.path: + self.send_good_headers("text/css") + else: + self.send_good_headers("text/html") + self.wfile.write(open(path).read().encode()) + else: + self.send_response(404) + + def send_good_headers(self, content_type): + """Send a 200 along with the given content_type.""" + self.send_response(200) + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Content-type", content_type) + self.end_headers() + +class ReuseAddrTCPServer(socketserver.TCPServer): + """Server allowing to start over an existing port. + + https://stackoverflow.com/questions/15260558 + """ + allow_reuse_address = True + +def start_server(structure): + """Start serving an interactive version of the LazyStructure @structure.""" + RequestHandler.structure = structure + with ReuseAddrTCPServer(("", PORT), RequestHandler) as httpd: + print(f"Result available at http://localhost:{PORT}") + httpd.serve_forever() diff --git a/examples/program_analysis/ui/style.css b/examples/program_analysis/ui/style.css new file mode 100644 index 0000000..33ba1a4 --- /dev/null +++ b/examples/program_analysis/ui/style.css @@ -0,0 +1,35 @@ +body { + vertical-align: top; + min-height: 1500px; +} + +.document { + white-space: pre; + line-height: 1.5em; + font-family: mono; + font-size: 0.95em; + position: relative; + border: 3px solid red; + display: inline-block; + padding: 10px; + margin-left: 5px; + vertical-align: top; +} + .chunk-in-structure { + border: 3px solid gray; + } + .chunk-in-map { + border: 3px solid green; + } + .highlight { + background-color: green; + } +.document.generated { + border-color: blue; +} + .document.generated .chunk-in-map { + border-color: blue; + } + .document.generated .highlight { + background-color: blue; + } -- cgit v1.2.3