-
Notifications
You must be signed in to change notification settings - Fork 0
/
interpret_haskell.py
164 lines (136 loc) · 5.42 KB
/
interpret_haskell.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import sublime, sublime_plugin
import subprocess, thread, os, os.path
#GHCi gives us lots of "Prelude|" lines when we give it multi-line input, so we clean these up
def cleanup_prelude(text):
#first item minus the > at the beginning
repeated_text = text.split("| ")[0].replace('>', '') + "| "
return text.replace(repeated_text, "")
def is_literate(text):
return text[0:2] == "> "
def remove_literate(text):
if (is_literate(text)):
return text[2:]
return text
def filter_literate_text(text):
if (not is_literate(text)):
return text
return '\n'.join(map(remove_literate, text.splitlines()))
#Turns top-level assignments (e.g. x = 5) into let statements to work with GHCi
def add_let_if_needed(text):
keywords = ["let", "data", "type"]
has_no_equals_sign = text.find("=") == -1
begins_with_keyword = any([text.startswith(keyword) for keyword in keywords])
if (has_no_equals_sign or begins_with_keyword):
return text
return "let " + text
#Group together lines with subsequent indented lines to determine what should be given to GHCi using multi-line mode and what to give with single-line mode
group_indented_sections_testcase = """\
do
x <- return 1
case x of
1 -> print "hi"
print "seven"
print "ham"
do
x <- return 5
print x
print "cheese"
print "whiz"
"""
def group_indented_sections(text):
groups = []
current_group = []
for line in text.splitlines():
if line[:1].isspace():
current_group.append(line)
else:
if len(current_group):
groups.append(current_group)
current_group = [line]
if len(current_group):
groups.append(current_group)
return groups
def ghci(text):
sublime.run_command("ghci_interpret_text", {"text":text})
class GhciLoadModule(sublime_plugin.TextCommand):
def run(self, edit):
file_name = self.view.file_name()
file_dir = os.path.dirname(file_name)
ghci(":cd " + file_dir)
ghci(":load " + quote_text(file_name) + " " + quote_text(documentation_helper_path()))
ghci(":module +FindDocumentation")
def quote_text(text):
return "\"" + text + "\""
class GhciCommand(sublime_plugin.TextCommand):
def run_command_on_regions(self, command, quote=False):
for region in self.view.sel():
if not region.empty():
text = self.view.substr(region)
if quote:
text = quote_text(text)
ghci(command+" "+text)
class GhciOpenModuleDocs(GhciCommand):
def run(self, edit):
self.run_command_on_regions("openDocsFor", quote=True)
class GhciBrowseModule(GhciCommand):
def run(self, edit):
self.run_command_on_regions(":browse")
class GhciPrintType(GhciCommand):
def run(self, edit):
self.run_command_on_regions(":type")
class GhciPrintInfo(GhciCommand):
def run(self, edit):
self.run_command_on_regions(":info")
class GhciInterpretRegions(sublime_plugin.TextCommand):
def run(self, edit, **kwargs):
print "hi"
prepend = ""
if (kwargs.has_key("prepend")):
prepend = kwargs["prepend"]
# for each region in the current view, run on either the region's selection or the line
for region in self.view.sel():
if not region.empty():
# Get the selected text
text = self.view.substr(region)
else: # Get the text of the current line
text = self.view.substr(self.view.line(region))
self.tell_ghci_multiline(prepend + text)
def tell_ghci_multiline(self, text):
# Write to GHCi using multi-line syntax so we can support multiple lines
text = filter_literate_text(text)
grouped_lines = group_indented_sections(text)
for line_group in grouped_lines:
is_single_line = len(line_group) == 1
line_group_with_let_if_needed = [add_let_if_needed(line_group[0])] + line_group[1:]
if (is_single_line):
self.tell_ghci(line_group_with_let_if_needed[0])
else:
map(self.tell_ghci, [":{"] + line_group_with_let_if_needed + [":}"])
def tell_ghci(self, text):
ghci(text)
def documentation_helper_path():
return sublime.packages_path() + "/SublimeGHCi/" + "FindDocumentation.hs"
class GhciInterpretText(sublime_plugin.ApplicationCommand):
def __init__(self):
self.process = subprocess.Popen(["ghci", documentation_helper_path()], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if self.process.stdout:
thread.start_new_thread(self.read_stdout, ())
if self.process.stderr:
thread.start_new_thread(self.read_stderr, ())
self.setup_prompt()
def setup_prompt(self):
self.tell_ghci(":set prompt >")
# we force ghci to print something to keep it from buffering its first "Prelude>" output
self.tell_ghci("print \"GHCi Ready.\"")
def read_stdout(self):
while True:
print cleanup_prelude(self.process.stdout.readline())
def read_stderr(self):
while True:
print cleanup_prelude(self.process.stderr.readline())
def tell_ghci(self, text):
ghci = self.process.stdin
print text
map(ghci.write, [text, '\n'])
def run(self, text=""):
self.tell_ghci(text)