Browse Source

Initial commit - part of the operations implemented in bytecode

boono 11 tháng trước cách đây
commit
3d43c24393

+ 39 - 0
.gitignore

@@ -0,0 +1,39 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store

+ 1 - 0
add.bf

@@ -0,0 +1 @@
+,>++++++[<-------->-],[<+>-]<.

+ 17 - 0
hw.bf

@@ -0,0 +1,17 @@
+++++++++++
+[
+>+++++++>++++++++++>+++>+<<<<-
+] Na początek ustawiamy kilka przydatnych później wartości
+>++.               drukuje 'H'
+>+.                drukuje 'e'
++++++++.           drukuje 'l'
+.                  drukuje 'l'
++++.               drukuje 'o'
+>++.               spacja
+<<+++++++++++++++. drukuje 'W'
+>.                 drukuje 'o'
++++.               drukuje 'r'
+------.            drukuje 'l'
+--------.          drukuje 'd'
+>+.                drukuje '!'
+>.                 nowa linia

+ 3 - 0
multiply.bf

@@ -0,0 +1,3 @@
+,>,>++++++++[<------<------>>-]
+<<[>[>+>+<<-]>>[<<+>>-]<<<-]
+>>>++++++[<++++++++>-]<.

+ 42 - 0
pom.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>xyz.kwiecien.experiments</groupId>
+    <artifactId>jfuck</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>24</maven.compiler.source>
+        <maven.compiler.target>24</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>5.11.3</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.13.0</version>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                    <compilerArgs>
+                        <arg>--enable-preview</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 78 - 0
src/main/java/xyz/kwiecien/jfuck/BFMain.java

@@ -0,0 +1,78 @@
+package xyz.kwiecien.jfuck;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Scanner;
+import java.util.Stack;
+
+public class BFMain {
+    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 array = new byte[30000];
+        var ptr = 0;
+        var loopStartIndex = new Stack<Integer>();
+        var scanner = new Scanner(System.in);
+
+        for (int i = 0; i < code.length(); i++) {
+            var c = code.charAt(i);
+            switch (c) {
+                case '>':
+                    ptr++;
+                    if (ptr == array.length) {
+                        ptr = 0;
+                    }
+                    break;
+                case '<':
+                    ptr--;
+                    if (ptr == -1) {
+                        ptr = array.length - 1;
+                    }
+                    break;
+                case '+':
+                    array[ptr]++;
+                    break;
+                case '-':
+                    array[ptr]--;
+                    break;
+                case '.':
+                    System.out.print(new String(new byte[] {array[ptr]}));
+                    break;
+                case ',':
+                    array[ptr] = (byte) scanner.next().charAt(0);
+                    break;
+                case ']':
+                    i = loopStartIndex.pop();
+                    // pass-through to the '[' handling on purpose
+                case '[':
+                    if (array[ptr] > 0) {
+                        loopStartIndex.push(i);
+                        continue;
+                    }
+                    int nestingLevel = 0;
+                    while (true) {
+                        i++;
+                        char nextChar = code.charAt(i);
+                        if (nextChar == '[') {
+                            nestingLevel++;
+                        } else if (nextChar == ']' && nestingLevel == 0) {
+                            break;
+                        } else if (nextChar == ']') {
+                            nestingLevel--;
+                        }
+                    }
+                    break;
+            }
+        }
+
+    }
+}

+ 40 - 0
src/main/java/xyz/kwiecien/jfuck/SimpleReturnGenerator.java

@@ -0,0 +1,40 @@
+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.util.function.Consumer;
+
+import static java.lang.constant.ConstantDescs.*;
+
+public class SimpleReturnGenerator {
+    public static void main(String[] args) throws IOException {
+        Consumer<CodeBuilder> initCode = cb -> cb
+                .aload(0)
+                .invokespecial(CD_Object, INIT_NAME, MTD_void).return_();
+        Consumer<CodeBuilder> mainCode = cb -> cb
+                .iconst_5()
+                .istore(1)
+                .iload(1)
+                .invokestatic(ClassDesc.of("java.lang", "System"), "exit", MethodTypeDesc.of(CD_void, CD_int))
+                .return_();
+
+        var bytes = ClassFile.of().build(ClassDesc.of("GenClass"), clb ->
+                clb.withFlags(ClassFile.ACC_PUBLIC)
+                        .withMethod(
+                                INIT_NAME,
+                                MTD_void,
+                                ClassFile.ACC_PUBLIC,
+                                mb -> mb.withCode(initCode))
+                        .withMethod(
+                                "main",
+                                MethodTypeDesc.of(CD_void, CD_String.arrayType()),
+                                ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
+                                mb -> mb.withCode(mainCode)));
+        Files.write(Path.of("/tmp/GenClass.class"), bytes);
+    }
+}

+ 8 - 0
src/main/java/xyz/kwiecien/jfuck/TestMain.java

@@ -0,0 +1,8 @@
+package xyz.kwiecien.jfuck;
+
+public class TestMain {
+    public static void main(String[] args) {
+        int result = 0;
+        System.exit(result);
+    }
+}

+ 6 - 0
src/main/java/xyz/kwiecien/jfuck/operation/BytecodeConstants.java

@@ -0,0 +1,6 @@
+package xyz.kwiecien.jfuck.operation;
+
+public interface BytecodeConstants {
+    int PTR_VAR_INDEX = 1;
+    int DATA_VAR_INDEX = 2;
+}

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

@@ -0,0 +1,17 @@
+package xyz.kwiecien.jfuck.operation;
+
+import java.lang.classfile.CodeBuilder;
+import java.lang.classfile.TypeKind;
+import java.util.function.Consumer;
+
+import static xyz.kwiecien.jfuck.operation.BytecodeConstants.DATA_VAR_INDEX;
+import static xyz.kwiecien.jfuck.operation.BytecodeConstants.PTR_VAR_INDEX;
+
+public class Initialization implements Operation {
+    @Override
+    public Consumer<CodeBuilder> appendBytecode() {
+        return c -> c
+                .iconst_0().istore(PTR_VAR_INDEX) // Pointer init
+                .sipush(30000).newarray(TypeKind.BYTE).astore(DATA_VAR_INDEX); // Initialize array of 30k bytes
+    }
+}

+ 23 - 0
src/main/java/xyz/kwiecien/jfuck/operation/Loop.java

@@ -0,0 +1,23 @@
+package xyz.kwiecien.jfuck.operation;
+
+import java.lang.classfile.CodeBuilder;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static xyz.kwiecien.jfuck.operation.BytecodeConstants.DATA_VAR_INDEX;
+import static xyz.kwiecien.jfuck.operation.BytecodeConstants.PTR_VAR_INDEX;
+
+public record Loop(List<Operation> innerOperations) implements Operation {
+    @Override
+    public Consumer<CodeBuilder> appendBytecode() {
+        return c -> c.block(bc -> {
+                    bc.aload(DATA_VAR_INDEX).iload(PTR_VAR_INDEX).baload() //Get data from pointer
+                            .ifeq(bc.endLabel()); //Jump out of loop if data == 0
+                    for (var innerOperation : innerOperations) {
+                        innerOperation.appendBytecode().accept(bc);
+                    }
+                    bc.goto_(bc.startLabel());
+                }
+        );
+    }
+}

+ 11 - 0
src/main/java/xyz/kwiecien/jfuck/operation/ModifyPointerOperation.java

@@ -0,0 +1,11 @@
+package xyz.kwiecien.jfuck.operation;
+
+import java.lang.classfile.CodeBuilder;
+import java.util.function.Consumer;
+
+public record ModifyPointerOperation(int value) implements Operation {
+    @Override
+    public Consumer<CodeBuilder> appendBytecode() {
+        return c -> {};
+    }
+}

+ 19 - 0
src/main/java/xyz/kwiecien/jfuck/operation/ModifyStackValueOperation.java

@@ -0,0 +1,19 @@
+package xyz.kwiecien.jfuck.operation;
+
+import java.lang.classfile.CodeBuilder;
+import java.util.function.Consumer;
+
+import static xyz.kwiecien.jfuck.operation.BytecodeConstants.DATA_VAR_INDEX;
+import static xyz.kwiecien.jfuck.operation.BytecodeConstants.PTR_VAR_INDEX;
+
+public record ModifyStackValueOperation(byte value) implements Operation {
+    @Override
+    public Consumer<CodeBuilder> appendBytecode() {
+        return c -> c.aload(DATA_VAR_INDEX).iload(PTR_VAR_INDEX).dup2() // dup2, because will be used to read value and then store incremented
+                .baload()
+                .bipush(value)
+                .iadd()
+                .i2b()
+                .bastore();
+    }
+}

+ 8 - 0
src/main/java/xyz/kwiecien/jfuck/operation/Operation.java

@@ -0,0 +1,8 @@
+package xyz.kwiecien.jfuck.operation;
+
+import java.lang.classfile.CodeBuilder;
+import java.util.function.Consumer;
+
+public interface Operation {
+    Consumer<CodeBuilder> appendBytecode();
+}

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

@@ -0,0 +1,11 @@
+package xyz.kwiecien.jfuck.operation;
+
+import java.lang.classfile.CodeBuilder;
+import java.util.function.Consumer;
+
+public class ReadOperation implements Operation {
+    @Override
+    public Consumer<CodeBuilder> appendBytecode() {
+        return c -> {};
+    }
+}

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

@@ -0,0 +1,30 @@
+package xyz.kwiecien.jfuck.operation;
+
+import java.lang.classfile.CodeBuilder;
+import java.lang.classfile.TypeKind;
+import java.lang.classfile.constantpool.MethodRefEntry;
+import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDescs;
+import java.lang.constant.MethodTypeDesc;
+import java.util.function.Consumer;
+
+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 {
+    private static final ClassDesc SYSTEM_DESC = ClassDesc.of("java.lang.System");
+    private static final ClassDesc PRINT_STREAM_DESC = ClassDesc.of("java.io.PrintStream");
+    private static final ClassDesc STRING_CLASS_DESC = ClassDesc.of("java.lang.String");
+    @Override
+    public Consumer<CodeBuilder> appendBytecode() {
+        return c -> c.getstatic(SYSTEM_DESC, "out", PRINT_STREAM_DESC)
+                .new_(STRING_CLASS_DESC).dup() // dup, because we will be initializing it and then using as an argument for print
+                .iconst_1().newarray(TypeKind.BYTE).dup() // dup, because we will be filling it with data and then passing to String.<init>
+                .iconst_0() // index of byte array to set
+                .aload(DATA_VAR_INDEX).iload(PTR_VAR_INDEX).baload() //Load data at pointer
+                .bastore() // store loaded data at index zero
+                .invokespecial(STRING_CLASS_DESC, INIT_NAME, MethodTypeDesc.of(CD_void, CD_byte.arrayType()))
+                .invokevirtual(PRINT_STREAM_DESC, "print", MethodTypeDesc.of(CD_void, CD_String));
+    }
+}

+ 45 - 0
src/test/java/OperationsTest.java

@@ -0,0 +1,45 @@
+import org.junit.jupiter.api.Test;
+import xyz.kwiecien.jfuck.operation.Initialization;
+import xyz.kwiecien.jfuck.operation.ModifyStackValueOperation;
+import xyz.kwiecien.jfuck.operation.WriteOperation;
+
+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.util.function.Consumer;
+
+import static java.lang.constant.ConstantDescs.*;
+
+public class OperationsTest {
+    @Test
+    void testGen() throws IOException {
+        Consumer<CodeBuilder> initCode = cb -> cb
+                .aload(0)
+                .invokespecial(CD_Object, INIT_NAME, MTD_void).return_();
+
+                Consumer<CodeBuilder> mainCode = cb -> {
+                    new Initialization().appendBytecode().accept(cb);
+                    new ModifyStackValueOperation((byte) 0x30).appendBytecode().accept(cb);
+                    new WriteOperation().appendBytecode().accept(cb);
+                    cb.return_();
+                };
+
+        var bytes = ClassFile.of().build(ClassDesc.of("GenClass"), clb ->
+                clb.withFlags(ClassFile.ACC_PUBLIC)
+                        .withMethod(
+                                INIT_NAME,
+                                MTD_void,
+                                ClassFile.ACC_PUBLIC,
+                                mb -> mb.withCode(initCode))
+                        .withMethod(
+                                "main",
+                                MethodTypeDesc.of(CD_void, CD_String.arrayType()),
+                                ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
+                                mb -> mb.withCode(mainCode)));
+        Files.write(Path.of("/tmp/GenClass.class"), bytes);
+    }
+}