-
Notifications
You must be signed in to change notification settings - Fork 2
/
ECSBBExtendedInterpreter.java
368 lines (324 loc) · 13.8 KB
/
ECSBBExtendedInterpreter.java
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
package me.atlne.barebones;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
//Class representing a BareBonesExtended (BBE) program
//Comments implemented by adding semicolon to lines starting with // if not ending with one already
//Added support for further blocks by finding block ends (multiple end delimiters)
//Added support for some syntax error logging
//Added support for setting variable values to other variables / numbers using recursive evaluation function
//Added support for basic mathematical operations
public class ECSBBExtendedInterpreter {
//Stores regex to check if string is integer
public static final Pattern INT_PATTERN = Pattern.compile("^-?\\d+$");
//Stores source code for program
private String source;
//Stores individual lines for program
private String[] lines;
//Stores a log of the state of the variables throughout the program
private String log = "";
//Stores map of all variables (Stored as integers) with their names
private HashMap<String, Integer> variables;
//Constructor for the program, takes in the source code for the program
public ECSBBExtendedInterpreter(String source) {
//Uses setter to set source and initialise lines array
setSource(source);
//Initialises variable mapping
variables = new HashMap<>();
}
//Main method, gets user to input name of program to run then runs from file
public static void main(String[] args) {
//Gets file name from user and reads file into string, creates new program from string and runs program
new ECSBBExtendedInterpreter(readFile(JOptionPane.showInputDialog(null, "Enter file name of BareBones program you'd like to run"))).run();
}
//Helper method to read file from file name, returns contents of file as string
public static String readFile(String fileName) {
//Declares string to store file contents
String contents = "";
//Creates buffered reader to read file
try(BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
//Iterates over every line in reader
for(Object line : reader.lines().toArray()) {
//Adds line to contents
contents += line.toString() + "\n";
}
//Closes reader after reading completed
reader.close();
} catch (IOException e) {
e.printStackTrace();
//If error occurs, shows error dialogue box
JOptionPane.showMessageDialog(null, "An error occurred whilst reading file \"" + fileName + "\"!");
}
//Returns file contents
return contents;
}
//Helper method to find if string matches any strings in set
public static boolean matchesAny(String a, String[] set) {
//Iterates over all strings in set
for(String e : set) {
//If element equal to a, returns true
if(a.equals(e)) {
return true;
}
}
//Else returns false if no matches found
return false;
}
//Helper method combining string array into string seperated by spaces from index specified onwards
public static String combineSpaces(String[] parts, int start) {
//String storing result
String result = "";
//Iterates over all parts from start onwards (inclusive)
for(int i = start; i < parts.length; i++) {
result += parts[i] + " ";
}
//Returns trimmed result to remove trailing space
return result.trim();
}
//Writes log file
public void writeLog() {
try {
//Uses file writer to write log to file
FileWriter logWriter = new FileWriter("log.txt");
logWriter.write(log.trim());
logWriter.close();
//Outputs log file creation message
JOptionPane.showMessageDialog(null, "Log file outputted to \"log.txt\" containing details of variable states after very line was executed.");
} catch (IOException e) {
e.printStackTrace();
//If error occurs, shows error dialogue box
JOptionPane.showMessageDialog(null, "An error occurred whilst writing the log file!");
}
}
//Helper method to display error message and create log file
public void throwError(String message) {
//Defines error message string
String errorMessage = "Error: " + message;
//Outputs error message
JOptionPane.showMessageDialog(null, "Execution failed!\n" + errorMessage);
//Adds error message to log
log += errorMessage;
//Writes log file
writeLog();
//Exits program
System.exit(0);
}
//Runs program from start to end
public void run() {
//Runs from index 0 to length of lines - 1
run(0, lines.length - 1);
//Outputs that execution was successful
JOptionPane.showMessageDialog(null, "Execution successful!");
//Displays final log in message box
JOptionPane.showMessageDialog(null, "OUTPUT:\n" + getVariableValues().trim());
//Writes log file
writeLog();
}
//Gets current values of variables
public String getVariableValues() {
//Declares string to store output
String output = "";
//Iterates over all variables in map's names and adds each value to string in format - "varName: value" per line
for(String varName : variables.keySet()) {
//Adds variable to output with value
output += varName + ": " + variables.get(varName) + "\n";
}
return output;
}
//Method to run program between indices specified
public void run(int startIndex, int endIndex) {
//Iterates over lines
for(int i = startIndex; i <= endIndex; i++) {
//Gets line from array, removing whitespace
String line = lines[i].trim();
//Checks if line starts with "//" (comment)
if(line.startsWith("//")) {
//Ignores and continues to next line
continue;
}
//Splits line into parts by spaces
String[] parts = line.split(" ");
//Gets operation code
String opcode = parts[0].toLowerCase();
//Checks what line does by testing first part case-insensitively
if(opcode.equals("clear")) { //Checks if clearing variable (sets value to 0)
//Puts variable name into map with value of 0 (gotten from second part)
variables.put(parts[1], 0);
} else if(opcode.equals("incr")) { //Checks if incrementing variable (adds 1 to value)
add(parts[1], 1);
} else if(opcode.equals("decr")) { //Checks if decrementing variables (adds -1 to value)
add(parts[1], -1);
} else if(opcode.equals("while")) { //Checks if start of while loop
//Finds end statement associated with while
int endStatementIndex = findBlockEnd("while", new String[] {"end"}, i, endIndex);
//Gets variable being checked (condition variable)
String condVar = parts[1];
//Gets value it cannot be for loop to continue (end value)
int endVal = evaluate(parts[3]);
//Checks if conditional variable exists, if not, sets value to 0
if(!variables.containsKey(condVar)) {
variables.put(condVar, 0);
}
//Runs block between while conditional variable not equal to end value
while(variables.get(condVar) != endVal) {
run(i + 1, endStatementIndex - 1);
}
} else if(opcode.equals("set")) { //Checks if setting variable value
//Sets variable from second part equal to following statement (all parts after second)
variables.put(parts[1].trim(), evaluate(combineSpaces(parts, 2)));
} else if(opcode.equals("add")) { //Checks if adding to variable
//Checks if the variable exists
if(variables.containsKey(parts[1].trim())) {
//Sets variable from second part equal to itself plus following statement (all parts after second)
variables.put(parts[1].trim(), variables.get(parts[1].trim()) + evaluate(combineSpaces(parts, 2)));
} else {
//Sets variable from second part equal to following statement (all parts after second)
variables.put(parts[1].trim(), evaluate(combineSpaces(parts, 2)));
}
} else if(opcode.equals("sub")) { //Checks if subtracting from variable
//Checks if the variable exists
if(variables.containsKey(parts[1].trim())) {
//Sets variable from second part equal to itself minus following statement (all parts after second)
variables.put(parts[1].trim(), variables.get(parts[1].trim()) - evaluate(combineSpaces(parts, 2)));
} else {
//Sets variable from second part equal to -1 * following statement (all parts after second)
variables.put(parts[1].trim(), -evaluate(combineSpaces(parts, 2)));
}
} else if(opcode.equals("mult")) { //Checks if multiplying variable by amount
//Checks if the variable exists
if(variables.containsKey(parts[1].trim())) {
//Sets variable from second part equal to itself minus following statement (all parts after second)
variables.put(parts[1].trim(), variables.get(parts[1].trim()) * evaluate(combineSpaces(parts, 2)));
} else {
//Sets variable from second part equal to 0
variables.put(parts[1].trim(), 0);
}
} else if(opcode.equals("div")) { //Checks if dividing variable by amount
//Checks if the variable exists
if(variables.containsKey(parts[1].trim())) {
//Sets variable from second part equal to itself minus following statement (all parts after second)
variables.put(parts[1].trim(), variables.get(parts[1].trim()) / evaluate(combineSpaces(parts, 2)));
} else {
//Sets variable from second part equal to 0
variables.put(parts[1].trim(), 0);
}
} else if(opcode.equals("mod")) { //Checks if modulating variable by amount (remainder division)
//Checks if the variable exists
if(variables.containsKey(parts[1].trim())) {
//Sets variable from second part equal to itself minus following statement (all parts after second)
variables.put(parts[1].trim(), variables.get(parts[1].trim()) % evaluate(combineSpaces(parts, 2)));
} else {
//Sets variable from second part equal to 0
variables.put(parts[1].trim(), 0);
}
} else if(opcode.equals("exp")) { //Checks if exponentiating variable by amount
//Gets evaluated amount from parts 2 and onward
int power = evaluate(combineSpaces(parts, 2));
//Checks if the variable exists
if(variables.containsKey(parts[1].trim())) {
//Sets variable from second part equal to itself minus following statement (all parts after second)
variables.put(parts[1].trim(), (int) Math.pow(variables.get(parts[1].trim()), power));
} else {
//Sets variable from second part equal to 0 if power != 0 and 1 if power is 0 (lim{n->inf} (n^n) -> 1)
variables.put(parts[1].trim(), power == 0 ? 1 : 0);
}
}
//Adds current values of variables to log string
log += "Line " + (i + 1) + ":\n" + getVariableValues() + "\n";
}
}
//Evaluates given statement
public int evaluate(String statement) {
//Trims statement for sanitisation
statement = statement.trim();
//Checks if statement is numerical or not
if(INT_PATTERN.matcher(statement).find()) { //In case of integer
//Resets the regex matcher
INT_PATTERN.matcher(statement).reset();
return Integer.parseInt(statement);
} else { //In case of variable
//Checks if variable exists in map
if(variables.containsKey(statement)) {
//Returns variable value
return variables.get(statement);
} else {
throwError("Variable \"" + statement + "\" not defined!");
return 0;
}
}
}
//Adds amount to variable
public void add(String varName, int amount) {
//Checks if variable in map
if(variables.containsKey(varName)) {
//Puts value of variable + amount into map
variables.put(varName, variables.get(varName) + amount);
} else {
//Puts amount into map if not in map
variables.put(varName, amount);
}
}
//Returns end statement index of block to run within bounds given (start being block statement's definition line)
public int findBlockEnd(String blockType, String[] endDelims, int startIndex, int endIndex) {
//Finds end statement associated with block by finding first one, adding on to the count for each block found first
int endCount = 1, endStatementIndex = endIndex;
//Loops over all lines in current block after block
for(int i = startIndex + 1; i <= endIndex; i++) {
//Gets line currently checking in block in lowercase
String blockLine = lines[i].trim().toLowerCase();
//Checks if line is of block type
if(blockLine.startsWith(blockType)) {
//Increments end count
endCount++;
} else if(matchesAny(blockLine, endDelims)) { //Checks if any end delimeters found
//Decrements end count
endCount--;
}
//Checks if end count reached 0
if(endCount == 0) {
//Sets end index to current iteration index
endStatementIndex = i;
//Breaks out of loop as end found
break;
}
}
return endStatementIndex;
}
public String getSource() {
return source;
}
public String[] getLines() {
return lines;
}
public HashMap<String, Integer> getVariables() {
return variables;
}
public void setSource(String source) {
//Trims source to remove unnecessary whitespace
this.source = source.trim();
//Splits source line-by-line
String[] lines = this.source.split("\n");
//Iterates over every line to check for comments
for(int i = 0; i < lines.length; i++) {
//Checks if line starts with "//" and not ending in ";"
if(lines[i].trim().startsWith("//") && !lines[i].trim().endsWith(";")) {
//Adds a semicolon to the line so treated as individual line that is ignored in run method
lines[i] += ";";
}
}
//Sets source to empty string
this.source = "";
//Reconstructs source from lines by iterating over and adding new lines
for(String line : lines)
this.source += line + "\n";
//Trims source
this.source.trim();
//Splits lines into array using ";" delimiter
//Ignores last semicolon to avoid final empty line
this.lines = this.source.substring(0, this.source.lastIndexOf(";")).split(";");
}
}