-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCallDone.cpp
More file actions
156 lines (134 loc) · 6.85 KB
/
Copy pathCallDone.cpp
File metadata and controls
156 lines (134 loc) · 6.85 KB
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
/* Transform the Call / Done commands into pure TM commands.
*
* Call has the syntax 'Call [name-of-file]', which has the effect of
* running the code in [name-of-file] until Done is executed, then
* proceeding to the next line.
*
* Syntax: call-done [scripts-dir]
*/
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include "Turing/StrUtils/StrUtils.h"
using namespace std;
namespace {
/* Removes comments and leading/trailing whitespace.
* Comments begin with the # character. We have to be
* careful when implementing this to make sure that we
* don't treat the quoted string '#' as a comment.
*/
void cleanLine(string& line) {
/* Search for a comment. This will find the first # mark. It might be
* in quotes, in which case we should ignore it
* and search again.
*/
size_t comment = line.find('#');
if (comment != string::npos &&
comment != 0 && comment + 1 != line.size() &&
line[comment - 1] == '\'' &&
line[comment + 1] == '\'') {
/* Look for the next one. */
comment = line.find(comment + 1);
}
/* Now if we have a hash mark, it's definitely a comment. */
if (comment != string::npos) {
line.erase(comment);
}
line = Utilities::trim(line);
}
/* Information about a subroutine needed for translation. */
struct Subroutine {
string filename; // Which file contains this?
string doneLabel; // When we're done, where do we jump back to?
string prefix; // Prefix associated with all labels in the subroutine
};
/* Core translation routine. The parameters specify
*
* 1. The number of the next Call return jump label, and
* 2. The prefix to attach to each label to ensure uniqueness.
* 3. What directory to search for script files.
*/
void translateFile(istream& input, int& nextLabelID, const string& doneTarget, const string& prefix, const string& directory) {
for (string line; getline(input, line); ) {
cleanLine(line);
/* If the line is a label, translate it by adding a custom prefix. This
* allows labels within different files to not clash with one another.
*/
if (line.back() == ':') {
cout << prefix << line << endl;
}
/* Anything with a Goto must be translated as well. We need to check that the
* Goto we find isn't prefixed or suffixed by a non-space character; if we find
* such a character, it means what we're seeing is a label name, not a Goto
* command.
*/
else if (size_t index = line.find("Goto"); index != string::npos && (index == 0 || isspace(line[index - 1]))
&& (index + 4 == line.size() || isspace(line[index + 4]))) {
string target = Utilities::trim(line.substr(index + 4));
line.erase(index + 4);
line += " " + prefix + target;
cout << line << endl;
}
/* If the line contains 'Done', replace the 'Done' with a jump to the appropriate
* label. (Note that the 'Done' can't be a substring of a label name, since everything
* with labels is handled above.)
*/
else if (line.find("Done") != string::npos) {
/* Make sure this isn't at the top level. */
if (doneTarget == "") throw runtime_error("Found call to 'Done' without being in a subroutine.");
size_t index = line.find("Done"); // TODO: Find a nice way to avoid searching twice?
line.erase(index);
line += "Goto " + doneTarget;
cout << line << endl;
}
/* If the line contains 'Call,' set up the call structure. */
else if (line.find("Call") != string::npos) {
size_t index = line.find("Call"); // TODO: Find a nice way to avoid searching twice?
/* In order to make this work, we need to do the following.
*
* 1. We need to replace the 'Call' instruction with a jump to the label where the subroutine
* is going to start.
* 2. We need to insert a label into the line right after us. This will be where the called
* subroutine is going to jump to.
* 3. We need to record that we're making this subroutine call and insert it at the end of
* the program.
*/
/* Get the name of the module. */
Subroutine subroutine;
/* Filename is whatever is being called here. */
subroutine.filename = Utilities::trim(line.substr(index + 4));
/* Done label is named based on the current index. */
subroutine.doneLabel = "_DoneTarget_" + to_string(nextLabelID);
/* Prefix is the module name plus the current ID. */
subroutine.prefix = "_" + to_string(nextLabelID) + "_" + subroutine.filename + "_" + prefix;
/* Tick up the next label ID so we assign unique names. */
nextLabelID++;
/* Now we can start doing the steps from above. First, replace the command with a jump to the target. */
line.erase(index);
line += "Goto " + subroutine.prefix + "Start";
cout << line << endl;
/* In case the whole line is a Call, insert a jump to our own done label. */
cout << "Goto " << subroutine.doneLabel << endl;
/* Now insert the code for the subroutine. */
ifstream subFile(directory + "/" + subroutine.filename);
if (!subFile) throw runtime_error("Cannot open module file " + subroutine.filename);
translateFile(subFile, nextLabelID, subroutine.doneLabel, subroutine.prefix, directory);
/* In case there was a fallthrough, reject. */
cout << "Return False" << endl;
/* Now add the destination label. */
cout << subroutine.doneLabel << ":" << endl;
}
/* Everything else renders as usual. */
else {
cout << line << endl;
}
}
}
}
int main(int argc, const char* argv[]) {
string directory = ".";
if (argc == 2) directory = argv[1];
int nextLabelID = 0;
translateFile(cin, nextLabelID, "", "", directory);
}