Created
December 14, 2018 05:46
-
-
Save Pyeroh/1b2903e53d71e0662d105dc0d6aedbdf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.BufferedReader; | |
import java.io.InputStream; | |
import java.io.InputStreamReader; | |
import java.io.OutputStream; | |
import java.util.Arrays; | |
import java.util.Deque; | |
import java.util.HashMap; | |
import java.util.LinkedList; | |
import java.util.Map; | |
import java.util.Optional; | |
@SuppressWarnings("ConstantConditions") // For IntelliJ to stop complaining about method "fail" | |
public class WhitespaceInterpreter { | |
// transforms space characters to ['s','t','n'] chars; | |
public static String unbleach(String code) { | |
return code != null ? code.replace(' ', 's').replace('\t', 't').replace('\n', 'n') : null; | |
} | |
public static String execute(String code, InputStream input) { | |
return execute(code, input, null); | |
} | |
public static String execute(String code, InputStream input, OutputStream stream) { | |
try { | |
return execute0(0, | |
code, | |
input, | |
stream, | |
new StringBuilder(), | |
new LinkedList<>(), | |
new HashMap<>(), | |
new HashMap<>(), | |
new HashMap<>()).value; | |
} finally { | |
if (stream != null) { | |
safeRun(stream::flush); | |
} | |
} | |
} | |
private static DecodedValue<String> execute0(int startingIndex, | |
String code, | |
InputStream input, | |
OutputStream stream, | |
StringBuilder output, | |
LinkedList<Integer> stack, | |
Map<Integer, Integer> heap, | |
Map<String, Integer> labels, | |
Map<String, Integer> labelsCount) { | |
code = code == null ? "" : code; | |
boolean exitSignal = false; | |
if (stream != null) { | |
safeRun(stream::flush); | |
} | |
if (code.isEmpty()) { | |
return fail("no code"); | |
} | |
if (startingIndex == 0) { | |
Arrays.stream(code.split("[ \n\t]+")) | |
.filter(s -> !s.isEmpty()) | |
.map("====="::concat) | |
.forEach(System.out::println); | |
code = unbleach(code.replaceAll("[^ \n\t]+", "")); | |
System.out.println(code); | |
detectLabels(code, labels, labelsCount); | |
} | |
BufferedReader lineInput = input == null ? null : new BufferedReader(new InputStreamReader(input)); | |
int i = startingIndex; | |
for (; i < code.length(); i++) { | |
char imp1 = code.charAt(i); | |
PointerIncrementer pi; | |
if (imp1 == 's') { | |
pi = stackManipulation(code.substring(++i), stack); | |
} else if (imp1 == 't') { | |
char imp2 = code.charAt(++i); | |
if (imp2 == 's') { | |
pi = arithmetic(code.substring(++i), stack); | |
} else if (imp2 == 't') { | |
pi = heapAccess(code.substring(++i), stack, heap); | |
} else { | |
pi = inputOutput(code.substring(++i), stack, heap, lineInput, output, stream); | |
} | |
} else { | |
pi = flowControl(code.substring(++i), i - 1, labels, stack, labelsCount); | |
if (((SubExecutor) pi).shouldExitProgram) { | |
exitSignal = true; | |
break; | |
} | |
if (((SubExecutor) pi).shouldExitSubRoutine) { | |
if (startingIndex != 0) { | |
exitSignal = true; | |
i = -1; | |
break; | |
} else { | |
fail("Why using return when not in a subroutine ?"); | |
} | |
} | |
if (((SubExecutor) pi).subroutineStartingIndex != -1) { | |
DecodedValue<String> executed = execute0(((SubExecutor) pi).subroutineStartingIndex + 1, code, input, stream, output, stack, heap, labels, labelsCount); | |
if (executed.pointerIncrement != -1) { | |
pi = executed; | |
} | |
} | |
} | |
i += pi.pointerIncrement; | |
} | |
if (startingIndex != 0 && i == code.length()) { | |
fail("No return for a subroutine"); | |
} | |
if (!exitSignal && i == code.length()) { | |
fail("Unclean termination"); | |
} | |
if (stream != null) { | |
safeRun(stream::flush); | |
} | |
System.out.println(output.toString()); | |
return new DecodedValue<>(output.toString(), i); | |
} | |
private static void detectLabels(String code, Map<String, Integer> labels, Map<String, Integer> labelsCount) { | |
for (int i = 0; i < code.length(); i++) { | |
char imp1 = code.charAt(i); | |
if (imp1 == 's') { | |
if (code.charAt(++i) == 'n') { | |
i++; | |
} else { | |
i = code.indexOf('n', i); | |
} | |
} else if (imp1 == 't') { | |
char imp2 = code.charAt(++i); | |
if (imp2 == 's' || imp2 == 'n') { | |
i += 2; | |
} else { | |
i++; | |
} | |
} else { | |
char imp2 = code.charAt(++i); | |
if (imp2 == 's') { | |
char imp3 = code.charAt(++i); | |
if (imp3 == 's') { | |
DecodedValue<String> decoded = decodeLabel(code.substring(++i)); | |
i += decoded.pointerIncrement; | |
labels.putIfAbsent(decoded.value, i); | |
labelsCount.put(decoded.value, labelsCount.getOrDefault(decoded.value, 0) + 1); | |
} else { | |
i = code.indexOf('n', ++i); | |
} | |
} else if (imp2 == 't') { | |
if (code.charAt(++i) != 'n') { | |
i = code.indexOf('n', ++i); | |
} | |
} else { | |
i++; | |
} | |
} | |
} | |
} | |
private static PointerIncrementer arithmetic(String code, Deque<Integer> stack) { | |
int i = -1; | |
char imp3 = code.charAt(++i); | |
Integer a = stack.pop(); | |
Integer b = stack.pop(); | |
if (imp3 == 's') { | |
char imp4 = code.charAt(++i); | |
if (imp4 == 's') { | |
stack.push(b + a); | |
} else if (imp4 == 't') { | |
stack.push(b - a); | |
} else { | |
stack.push(b * a); | |
} | |
} else if (imp3 == 't') { | |
char imp4 = code.charAt(++i); | |
if (imp4 == 's') { | |
stack.push(Math.floorDiv(b, a)); | |
} else if (imp4 == 't') { | |
stack.push(Math.floorMod(b, a)); | |
} else { | |
failUnexpected("tst", imp4); | |
} | |
} else { | |
failUnexpected("ts", imp3); | |
} | |
return new PointerIncrementer(i); | |
} | |
private static PointerIncrementer heapAccess(String code, Deque<Integer> stack, Map<Integer, Integer> heap) { | |
int i = -1; | |
char imp3 = code.charAt(++i); | |
Integer a = stack.pop(); | |
if (imp3 == 's') { | |
Integer b = stack.pop(); | |
heap.put(b, a); | |
} else if (imp3 == 't') { | |
stack.push(heap.get(a)); | |
} else { | |
failUnexpected("tt", imp3); | |
} | |
return new PointerIncrementer(i); | |
} | |
private static PointerIncrementer inputOutput(String code, Deque<Integer> stack, Map<Integer, Integer> heap, BufferedReader lineInput, StringBuilder output, OutputStream stream) { | |
int i = -1; | |
char imp3 = code.charAt(++i); | |
if (imp3 == 's') { | |
char imp4 = code.charAt(++i); | |
int firstValue = stack.pop(); | |
if (imp4 == 's') { | |
appendToOutput((char) firstValue, output, stream); | |
} else if (imp4 == 't') { | |
appendToOutput(firstValue, output, stream); | |
} else { | |
failUnexpected("tns", imp4); | |
} | |
} else if (imp3 == 't') { | |
char imp4 = code.charAt(++i); | |
Integer a = null; | |
if (imp4 == 's') { | |
a = safeGet(lineInput::read); | |
if (a == -1) { | |
fail("End of input"); | |
} | |
} else if (imp4 == 't') { | |
a = safeGet(() -> Integer.valueOf(lineInput.readLine())); | |
} else { | |
failUnexpected("tnt", imp4); | |
} | |
int b = stack.pop(); | |
heap.put(b, a); | |
} else { | |
failUnexpected("tn", imp3); | |
} | |
return new PointerIncrementer(i); | |
} | |
private static SubExecutor flowControl(String code, int startingIndex, Map<String, Integer> labels, Deque<Integer> stack, Map<String, Integer> labelsCount) { | |
int i = 0; | |
int subroutineStartingIndex = -1; | |
boolean exitSub = false; | |
boolean exitMain = false; | |
char imp2 = code.charAt(i++); | |
if (imp2 == 's') { | |
char imp3 = code.charAt(i++); | |
if (imp3 == 's') { | |
DecodedValue<String> decoded = decodeLabel(code.substring(i++)); | |
i += decoded.pointerIncrement; | |
int count = labelsCount.get(decoded.value); | |
if (count == 0 || count > 1) { | |
fail("Already existent label : \"" + decoded.value + "\""); | |
} | |
} else if (imp3 == 't') { | |
DecodedValue<String> decoded = decodeLabel(code.substring(i++)); | |
i += decoded.pointerIncrement; | |
subroutineStartingIndex = labels.get(decoded.value); | |
} else { | |
DecodedValue<String> decoded = decodeLabel(code.substring(i)); | |
i = labels.get(decoded.value) - startingIndex; | |
} | |
} else if (imp2 == 't') { | |
char imp3 = code.charAt(i++); | |
if (imp3 == 's') { | |
DecodedValue<String> decoded = decodeLabel(code.substring(i++)); | |
i += decoded.pointerIncrement; | |
if (stack.pop() == 0) { | |
i = labels.get(decoded.value) - startingIndex; | |
} | |
} else if (imp3 == 't') { | |
DecodedValue<String> decoded = decodeLabel(code.substring(i++)); | |
i += decoded.pointerIncrement; | |
if (stack.pop() < 0) { | |
i = labels.get(decoded.value) - startingIndex; | |
} | |
} else { | |
exitSub = true; | |
} | |
} else { | |
char imp3 = code.charAt(i++); | |
if (imp3 == 'n') { | |
i = code.length(); | |
exitMain = true; | |
} else { | |
failUnexpected("nn", imp3); | |
} | |
} | |
return new SubExecutor(i - 1, subroutineStartingIndex, exitSub, exitMain); | |
} | |
private static PointerIncrementer stackManipulation(String code, LinkedList<Integer> stack) { | |
int i = 0; | |
char imp2 = code.charAt(i++); | |
if (imp2 == 's') { | |
DecodedValue<Integer> integerDecodedValue = decodeNumber(code.substring(i++)); | |
i += integerDecodedValue.pointerIncrement; | |
stack.push(integerDecodedValue.value); | |
} else if (imp2 == 't') { | |
char imp3 = code.charAt(i++); | |
if (imp3 == 's') { | |
DecodedValue<Integer> integerDecodedValue = decodeNumber(code.substring(i++)); | |
i += integerDecodedValue.pointerIncrement; | |
stack.push(stack.get(integerDecodedValue.value)); | |
} else if (imp3 == 'n') { | |
DecodedValue<Integer> decoded = decodeNumber(code.substring(i++)); | |
i += decoded.pointerIncrement; | |
if (decoded.value < 0 || decoded.value >= stack.size()) { | |
stack.subList(1, stack.size()).clear(); | |
} else { | |
stack.subList(1, decoded.value + 1).clear(); | |
} | |
} else { | |
failUnexpected("st", imp3); | |
} | |
} else if (imp2 == 'n') { | |
char imp3 = code.charAt(i++); | |
if (imp3 == 's') { | |
stack.push(stack.getFirst()); | |
} else if (imp3 == 't') { | |
Integer firstValue = stack.pop(); | |
Integer secondValue = stack.pop(); | |
stack.push(firstValue); | |
stack.push(secondValue); | |
} else { | |
stack.pop(); | |
} | |
} | |
return new PointerIncrementer(i - 1); | |
} | |
private static DecodedValue<Integer> decodeNumber(String code) { | |
int i = 0; | |
char sign = code.charAt(i++); | |
if (sign == 't') { | |
sign = '-'; | |
} else if (sign == 's') { | |
sign = '+'; | |
} else { | |
fail("Unknown sign"); | |
} | |
StringBuilder number = new StringBuilder().append(sign).append('0'); | |
char bit; | |
while ((bit = code.charAt(i++)) != 'n') { | |
number.append(bit == 's' ? '0' : (bit == 't' ? '1' : (char) fail("Invalid bit"))); | |
} | |
return new DecodedValue<>(Integer.valueOf(number.toString(), 2), i - 1); | |
} | |
private static DecodedValue<String> decodeLabel(String code) { | |
int i = 0; | |
StringBuilder label = new StringBuilder(); | |
char bit; | |
while ((bit = code.charAt(i++)) != 'n') { | |
label.append(bit == 's' ? '0' : (bit == 't' ? '1' : (char) fail("Invalid bit"))); | |
} | |
return new DecodedValue<>(Optional.of(label.toString()) | |
.filter(s -> !s.isEmpty()) | |
.map(s -> Integer.valueOf(s, 2)) | |
.map(String::valueOf) | |
.orElse(""), i - 1); | |
} | |
private static void appendToOutput(char c, StringBuilder outputSB, OutputStream outputStream) { | |
outputSB.append(c); | |
if (outputStream != null) { | |
safeRun(() -> outputStream.write((int) c)); | |
} | |
} | |
private static void appendToOutput(int i, StringBuilder outputSB, OutputStream outputStream) { | |
outputSB.append(i); | |
if (outputStream != null) { | |
String.valueOf(i).chars().forEach(c -> safeRun(() -> outputStream.write(c))); | |
} | |
} | |
private static <T> T fail(String message) { | |
if (message != null) { | |
throw new RuntimeException(message); | |
} else { | |
throw new RuntimeException(); | |
} | |
} | |
private static void failUnexpected(String mainInstruction, char subInstruction) { | |
fail(String.format("Unexpected instruction after %s : %s", mainInstruction, subInstruction)); | |
} | |
private static <T> T safeGet(CheckedSupplier<T> supplier) { | |
try { | |
return supplier.get(); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private static void safeRun(CheckedRunnable runnable) { | |
try { | |
runnable.run(); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
@FunctionalInterface | |
interface CheckedSupplier<T> { | |
T get() throws Exception; | |
} | |
@FunctionalInterface | |
interface CheckedRunnable { | |
void run() throws Exception; | |
} | |
static class PointerIncrementer { | |
final int pointerIncrement; | |
PointerIncrementer(int pointerIncrement) { | |
this.pointerIncrement = pointerIncrement; | |
} | |
} | |
static class SubExecutor extends PointerIncrementer { | |
final int subroutineStartingIndex; | |
final boolean shouldExitSubRoutine; | |
final boolean shouldExitProgram; | |
SubExecutor(int pointerIncrement, int subroutineStartingIndex, boolean shouldExitSubRoutine, boolean shouldExitProgram) { | |
super(pointerIncrement); | |
this.subroutineStartingIndex = subroutineStartingIndex; | |
this.shouldExitSubRoutine = shouldExitSubRoutine; | |
this.shouldExitProgram = shouldExitProgram; | |
} | |
} | |
static class DecodedValue<T> extends PointerIncrementer { | |
final T value; | |
DecodedValue(T value, int pointerIncrement) { | |
super(pointerIncrement); | |
this.value = value; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment