Ver código fonte

Working compiler

boono 11 meses atrás
pai
commit
0c8fb4e304

+ 57 - 0
src/main/java/xyz/kwiecien/jfuck/BFCompiler.java

@@ -0,0 +1,57 @@
+package xyz.kwiecien.jfuck;
+
+import java.io.IOException;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.CodeBuilder;
+import java.lang.constant.ClassDesc;
+import java.lang.constant.MethodTypeDesc;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Consumer;
+
+import static java.lang.classfile.ClassFile.*;
+import static java.lang.constant.ConstantDescs.*;
+import static xyz.kwiecien.jfuck.operation.BytecodeConstants.DATA_SIZE_CONSTANT_NAME;
+
+public class BFCompiler {
+    public static void main(String[] args) throws IOException {
+        if (args.length < 1) {
+            System.err.println("Usage: GenMain <filename>");
+            System.exit(1);
+        }
+        var path = Paths.get(args[0]);
+        if (!path.toFile().exists()) {
+            System.err.println("Unable to find file: " + args[0]);
+            System.exit(1);
+        }
+        var code = Files.readString(path);
+        var operations = new OperationExtractor().extract(code);
+
+        Consumer<CodeBuilder> initCode = cb -> cb
+                .aload(0)
+                .invokespecial(CD_Object, INIT_NAME, MTD_void).return_();
+        Consumer<CodeBuilder> mainCode = cb -> {
+            for (var operation : operations) {
+                operation.appendBytecode(cb);
+            }
+            cb.return_();
+        };
+
+        var bytes = ClassFile.of().build(ClassDesc.of("GenClass"), clb -> {
+            clb.withFlags(ACC_PUBLIC);
+            clb.withField(DATA_SIZE_CONSTANT_NAME, CD_int, ACC_STATIC | ACC_FINAL);
+            clb.withMethod(
+                            INIT_NAME,
+                            MTD_void,
+                            ACC_PUBLIC,
+                            mb -> mb.withCode(initCode))
+                    .withMethod(
+                            "main",
+                            MethodTypeDesc.of(CD_void, CD_String.arrayType()),
+                            ACC_PUBLIC + ACC_STATIC,
+                            mb -> mb.withCode(mainCode));
+        });
+        Files.write(Path.of("/tmp/GenClass.class"), bytes);
+    }
+}

+ 72 - 0
src/main/java/xyz/kwiecien/jfuck/OperationExtractor.java

@@ -0,0 +1,72 @@
+package xyz.kwiecien.jfuck;
+
+import xyz.kwiecien.jfuck.operation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+public class OperationExtractor {
+    public List<Operation> extract(String code) {
+        Operation currentOperation = new Initialization();
+        var operationLists = new Stack<List<Operation>>();
+        operationLists.push(new ArrayList<>());
+
+        for (int currentIndex = 0; currentIndex < code.length(); currentIndex++) {
+            char c = code.charAt(currentIndex);
+            switch (c) {
+                case '+':
+                case '-':
+                    byte valueModification = (byte) (c == '+' ? 1 : -1);
+                    if (currentOperation instanceof ModifyStackValueOperation(byte value)) {
+                        currentOperation = new ModifyStackValueOperation((byte) (value + valueModification));
+                    } else {
+                        if (currentOperation != null) {
+                            operationLists.peek().add(currentOperation);
+                        }
+                        currentOperation = new ModifyStackValueOperation(valueModification);
+                    }
+                    break;
+                case '<':
+                case '>':
+                    int addressModification = c == '>' ? 1 : -1;
+                    if (currentOperation instanceof ModifyPointerOperation(int value)) {
+                        currentOperation = new ModifyPointerOperation(value + addressModification);
+                    } else {
+                        if (currentOperation != null) {
+                            operationLists.peek().add(currentOperation);
+                        }
+                        currentOperation = new ModifyPointerOperation(addressModification);
+                    }
+                    break;
+                case '.':
+                    if (currentOperation != null) {
+                        operationLists.peek().add(currentOperation);
+                    }
+                    currentOperation = new WriteOperation();
+                    break;
+                case ',':
+                    if (currentOperation != null) {
+                        operationLists.peek().add(currentOperation);
+                    }
+                    currentOperation = new ReadOperation();
+                    break;
+                case '[':
+                    if (currentOperation != null) {
+                        operationLists.peek().add(currentOperation);
+                    }
+                    operationLists.push(new ArrayList<>());
+                    currentOperation = null;
+                    break;
+                case ']':
+                    if (currentOperation != null) {
+                        operationLists.peek().add(currentOperation);
+                    }
+                    currentOperation = new Loop(operationLists.pop());
+                    break;
+            }
+        }
+        operationLists.peek().add(currentOperation);
+        return operationLists.pop();
+    }
+}

+ 1 - 1
src/main/java/xyz/kwiecien/jfuck/operation/Initialization.java

@@ -8,7 +8,7 @@ import static java.lang.constant.ConstantDescs.CD_void;
 import static java.lang.constant.ConstantDescs.INIT_NAME;
 import static xyz.kwiecien.jfuck.operation.BytecodeConstants.*;
 
-public class Initialization implements Operation {
+public record Initialization() implements Operation {
     @Override
     public void appendBytecode(CodeBuilder cb) {
         cb.iconst_0().istore(PTR_VAR_INDEX) // Pointer init

+ 1 - 1
src/main/java/xyz/kwiecien/jfuck/operation/ReadOperation.java

@@ -6,7 +6,7 @@ import java.lang.constant.MethodTypeDesc;
 import static java.lang.constant.ConstantDescs.*;
 import static xyz.kwiecien.jfuck.operation.BytecodeConstants.*;
 
-public class ReadOperation implements Operation {
+public record ReadOperation() implements Operation {
     @Override
     public void appendBytecode(CodeBuilder cb) {
         cb.aload(DATA_VAR_INDEX).iload(PTR_VAR_INDEX)

+ 1 - 1
src/main/java/xyz/kwiecien/jfuck/operation/WriteOperation.java

@@ -8,7 +8,7 @@ import static java.lang.constant.ConstantDescs.*;
 import static xyz.kwiecien.jfuck.operation.BytecodeConstants.DATA_VAR_INDEX;
 import static xyz.kwiecien.jfuck.operation.BytecodeConstants.PTR_VAR_INDEX;
 
-public class WriteOperation implements Operation {
+public record WriteOperation() implements Operation {
     @Override
     public void appendBytecode(CodeBuilder cb) {
         cb.getstatic(BytecodeConstants.SYSTEM_DESC, "out", BytecodeConstants.PRINT_STREAM_DESC)

+ 2 - 1
src/test/java/OperationsTest.java

@@ -17,11 +17,12 @@ import static xyz.kwiecien.jfuck.operation.BytecodeConstants.DATA_SIZE_CONSTANT_
 
 public class OperationsTest {
     @Test
-    void testGen() throws IOException {
+    void testAdd() throws IOException {
         Consumer<CodeBuilder> initCode = cb -> cb
                 .aload(0)
                 .invokespecial(CD_Object, INIT_NAME, MTD_void).return_();
 
+        //Full example of add - adding two numbers from stdin
         Consumer<CodeBuilder> addCode = cb -> {
             new Initialization().appendBytecode(cb);
             new ReadOperation().appendBytecode(cb);

+ 117 - 0
src/test/java/xyz/kwiecien/jfuck/OperationExtractorTest.java

@@ -0,0 +1,117 @@
+package xyz.kwiecien.jfuck;
+
+import org.junit.jupiter.api.Test;
+import xyz.kwiecien.jfuck.operation.*;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class OperationExtractorTest {
+    @Test
+    void extractManyValueIncrements() {
+        var code = "+++++";
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ModifyStackValueOperation((byte) 5)
+        ), operations);
+    }
+
+    @Test
+    void extractManyValueDecrements() {
+        var code = "-----";
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ModifyStackValueOperation((byte) -5)
+        ), operations);
+    }
+
+    @Test
+    void extractMixedValueModifications() {
+        var code = "++--+-++"; // 2 more "+" than "-"
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ModifyStackValueOperation((byte) 2)
+        ), operations);
+    }
+
+    @Test
+    void extractManyPointerIncrements() {
+        var code = ">>>>>";
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ModifyPointerOperation((byte) 5)
+        ), operations);
+    }
+
+    @Test
+    void extractManyPointerDecrements() {
+        var code = "<<<<<";
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ModifyPointerOperation((byte) -5)
+        ), operations);
+    }
+
+    @Test
+    void extractMixePointerModifications() {
+        var code = "<<>><><<"; // 2 more "<" than ">"
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ModifyPointerOperation((byte) -2)
+        ), operations);
+    }
+
+    @Test
+    void extractMixedOperationsWithoutLoop() {
+        var code = ",+>-<.";
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ReadOperation(),
+                new ModifyStackValueOperation((byte) 1),
+                new ModifyPointerOperation(1),
+                new ModifyStackValueOperation((byte) -1),
+                new ModifyPointerOperation(-1),
+                new WriteOperation()
+        ), operations);
+    }
+
+    @Test
+    void extractSimpleLoop() {
+        var code = "[,.]";
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new Loop(List.of(
+                        new ReadOperation(),
+                        new WriteOperation()
+                ))
+        ), operations);
+    }
+
+    @Test
+    void extractEmbeddedLoops() {
+        var code = ",[,[,.].].";
+        var operations = new OperationExtractor().extract(code);
+        assertEquals(List.of(
+                new Initialization(),
+                new ReadOperation(),
+                new Loop(List.of(
+                        new ReadOperation(),
+                        new Loop(List.of(
+                                new ReadOperation(),
+                                new WriteOperation()
+                        )),
+                        new WriteOperation()
+                )),
+                new WriteOperation()
+        ), operations);
+    }
+}

+ 1 - 0
test.bf

@@ -0,0 +1 @@
+,+.