commit 6600d02c489f23bf0805f45b3a53aae5018153cd Author: haskal Date: Sat Jul 31 17:29:12 2021 -0400 floppppp 🐉💾 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1044f57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.byte +*.class +*.cmi +*.cmo diff --git a/Jocaml.java b/Jocaml.java new file mode 100644 index 0000000..bbc6db7 --- /dev/null +++ b/Jocaml.java @@ -0,0 +1,299 @@ +import java.io.File; +import java.io.FileInputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Jocaml { + private static String TRAILER_MAGIC = "Caml1999X028"; + private static long MARSHAL_MAGIC_SMALL = 0x8495A6BE; + private static long MARSHAL_MAGIC_BIG = 0x8495A6BF; + + private static class CodeOffset { + public final int offset; + + public CodeOffset(int offset) { + this.offset = offset; + } + + public String toString() { + return "CodeOffset<" + offset + ">"; + } + + public boolean equals(Object other) { + return other instanceof CodeOffset && ((CodeOffset) other).offset == this.offset; + } + + public int hashCode() { + return offset * 13; + } + } + + private static class Block { + public final int tag; + public final List contents; + + public Block(int tag, List contents) { + this.tag = tag; + this.contents = contents; + } + + public String toString() { + return "Block<" + tag + ": " + contents.toString() + ">"; + } + } + + private static class Section { + String name; + ByteBuffer data; + + public Section(String name, ByteBuffer data) { + this.name = name; + this.data = data; + } + + public String toString() { + return "Section"; + } + + public List unstring() { + List strings = new ArrayList<>(); + for (int i = 0; i < this.data.capacity();) { + StringBuffer buf = new StringBuffer(); + while (this.data.get(i) != 0) { + buf.append((char) this.data.get(i)); + i += 1; + } + strings.add(buf.toString()); + i += 1; + } + return strings; + } + + private Object parseNextObject(List shared) throws Exception { + int nextTag = this.data.get() & (short) 0xff; + switch (nextTag) { + // integers + case 0x00: { + byte value = this.data.get(); + return Long.valueOf(value); + } + case 0x01: { + short value = this.data.getShort(); + return Long.valueOf(value); + } + case 0x02: { + int value = this.data.getInt(); + return Long.valueOf(value); + } + case 0x03: { + long value = this.data.getLong(); + return Long.valueOf(value); + } + + // shared elements + case 0x04: { + long offset = this.data.get() & (long) 0xff; + return shared.get((int) offset); + } + case 0x05: { + long offset = this.data.getShort() & (long) 0xff; + return shared.get((int) offset); + } + case 0x06: { + long offset = this.data.getInt() & (long) 0xff; + return shared.get((int) offset); + } + + // blocks + case 0x08: { + long header = this.data.getInt() & (long) 0xff; + int tag = (int) header & 0xff; + long size = header >> 10; + List elems = new ArrayList<>(); + Block block = new Block(tag, elems); + if (size > 0) { + shared.add(block); + for (int i = 0; i < size; i++) { + elems.add(parseNextObject(shared)); + } + } + return block; + } + case 0x13: { + throw new Exception("long block not supported"); + } + + // string + case 0x09: { + int len = this.data.get() & (short) 0xff; + byte[] buf = new byte[len]; + this.data.get(buf); + String s = new String(buf); + shared.add(s); + return s; + } + case 0x0a: { + long len = this.data.getInt() & (long) 0xff; + byte[] buf = new byte[(int)len]; + this.data.get(buf); + String s = new String(buf); + shared.add(s); + return s; + } + + // float + case 0x0b: + case 0x0c: { + Double obj = Double.valueOf(this.data.getDouble()); + shared.add(obj); + return obj; + } + case 0x0D: + case 0x0E: { + throw new Exception("double array not supported"); + } + case 0x07: + case 0x0f: { + throw new Exception("double array not supported"); + } + + // misc + case 0x10: { + throw new Exception("code pointer with checksum"); + } + case 0x11: { + throw new Exception("code pointer with closure"); + } + case 0x12: + case 0x18: + case 0x19: { + StringBuffer buf = new StringBuffer(); + while (true) { + int next = this.data.get() & (short) 0xff; + if (next == 0) { + break; + } + buf.append((char) next); + } + String name = buf.toString(); + switch (name) { + // int64 + case "_j": { + long v = Long.valueOf(this.data.getLong()); + shared.add(v); + return v; + } + default: + throw new Exception("unknown custom op: " + name); + } + } + default: { + if (nextTag < 0x20) { + throw new Exception("invalid tag " + nextTag); + } else if (nextTag < 0x40) { + int length = nextTag & 0x1f; + byte[] buf = new byte[(int)length]; + this.data.get(buf); + String s = new String(buf); + shared.add(s); + return s; + } else if (nextTag < 0x80) { + Long value = Long.valueOf(nextTag & 0x3f); + return value; + } else { + long size = (nextTag >> 4) & 0x07; + int tag = nextTag & 0x0f; + List elems = new ArrayList<>(); + Block block = new Block(tag, elems); + if (size > 0) { + shared.add(block); + for (int i = 0; i < size; i++) { + elems.add(parseNextObject(shared)); + } + } + return block; + } + } + } + } + + public void unmarshal() throws Exception { + int magic = this.data.getInt(0); + if (magic != MARSHAL_MAGIC_SMALL) { + throw new Exception("bad marshal magic"); + } + + int byteLength = this.data.getInt(4); + int numObjs = this.data.getInt(8); + System.out.println(numObjs); + + List shared = new ArrayList<>(); + List objs = new ArrayList<>(); + + this.data.position(20); + + while (this.data.position() < this.data.capacity()) { + Object next = parseNextObject(shared); + objs.add(next); + } + + System.out.println(shared.size()); + + System.out.println(objs); + } + } + + public static Section findSection(Section[] sections, String name) { + for (Section section : sections) { + if (section.name.equals(name)) { + return section; + } + } + return null; + } + + public static void main(String[] args) throws Exception { + ByteBuffer buf; + int len; + { + FileInputStream in = new FileInputStream(new File(args[0])); + len = in.available(); + buf = ByteBuffer.allocate(len); + Channels.newChannel(in).read(buf); + } + + for (int i = 0; i < 12; i++) { + if (buf.get(len - 12 + i) != (byte) TRAILER_MAGIC.charAt(i)) { + throw new Exception("invalid trailer"); + } + } + + int numSections = buf.getInt(len - 16); + Section[] sections = new Section[numSections]; + + for (int i = numSections - 1, sectionBase = len - 16 - (numSections * 8); i >= 0; i--) { + int base = len - 16 - (numSections * 8) + (i * 8); + char[] name = new char[4]; + name[0] = (char) buf.get(base + 0); + name[1] = (char) buf.get(base + 1); + name[2] = (char) buf.get(base + 2); + name[3] = (char) buf.get(base + 3); + int sectionLength = buf.getInt(base + 4); + sectionBase -= sectionLength; + byte[] data = new byte[sectionLength]; + buf.position(sectionBase); + buf.get(data); + sections[i] = new Section(new String(name), ByteBuffer.wrap(data)); + } + + System.out.println(Arrays.toString(sections)); + findSection(sections, "SYMB").unmarshal(); + findSection(sections, "CRCS").unmarshal(); + findSection(sections, "DATA").unmarshal(); + } +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..57f4925 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: all run clean + +all: Jocaml.class + +run: Jocaml.class test.byte + java Jocaml test.byte + +clean: + $(RM) *.class *.cmi *.byte *.cmo + +test.byte: test.ml + ocamlfind ocamlc -linkpkg -o test.byte test.ml + +%.class: %.java + javac $< diff --git a/test.ml b/test.ml new file mode 100644 index 0000000..7a375d9 --- /dev/null +++ b/test.ml @@ -0,0 +1,10 @@ +Printf.printf "hello world!\n" + +let rec fac (n : int) : int = + if n <= 1 then + 1 + else + fac (n-1) * n;; + + +Printf.printf "fac 6 is %d\n" (fac 6)