Skip to content

Instantly share code, notes, and snippets.

@InI4
Last active April 27, 2025 20:10
Show Gist options
  • Save InI4/bbc13db6929b8c1521b5d96ba27cf2d0 to your computer and use it in GitHub Desktop.
Save InI4/bbc13db6929b8c1521b5d96ba27cf2d0 to your computer and use it in GitHub Desktop.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.stream.Stream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Providing minsky machine in the sense of
* https://codegolf.stackexchange.com/questions/279153/long-running-section-11-4-minsky-machine
*
* A fairly empty class to hold all together in plain old Java fashion.
*/
public class MinskyMachine {
public final static Locale EN = Locale.ENGLISH;
private final static Instruction[] DUMMY = new Instruction[0];
public final static char LABEL_CHAR = ':';
public final static char COMMENT_CHAR = '#';
public final static String NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_";
public final static String DIGITS = "0123456789";
public final static String LABEL = NAME + DIGITS;
public MinskyMachine() {
}
//------------------------------------------------------------------------
// Encoding of compiled code:
/** Static description of an instruction. */
public static abstract class Instruction {
private final String registerID;
public Instruction(String registerID) {
this.registerID = registerID;
}
public abstract void step(State state);
public String getRegisterID() { return registerID; }
}
public static class Inc extends Instruction {
public Inc(String registerID) {
super(registerID);
}
public void step(State state) { state.inc(); }
public String toString() { return "+"+getRegisterID(); }
}
public static class Dec extends Instruction {
private final int target;
public Dec(String registerID, int target) {
super(registerID);
this.target = target;
}
public int getTarget() {
return target;
}
public void step(State state) { state.dec(this); }
public String toString() { return "-"+getRegisterID()+target; }
}
//------------------------------------------------------------------------
// "Runtime environment":
/** A (mutable) register. */
public static class Register {
private int value = 0;
public Register() {
}
public int getValue() { return value; }
public boolean isZero() { return value == 0; }
public void inc() { ++value; }
public void dec() { --value; }
public String toString() { return Integer.toString(value); }
}
/** A state is a runnable version of code. */
public static class State {
private final int logLevel;
private final Instruction[] instructions;
private final Register[] registers;
private final HashMap<String,Register> registersMap = new HashMap<>(); // for toString()!
private int pc = 0;
private long steps = 0;
public State(Instruction[] instructions, int logLevel) {
// We want to be able, to have different States running in parallel threads!
// Thus we clone the instructions and provide fresh registers on startup.
this.instructions = instructions.clone();
registers = new Register[instructions.length];
for(int i = 0; i < instructions.length; i++) {
String registerID = instructions[i].getRegisterID();
registers[i] = registersMap.computeIfAbsent(registerID, k -> new Register());
}
this.logLevel = logLevel;
tryLog(2);
}
private void tryLog(int target) {
if ( logLevel >= target )
System.out.println(this);
}
public String toString() {
return "[S:"+pc+" "+registersMap+" "+getSteps()+"]";
}
public void inc() {
registers[pc++].inc();
}
public void dec(Dec instruction) {
Register register = registers[pc];
if ( register.isZero() ) {
pc++;
tryLog(1);
} else {
register.dec();
pc = instruction.getTarget();
}
}
public long getSteps() {
return steps;
}
public boolean step() {
if ( pc >= instructions.length )
return false;
Instruction instruction = instructions[pc];
instruction.step(this);
steps++;
tryLog(2);
return true;
}
public void run() {
while ( step() );
}
}
//------------------------------------------------------------------------
// Compile string to instruction(s):
public Instruction compileLine(String line, HashMap<String,Integer> labels) {
line = line.trim();
int i1 = scan(line, 1, NAME);
String registerID = line.substring(1, i1);
if ( line.charAt(0) == '+' )
return new Inc(registerID);
else if ( line.charAt(0) == '-' ) {
int target;
if ( line.charAt(i1) == LABEL_CHAR ) {
int i2 = scan(line, i1+1, LABEL);
String label = line.substring(i1+1,i2);
Integer t = labels.get(label);
if ( t == null )
compileException("Unknown label ´"+label+"´", line);
target = t;
} else {
int i2 = scan(line, i1, DIGITS);
target = Integer.parseInt(line.substring(i1,i2));
}
return new Dec(registerID, target);
} else {
compileException("No ´+-´", line);
return null;
}
}
public Instruction[] compile(boolean listLabels, Stream<String> lines) {
// 1st pass: handle comments and labels preceding instructions
HashMap<String,Integer> labels = new HashMap<>();
ArrayList<String> linesList = new ArrayList<>();
lines.forEach(line -> {
line = line.trim();
if ( line.charAt(0) != COMMENT_CHAR ) {
int noLabel = 0;
if ( line.charAt(0) == LABEL_CHAR ) {
noLabel = scan(line,1,LABEL);
String label = line.substring(1,noLabel);
Integer i2 = labels.put(label, linesList.size());
if ( i2 != null )
compileException("Duplicate label "+label+" @"+i2+"+"+linesList.size(), line);
}
linesList.add(line.substring(noLabel));
}
});
if ( listLabels && labels.size() > 0 )
System.out.println("Labels: "+labels);
// 2nd pass: handle instructions
ArrayList<Instruction> instructions = new ArrayList<>();
for(String line : linesList)
instructions.add(compileLine(line, labels));
return instructions.toArray(DUMMY);
}
public static int scan(String line, int start, String allowed) {
int i = start;
while ( i < line.length() && allowed.indexOf(line.charAt(i)) >= 0 )
i++;
if ( i == start )
compileException("No ´"+allowed+"´ @"+start, line);
return i;
}
private static void compileException(String msg, String line) {
throw new IllegalArgumentException(msg+" in `"+line+"`.");
}
//------------------------------------------------------------------------
public static void main(String... args) throws Exception {
if ( args.length == 0 ) {
test(0, false, Stream.of( "+B", "+B", "-A06", "+C", "+A", "-B04", "+B", "-A00", "-C06"));
test(0, false, Stream.of(":00 +B", "+B", "-A:06", "+C", ":04 +A", "-B:04", ":06 +B", "-A:00", "-C:06"));
} else {
boolean listFlag = false;
int logLevel = 0;
for(String arg : args) {
if ( "L".equalsIgnoreCase(arg) ) {
listFlag = true;
} else {
try {
logLevel = Integer.parseInt(arg);
} catch ( Exception ex ) {
test(logLevel, listFlag, Files.lines(Path.of(arg)));
listFlag = false;
logLevel = 0;
}
}
}
}
}
public static long test(int log, boolean list, Stream<String> lines) throws Exception {
MinskyMachine mm = new MinskyMachine();
Instruction[] code = mm.compile(false, lines);
State state = new State(code, log);
long t0 = System.nanoTime();
state.run();
long t1 = System.nanoTime();
long steps = state.getSteps();
System.out.printf(EN, "Steps=%8d with %2d instructions (%.2e s = %.2e ns/step). %s%n",
steps, code.length, 1e-9*(t1-t0), ((double)(t1-t0))/steps, state);
if ( list ) mm.list(code, System.out);
return steps;
}
public static void list(Instruction[] instructions, Appendable out) throws IOException {
for(Instruction instruction : instructions) {
out.append(instruction.toString());
out.append("\n");
}
}
}
# Optimized exponential prefill smaller quadratic
+A
+A
:S2 +C
+C
+C
-A:S2
:S3 +A
+A
+A
+A
-C:S3
:S4 +C
+C
+C
+C
-A:S4
:S5 +A
+A
+A
+A
-C:S5
:S6 +C
-B:X1
+C
+C
:X1 -A:S6
:L1 +A
+B
-C:L1
-A:D1
:D1 -A:D2
:D2 -A:S6
@InI4
Copy link
Author

InI4 commented Apr 27, 2025

This is a piece of seelf running java, no need to compile. Just start with java MinskyMachine.java trial8.msm on commandline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment