View Javadoc
1   package legacy.forth;
2   
3   import java.util.Arrays;
4   import java.util.HashMap;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.Stack;
8   import java.util.stream.Collectors;
9   
10  public class ForthEvaluator {
11  	List<Integer> evaluateProgram(List<String> commands) {
12  		Stack<Integer> stack = new Stack<Integer>();
13  		Map<String, String> name2definition = new HashMap<String, String>();
14  		for (String command : commands) {
15  			command = command.toLowerCase();
16  
17  			if (command.startsWith(": ")) {
18  				List<String> tokens = tokenize(command.substring(2, command.length() - 2));
19  				String name = tokens.get(0);
20  
21  				if (isNumber(name)) {
22  					throw new IllegalArgumentException("Cannot redefine numbers");
23  				}
24  
25  				String definition = replace(String.join(" ", tokens.subList(1, tokens.size())), name2definition);
26  				name2definition.put(name, definition);
27  			} else {
28  				for (String token : tokenize(replace(command, name2definition))) {
29  					if (isNumber(token)) {
30  						int number = Integer.parseInt(token);
31  						stack.push(number);
32  					} else if (token.equals("+")) {
33  						checkStackSize(stack, "Addition", 2);
34  
35  						int number1 = stack.pop();
36  						int number2 = stack.pop();
37  						stack.push(number2 + number1);
38  					} else if (token.equals("-")) {
39  						checkStackSize(stack, "Subtraction", 2);
40  
41  						int number1 = stack.pop();
42  						int number2 = stack.pop();
43  						stack.push(number2 - number1);
44  					} else if (token.equals("*")) {
45  						checkStackSize(stack, "Multiplication", 2);
46  
47  						int number1 = stack.pop();
48  						int number2 = stack.pop();
49  						stack.push(number2 * number1);
50  					} else if (token.equals("/")) {
51  						checkStackSize(stack, "Division", 2);
52  
53  						int number1 = stack.pop();
54  						int number2 = stack.pop();
55  
56  						if (number1 == 0) {
57  							throw new IllegalArgumentException("Division by 0 is not allowed");
58  						}
59  
60  						stack.push(number2 / number1);
61  					} else if (token.equals("dup")) {
62  						checkStackSize(stack, "Duplicating", 1);
63  
64  						stack.push(stack.peek());
65  					} else if (token.equals("drop")) {
66  						checkStackSize(stack, "Dropping", 1);
67  
68  						stack.pop();
69  					} else if (token.equals("swap")) {
70  						checkStackSize(stack, "Swapping", 2);
71  
72  						int number1 = stack.pop();
73  						int number2 = stack.pop();
74  						stack.push(number1);
75  						stack.push(number2);
76  					} else if (token.equals("over")) {
77  						checkStackSize(stack, "Overing", 2);
78  
79  						stack.push(stack.get(stack.size() - 2));
80  					} else {
81  						throw new IllegalArgumentException(
82  								String.format("No definition available for operator \"%s\"", token));
83  					}
84  				}
85  			}
86  		}
87  		return stack;
88  	}
89  
90  	void checkStackSize(Stack<Integer> stack, String operation, int minSize) {
91  		if (stack.size() < minSize) {
92  			throw new IllegalArgumentException(String.format("%s requires that the stack contain at least %d value%s",
93  					operation, minSize, minSize == 1 ? "" : "s"));
94  		}
95  	}
96  
97  	String replace(String command, Map<String, String> name2definition) {
98  		return String.join(" ", tokenize(command).stream().map(token -> name2definition.getOrDefault(token, token))
99  				.collect(Collectors.toList()));
100 	}
101 
102 	List<String> tokenize(String s) {
103 		return Arrays.stream(s.split(" +")).filter(token -> !token.isEmpty()).collect(Collectors.toList());
104 	}
105 
106 	boolean isNumber(String s) {
107 		try {
108 			Integer.parseInt(s);
109 			return true;
110 		} catch (NumberFormatException e) {
111 			return false;
112 		}
113 	}
114 }