From 0cac6379da17a56b072cddaad1b9d80d854af3eb Mon Sep 17 00:00:00 2001 From: Filip Krikava Date: Sat, 13 Jan 2024 22:03:34 -0500 Subject: [PATCH 01/10] WIP: RDSReader and BC compiler --- .neoconf.json | 13 + src/main/java/Main.java | 33 ++ src/main/java/nrc/RPlatform.java | 10 + src/main/java/nrc/bc/Compiler.java | 200 +++++++ src/main/java/nrc/bc/CompilerException.java | 7 + src/main/java/nrc/bc/OpCodes.java | 133 +++++ src/main/java/nrc/rds/RDSException.java | 12 + src/main/java/nrc/rds/RDSReader.java | 552 ++++++++++++++++++ src/main/java/nrc/sexp/AbstractSXP.java | 22 + src/main/java/nrc/sexp/Attributes.java | 54 ++ src/main/java/nrc/sexp/BCodeSXP.java | 24 + src/main/java/nrc/sexp/CloSXP.java | 61 ++ src/main/java/nrc/sexp/EnvSXP.java | 15 + src/main/java/nrc/sexp/IntSXP.java | 9 + src/main/java/nrc/sexp/LangSXP.java | 62 ++ src/main/java/nrc/sexp/LglSXP.java | 9 + src/main/java/nrc/sexp/ListSXP.java | 24 + src/main/java/nrc/sexp/NilSXP.java | 18 + src/main/java/nrc/sexp/RealSXP.java | 9 + src/main/java/nrc/sexp/SEXP.java | 13 + src/main/java/nrc/sexp/SEXPConsts.java | 42 ++ src/main/java/nrc/sexp/SEXPTypes.java | 95 +++ src/main/java/nrc/sexp/StrSXP.java | 9 + src/main/java/nrc/sexp/SymSXP.java | 41 ++ src/main/java/nrc/sexp/VecSXP.java | 9 + src/main/java/nrc/sexp/VectorSXP.java | 67 +++ src/main/java/nrc/util/Either.java | 11 + src/main/java/nrc/util/IO.java | 28 + src/main/java/nrc/util/Left.java | 4 + src/main/java/nrc/util/Pair.java | 7 + src/main/java/nrc/util/Right.java | 4 + src/test/java/nrc/bc/CompilerTest.java | 11 + src/test/java/nrc/rds/RDSReaderTest.java | 91 +++ src/test/resources/nrc/bc/f1.rds | Bin 0 -> 463 bytes src/test/resources/nrc/bc/f1c.rds | Bin 0 -> 833 bytes src/test/resources/nrc/rds/closure-bc.rds | Bin 0 -> 1165 bytes src/test/resources/nrc/rds/closure-bc2.rds | Bin 0 -> 2045 bytes .../nrc/rds/closure-install.packages.rds | Bin 0 -> 206042 bytes src/test/resources/nrc/rds/closure.rds | Bin 0 -> 512 bytes src/test/resources/nrc/rds/ints.rds | Bin 0 -> 55 bytes src/test/resources/nrc/rds/lgls.rds | Bin 0 -> 43 bytes src/test/resources/nrc/rds/list.rds | Bin 0 -> 69 bytes src/test/resources/nrc/rds/reals.rds | Bin 0 -> 79 bytes 43 files changed, 1699 insertions(+) create mode 100644 .neoconf.json create mode 100644 src/main/java/Main.java create mode 100644 src/main/java/nrc/RPlatform.java create mode 100644 src/main/java/nrc/bc/Compiler.java create mode 100644 src/main/java/nrc/bc/CompilerException.java create mode 100644 src/main/java/nrc/bc/OpCodes.java create mode 100644 src/main/java/nrc/rds/RDSException.java create mode 100644 src/main/java/nrc/rds/RDSReader.java create mode 100644 src/main/java/nrc/sexp/AbstractSXP.java create mode 100644 src/main/java/nrc/sexp/Attributes.java create mode 100644 src/main/java/nrc/sexp/BCodeSXP.java create mode 100644 src/main/java/nrc/sexp/CloSXP.java create mode 100644 src/main/java/nrc/sexp/EnvSXP.java create mode 100644 src/main/java/nrc/sexp/IntSXP.java create mode 100644 src/main/java/nrc/sexp/LangSXP.java create mode 100644 src/main/java/nrc/sexp/LglSXP.java create mode 100644 src/main/java/nrc/sexp/ListSXP.java create mode 100644 src/main/java/nrc/sexp/NilSXP.java create mode 100644 src/main/java/nrc/sexp/RealSXP.java create mode 100644 src/main/java/nrc/sexp/SEXP.java create mode 100644 src/main/java/nrc/sexp/SEXPConsts.java create mode 100644 src/main/java/nrc/sexp/SEXPTypes.java create mode 100644 src/main/java/nrc/sexp/StrSXP.java create mode 100644 src/main/java/nrc/sexp/SymSXP.java create mode 100644 src/main/java/nrc/sexp/VecSXP.java create mode 100644 src/main/java/nrc/sexp/VectorSXP.java create mode 100644 src/main/java/nrc/util/Either.java create mode 100644 src/main/java/nrc/util/IO.java create mode 100644 src/main/java/nrc/util/Left.java create mode 100644 src/main/java/nrc/util/Pair.java create mode 100644 src/main/java/nrc/util/Right.java create mode 100644 src/test/java/nrc/bc/CompilerTest.java create mode 100644 src/test/java/nrc/rds/RDSReaderTest.java create mode 100644 src/test/resources/nrc/bc/f1.rds create mode 100644 src/test/resources/nrc/bc/f1c.rds create mode 100644 src/test/resources/nrc/rds/closure-bc.rds create mode 100644 src/test/resources/nrc/rds/closure-bc2.rds create mode 100644 src/test/resources/nrc/rds/closure-install.packages.rds create mode 100644 src/test/resources/nrc/rds/closure.rds create mode 100644 src/test/resources/nrc/rds/ints.rds create mode 100644 src/test/resources/nrc/rds/lgls.rds create mode 100644 src/test/resources/nrc/rds/list.rds create mode 100644 src/test/resources/nrc/rds/reals.rds diff --git a/.neoconf.json b/.neoconf.json new file mode 100644 index 000000000..ad08870f4 --- /dev/null +++ b/.neoconf.json @@ -0,0 +1,13 @@ +{ + "lspconfig": { + "jdtls": { + "java.configuration.runtimes": [ + { + "name": "JavaSE-21", + "path": "/home/krikava/.asdf/installs/java/openjdk-21", + "default": true + } + ] + } + } +} diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 000000000..b4daf9226 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,33 @@ +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import nrc.rds.RDSReader; +import nrc.sexp.SEXP; +import nrc.util.IO; + +public class Main { + + public static void main(String[] args) throws Exception { + new Main().doMain(args); + } + + private void doMain(String[] args) throws Exception { + compile(args[0]); + } + + private void compile(String file) throws IOException { + try (var input = new FileInputStream(file)) { + compile(input); + } + } + + private void compile(InputStream input) throws IOException { + var reader = new RDSReader(IO.maybeDecompress(input)); + compile(reader.read()); + } + + private void compile(SEXP sexp) { + System.out.println(sexp); + } +} \ No newline at end of file diff --git a/src/main/java/nrc/RPlatform.java b/src/main/java/nrc/RPlatform.java new file mode 100644 index 000000000..e90957f5f --- /dev/null +++ b/src/main/java/nrc/RPlatform.java @@ -0,0 +1,10 @@ +package nrc; + +public final class RPlatform { + public static final int INT_MAX = ~(1 << 31); + + private RPlatform() { + } + + public static final int BC_VERSION = 12; +} diff --git a/src/main/java/nrc/bc/Compiler.java b/src/main/java/nrc/bc/Compiler.java new file mode 100644 index 000000000..b0d29cb4e --- /dev/null +++ b/src/main/java/nrc/bc/Compiler.java @@ -0,0 +1,200 @@ +package nrc.bc; + +import nrc.RPlatform; +import nrc.sexp.*; +import nrc.util.Left; +import nrc.util.Right; + +import java.util.*; + +class CodeBuffer { + private final List code = new ArrayList<>(); + private final Map consts = new HashMap<>(); + + public int putConst(SEXP c) { + return consts.computeIfAbsent(c, (ignored) -> consts.size()); + } + + public void putCode(int op) { + code.add(op); + } + + public void putCode(int op, int... args) { + code.add(op); + for (var arg : args) { + code.add(arg); + } + } + + public BCodeSXP toBCSXP() { + var codeCopy = new ArrayList<>(code); + codeCopy.addFirst(RPlatform.BC_VERSION); + + var constsCopy = new ArrayList(consts.size()); + for (var e : consts.entrySet()) { + constsCopy.set(e.getValue(), e.getKey()); + } + + return new BCodeSXP(codeCopy, constsCopy); + } +} + +record Envir(EnvSXP env) { + +} + +record Context(boolean topLevel, boolean tailCall, Envir cenv) { + Context makePromisecContext() { + var copy = new Context(false, true, cenv); + return copy; + } +} + +public class Compiler implements OpCodes, SEXPConsts { + private static final Set MAYBE_NSE_SYMBOLS = Set.of("bquote"); + + private static final CodeBuffer cb = new CodeBuffer(); + + public SEXP compile(SEXP expr, EnvSXP env) { + var ctx = new Context(true, true, new Envir(env)); + // TODO: add local variables into the environment + compileExpr(expr, ctx); + + return cb.toBCSXP(); + } + + private void compileExpr(SEXP expr, Context ctx) { + // TODO: check we do not attempt to compiler BCSXP or PROMSXP + switch (expr) { + case LangSXP e -> compileCall(e, ctx); + case SymSXP e -> compileSym(e, ctx); + default -> compileConst(expr, ctx); + } + } + + private Object compileSym(SymSXP e, Context ctx) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'compileSym'"); + } + + private void compileConst(SEXP expr, Context ctx) { + if (expr.getType() == SEXPTypes.PROMSXP || expr.getType() == SEXPTypes.BCODESXP) { + throw new CompilerException("Unexpected type: " + expr.getType()); + } + + switch (expr) { + case NilSXP x -> cb.putCode(OP_LDNULL); + case LglSXP x when x == TRUE -> cb.putCode(OP_LDTRUE); + case LglSXP x when x == FALSE -> cb.putCode(OP_LDFALSE); + default -> { + var idx = cb.putConst(expr); + cb.putCode(OP_LDCONST, idx); + } + } + + checkTailCall(ctx); + } + + void compileCall(LangSXP call, Context ctx) { + var args = call.getArgs(); + switch (call.getFun()) { + case Left(var fun) -> compileCallSymFun(fun, args, call, ctx); + case Right(var fun) -> compileCallExprFun(fun, args, ctx); + } + } + + private void compileCallExprFun(LangSXP fun, ListSXP args, Context ctx) { + } + + private void compileCallSymFun(SymSXP fun, ListSXP args, LangSXP call, Context ctx) { + var idx = cb.putConst(fun); + cb.putCode(OP_GETFUN, idx); + var nse = MAYBE_NSE_SYMBOLS.contains(fun.getName()); + compileArgs(args, nse, ctx); + idx = cb.putConst(call); + cb.putCode(OP_CALL, idx); + checkTailCall(ctx); + } + + private void compileArgs(ListSXP args, boolean nse, Context ctx) { + for (var arg : args) { + var tag = arg.a(); + var val = arg.b(); + + switch (val) { + case SymSXP x when x == ELIPSIS -> throw new UnsupportedOperationException(); + case SymSXP x when x == MISSING_ARG -> { + cb.putCode(OP_DOMISSING); + compileTag(tag, ctx); + } + case SymSXP x -> { + compileNormArg(x, nse, ctx); + compileTag(tag, ctx); + } + case LangSXP x -> { + compileNormArg(x, nse, ctx); + compileTag(tag, ctx); + } + default -> { + compileConstArg(val, ctx); + compileTag(tag, ctx); + } + } + } + } + + private void compileConstArg(SEXP arg, Context ctx) { + switch (arg) { + case NilSXP x -> cb.putCode(OP_PUSHNULLARG); + case LglSXP x when x == TRUE -> cb.putCode(OP_PUSHTRUEARG); + case LglSXP x when x == FALSE -> cb.putCode(OP_PUSHFALSEARG); + default -> { + var idx = cb.putConst(arg); + cb.putCode(OP_PUSHCONSTARG, idx); + } + } + } + + private void compileNormArg(SEXP arg, boolean nse, Context ctx) { + var idx = 0; + if (nse) { + idx = cb.putConst(arg); + } else { + var code = new Compiler().compile(arg, makePromiseContext(ctx)); + idx = cb.putConst(code); + } + cb.putCode(OP_MAKEPROM, idx); + + } + + private SEXP compile(SEXP expr, Context ctx) { + compileExpr(expr, ctx); + return cb.toBCSXP(); + } + + private Context makePromiseContext(Context ctx) { + /* + * cntxt$needRETURNJMP <- TRUE + * if (! is.null(cntxt$loop)) + * cntxt$loop$gotoOK <- FALSE + */ + return new Context(false, true, ctx.cenv()); + } + + private void compileTag(Optional tag, Context ctx) { + } + + private void checkTailCall(Context ctx) { + if (ctx.tailCall()) { + cb.putCode(OP_RETURN); + } + } + + public SEXP compileFun(CloSXP source) { + var body = source.getBody(); + + // FIXME: this is wrong -- need a special cmpFun to include the formals and others + var bc = compile(body, new EnvSXP()); + return bc; + } +} diff --git a/src/main/java/nrc/bc/CompilerException.java b/src/main/java/nrc/bc/CompilerException.java new file mode 100644 index 000000000..e318c52c5 --- /dev/null +++ b/src/main/java/nrc/bc/CompilerException.java @@ -0,0 +1,7 @@ +package nrc.bc; + +public class CompilerException extends RuntimeException { + public CompilerException(String message) { + super(message); + } +} diff --git a/src/main/java/nrc/bc/OpCodes.java b/src/main/java/nrc/bc/OpCodes.java new file mode 100644 index 000000000..045428e9c --- /dev/null +++ b/src/main/java/nrc/bc/OpCodes.java @@ -0,0 +1,133 @@ +package nrc.bc; + +public interface OpCodes { + public static final int OP_BCMISMATCH = 0; + public static final int OP_RETURN = 1; + public static final int OP_GOTO = 2; + public static final int OP_BRIFNOT = 3; + public static final int OP_POP = 4; + public static final int OP_DUP = 5; + public static final int OP_PRINTVALUE = 6; + public static final int OP_STARTLOOPCNTXT = 7; + public static final int OP_ENDLOOPCNTXT = 8; + public static final int OP_DOLOOPNEXT = 9; + public static final int OP_DOLOOPBREAK = 10; + public static final int OP_STARTFOR = 11; + public static final int OP_STEPFOR = 12; + public static final int OP_ENDFOR = 13; + public static final int OP_SETLOOPVAL = 14; + public static final int OP_INVISIBLE = 15; + public static final int OP_LDCONST = 16; + public static final int OP_LDNULL = 17; + public static final int OP_LDTRUE = 18; + public static final int OP_LDFALSE = 19; + public static final int OP_GETVAR = 20; + public static final int OP_DDVAL = 21; + public static final int OP_SETVAR = 22; + public static final int OP_GETFUN = 23; + public static final int OP_GETGLOBFUN = 24; + public static final int OP_GETSYMFUN = 25; + public static final int OP_GETBUILTIN = 26; + public static final int OP_GETINTLBUILTIN = 27; + public static final int OP_CHECKFUN = 28; + public static final int OP_MAKEPROM = 29; + public static final int OP_DOMISSING = 30; + public static final int OP_SETTAG = 31; + public static final int OP_DODOTS = 32; + public static final int OP_PUSHARG = 33; + public static final int OP_PUSHCONSTARG = 34; + public static final int OP_PUSHNULLARG = 35; + public static final int OP_PUSHTRUEARG = 36; + public static final int OP_PUSHFALSEARG = 37; + public static final int OP_CALL = 38; + public static final int OP_CALLBUILTIN = 39; + public static final int OP_CALLSPECIAL = 40; + public static final int OP_MAKECLOSURE = 41; + public static final int OP_UMINUS = 42; + public static final int OP_UPLUS = 43; + public static final int OP_ADD = 44; + public static final int OP_SUB = 45; + public static final int OP_MUL = 46; + public static final int OP_DIV = 47; + public static final int OP_EXPT = 48; + public static final int OP_SQRT = 49; + public static final int OP_EXP = 50; + public static final int OP_EQ = 51; + public static final int OP_NE = 52; + public static final int OP_LT = 53; + public static final int OP_LE = 54; + public static final int OP_GE = 55; + public static final int OP_GT = 56; + public static final int OP_AND = 57; + public static final int OP_OR = 58; + public static final int OP_NOT = 59; + public static final int OP_DOTSERR = 60; + public static final int OP_STARTASSIGN = 61; + public static final int OP_ENDASSIGN = 62; + public static final int OP_STARTSUBSET = 63; + public static final int OP_DFLTSUBSET = 64; + public static final int OP_STARTSUBASSIGN = 65; + public static final int OP_DFLTSUBASSIGN = 66; + public static final int OP_STARTC = 67; + public static final int OP_DFLTC = 68; + public static final int OP_STARTSUBSET2 = 69; + public static final int OP_DFLTSUBSET2 = 70; + public static final int OP_STARTSUBASSIGN2 = 71; + public static final int OP_DFLTSUBASSIGN2 = 72; + public static final int OP_DOLLAR = 73; + public static final int OP_DOLLARGETS = 74; + public static final int OP_ISNULL = 75; + public static final int OP_ISLOGICAL = 76; + public static final int OP_ISINTEGER = 77; + public static final int OP_ISDOUBLE = 78; + public static final int OP_ISCOMPLEX = 79; + public static final int OP_ISCHARACTER = 80; + public static final int OP_ISSYMBOL = 81; + public static final int OP_ISOBJECT = 82; + public static final int OP_ISNUMERIC = 83; + public static final int OP_VECSUBSET = 84; + public static final int OP_MATSUBSET = 85; + public static final int OP_VECSUBASSIGN = 86; + public static final int OP_MATSUBASSIGN = 87; + public static final int OP_AND1ST = 88; + public static final int OP_AND2ND = 89; + public static final int OP_OR1ST = 90; + public static final int OP_OR2ND = 91; + public static final int OP_GETVAR_MISSOK = 92; + public static final int OP_DDVAL_MISSOK = 93; + public static final int OP_VISIBLE = 94; + public static final int OP_SETVAR2 = 95; + public static final int OP_STARTASSIGN2 = 96; + public static final int OP_ENDASSIGN2 = 97; + public static final int OP_SETTER_CALL = 98; + public static final int OP_GETTER_CALL = 99; + public static final int OP_SWAP = 100; + public static final int OP_DUP2ND = 101; + public static final int OP_SWITCH = 102; + public static final int OP_RETURNJMP = 103; + public static final int OP_STARTSUBSET_N = 104; + public static final int OP_STARTSUBASSIGN_N = 105; + public static final int OP_VECSUBSET2 = 106; + public static final int OP_MATSUBSET2 = 107; + public static final int OP_VECSUBASSIGN2 = 108; + public static final int OP_MATSUBASSIGN2 = 109; + public static final int OP_STARTSUBSET2_N = 110; + public static final int OP_STARTSUBASSIGN2_N = 111; + public static final int OP_SUBSET_N = 112; + public static final int OP_SUBSET2_N = 113; + public static final int OP_SUBASSIGN_N = 114; + public static final int SUBASSIGN2_N = 115; + public static final int OP_LOG = 116; + public static final int OP_LOGBASE = 117; + public static final int OP_MATH1 = 118; + public static final int OP_DOTCALL = 119; + public static final int OP_COLON = 120; + public static final int OP_SEQALONG = 121; + public static final int OP_SEQLEN = 122; + public static final int OP_BASEGUARD = 123; + public static final int OP_INCLNK = 124; + public static final int OP_DECLNK = 125; + public static final int OP_DECLNK_N = 126; + public static final int OP_INCLNKSTK = 127; + public static final int OP_DECLNKSTK = 128; +} \ No newline at end of file diff --git a/src/main/java/nrc/rds/RDSException.java b/src/main/java/nrc/rds/RDSException.java new file mode 100644 index 000000000..c919a2da6 --- /dev/null +++ b/src/main/java/nrc/rds/RDSException.java @@ -0,0 +1,12 @@ +package nrc.rds; + +public class RDSException extends RuntimeException { + + public RDSException(String message) { + super(message); + } + + public RDSException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/nrc/rds/RDSReader.java b/src/main/java/nrc/rds/RDSReader.java new file mode 100644 index 000000000..1f8935d5b --- /dev/null +++ b/src/main/java/nrc/rds/RDSReader.java @@ -0,0 +1,552 @@ +package nrc.rds; + +import com.google.common.collect.Lists; +import nrc.RPlatform; +import nrc.sexp.*; +import nrc.util.Either; +import nrc.util.Pair; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +class RDSInputStream implements AutoCloseable { + private final DataInputStream in; + + RDSInputStream(InputStream in) { + this.in = new DataInputStream(in); + } + + @Override + public void close() throws Exception { + in.close(); + } + + public boolean isAtTheEnd() throws IOException { + return in.available() == 0; + } + + public byte readByte() throws IOException { + return in.readByte(); + } + + public int readInt() throws IOException { + return in.readInt(); + } + + public double readDouble() throws IOException { + return in.readDouble(); + } + + public String readString(int natEncSize, Charset charset) throws IOException { + var buf = new byte[natEncSize]; + in.readFully(buf, 0, natEncSize); + return new String(buf, charset); + } + + public int[] readInts(int length) throws IOException { + int[] ints = new int[length]; + for (int i = 0; i < length; i++) { + var n = readInt(); + ints[i] = n; + } + return ints; + } + + public double[] readDoubles(int length) throws IOException { + double[] ints = new double[length]; + for (int i = 0; i < length; i++) { + var n = readDouble(); + ints[i] = n; + } + return ints; + } +} + +class RDSItemType { + private RDSItemType() { + } + + static final int REFSXP = 255; + static final int NILVALUE_SXP = 254; + static final int GLOBALENV_SXP = 253; + static final int UNBOUNDVALUE_SXP = 252; + static final int MISSINGARG_SXP = 251; + static final int BASENAMESPACE_SXP = 250; + static final int NAMESPACESXP = 249; + static final int PACKAGESXP = 248; + static final int PERSISTSXP = 247; + static final int CLASSREFSXP = 246; + static final int GENERICREFSXP = 245; + static final int BCREPDEF = 244; + static final int BCREPREF = 243; + static final int EMPTYENV_SXP = 242; + static final int BASEENV_SXP = 241; + static final int ATTRLANGSXP = 240; + static final int ATTRLISTSXP = 239; +} + +class Flags { + private static final int UTF8_MASK = (1 << 3); + private static final int ATTR_MASK = (1 << 9); + private static final int TAG_MASK = (1 << 10); + + private final int flags; + + public Flags(int flags) { + this.flags = flags; + } + + public int getType() { + return flags & 255; + } + + public int decodeLevels() { + return flags >> 12; + } + + public boolean isUTF8() { + return (decodeLevels() & UTF8_MASK) != 0; + } + + public boolean hasAttributes() { + return (flags & ATTR_MASK) != 0; + } + + public boolean hasTag() { + return (flags & TAG_MASK) != 0; + } + + public int unpackRefIndex() { + return flags >> 8; + } + +} + +public class RDSReader implements AutoCloseable, SEXPConsts { + + private final RDSInputStream in; + private final List refTable = new ArrayList<>(128); + + // FIXME: this should include the logic from platform.c + private Charset nativeEncoding = Charset.defaultCharset(); + + public RDSReader(InputStream in) { + this.in = new RDSInputStream(in); + } + + public static SEXP readStream(InputStream input) throws Exception { + // TODO: add @NotNull annotation + Objects.requireNonNull(input, "input"); + + try (var reader = new RDSReader(input)) { + return reader.read(); + } + } + + private void initialize() throws IOException { + var type = in.readByte(); + if (type != 'X') { + throw new RDSException("Unsupported type (possibly compressed)"); + } + var nl = in.readByte(); + assert nl == '\n'; + + // versions + var formatVersion = in.readInt(); + // writer version + in.readInt(); + // minimal reader version + in.readInt(); + + // native encoding for version == 3 + switch (formatVersion) { + case 2: + break; + case 3: + var natEncSize = in.readInt(); + nativeEncoding = Charset.forName(in.readString(natEncSize, StandardCharsets.US_ASCII)); + break; + default: + throw new RDSException("Unsupported version: " + formatVersion); + } + } + + public SEXP read() throws IOException { + initialize(); + var sexp = readItem(); + if (!in.isAtTheEnd()) { + throw new RDSException("Expected end of file"); + } + return sexp; + } + + private SEXP readItem() throws IOException { + var flags = readFlags(); + + return switch (flags.getType()) { + case SEXPTypes.NILSXP -> NULL; + case SEXPTypes.SYMSXP -> readSymbol(); + case SEXPTypes.CLOSXP -> readClosure(flags); + case SEXPTypes.LISTSXP -> readList(flags); + case SEXPTypes.INTSXP -> readInts(flags); + case SEXPTypes.REALSXP -> readReals(flags); + case SEXPTypes.LGLSXP -> readBools(flags); + case SEXPTypes.VECSXP -> readVec(flags); + case SEXPTypes.ENVSXP -> readEnv(flags); + case SEXPTypes.STRSXP -> readStrs(flags); + case SEXPTypes.LANGSXP -> readLang(flags); + case SEXPTypes.BCODESXP -> readByteCode(); + case SEXPTypes.EXPRSXP -> readExpr(flags); + // special RDS-only types + case RDSItemType.NILVALUE_SXP -> NULL; + case RDSItemType.MISSINGARG_SXP -> SEXPConsts.MISSING_ARG; + case RDSItemType.EMPTYENV_SXP -> new EnvSXP(); + case RDSItemType.GLOBALENV_SXP -> SEXPConsts.GLOBAL_ENV; + case RDSItemType.REFSXP -> readRef(flags); + default -> throw new RDSException("Unsupported RDS item type: " + flags.getType()); + }; + } + + private VecSXP readExpr(Flags flags) throws IOException { + var length = in.readInt(); + var sexps = new ArrayList(length); + for (int i = 0; i < length; i++) { + sexps.add(readItem()); + } + return new VecSXP(sexps); + } + + private BCodeSXP readByteCode() throws IOException { + var length = in.readInt(); + var reps = new SEXP[length]; + return readByteCode1(reps); + } + + private BCodeSXP readByteCode1(SEXP[] reps) throws IOException { + var code = switch (readItem()) { + case IntSXP s -> s; + default -> throw new RDSException("Expected IntSXP"); + }; + + if (code.get(0) != RPlatform.BC_VERSION) { + throw new RDSException("Unsupported byte code version: " + code.get(0)); + } + + var consts = readByteCodeConsts(reps); + return new BCodeSXP(code.getData(), consts); + + } + + private List readByteCodeConsts(SEXP[] reps) throws IOException { + var length = in.readInt(); + var consts = new ArrayList(length); + for (int i = 0; i < length; i++) { + var type = in.readInt(); + switch (type) { + case SEXPTypes.BCODESXP: + consts.add(readByteCode1(reps)); + break; + case RDSItemType.BCREPREF: + case RDSItemType.BCREPDEF: + case SEXPTypes.LANGSXP: + case SEXPTypes.LISTSXP: + case RDSItemType.ATTRLISTSXP: + case RDSItemType.ATTRLANGSXP: + consts.add(readByteCodeLang(type, reps)); + break; + default: + consts.add(readItem()); + } + } + return consts; + } + + private SEXP readByteCodeLang(int type, SEXP[] reps) throws IOException { + switch (type) { + case RDSItemType.BCREPREF: + return reps[in.readInt()]; + case RDSItemType.BCREPDEF: + case SEXPTypes.LANGSXP: + case SEXPTypes.LISTSXP: + case RDSItemType.ATTRLISTSXP: + case RDSItemType.ATTRLANGSXP: + var pos = -1; + if (type == RDSItemType.BCREPDEF) { + pos = in.readInt(); + type = in.readInt(); + } + var attributes = Optional.ofNullable( + type == RDSItemType.ATTRLANGSXP || type == RDSItemType.ATTRLISTSXP ? readAttributes() + : null); + + SEXP tagSexp = NULL; + SEXP ans; + + switch (type) { + case RDSItemType.ATTRLANGSXP: + case SEXPTypes.LANGSXP: + tagSexp = readItem(); + assert tagSexp == NULL; + + Either fun = switch (readByteCodeLang(in.readInt(), reps)) { + case SymSXP s -> Either.left(s); + case LangSXP s -> Either.right(s); + default -> throw new RDSException("Expected symbol or language"); + }; + + var args = readByteCodeLang(in.readInt(), reps); + ans = new LangSXP(fun, (ListSXP) args); + break; + case RDSItemType.ATTRLISTSXP: + case SEXPTypes.LISTSXP: + tagSexp = readItem(); + Optional tag; + + if (tagSexp instanceof SymSXP sym) { + tag = Optional.of(sym.getName()); + } else if (tagSexp instanceof NilSXP) { + tag = Optional.empty(); + } else { + throw new RDSException("Expected symbol or nil"); + } + + var head = readByteCodeLang(in.readInt(), reps); + var tail = readByteCodeLang(in.readInt(), reps); + + var data = Lists.newArrayList(Pair.of(tag, head)); + + switch (tail) { + case ListSXP list -> ListSXP.flatten(list, data); + case NilSXP n -> { // no action + } + default -> throw new RDSException("Expected list, got: " + tail.getType()); + } + + ans = new ListSXP(data); + break; + + default: + throw new RDSException("Unexpected bclang type: " + type); + } + + attributes.ifPresent(ans::setAttributes); + + if (pos >= 0) { + reps[pos] = ans; + } + + return ans; + + default: + return readItem(); + + } + } + + private SEXP readRef(Flags flags) throws IOException { + var index = flags.unpackRefIndex(); + if (index == 0) { + index = in.readInt(); + } + return refTable.get(index - 1); + } + + private LangSXP readLang(Flags flags) throws IOException { + var attributes = readAttributes(flags); + // FIXME: not sure what it is good for + readTag(flags); + + Either fun = switch (readItem()) { + case SymSXP s -> Either.left(s); + case LangSXP s -> Either.right(s); + default -> throw new RDSException("Expected symbol or language"); + }; + + var args = (ListSXP) readItem(); + var item = new LangSXP(fun, args); + attributes.ifPresent(item::setAttributes); + return item; + } + + private String readChars() throws IOException { + var flags = readFlags(); + assert flags.getType() == SEXPTypes.CHARSXP; + var length = in.readInt(); + if (length == -1) { + return SEXPConsts.NA_STRING; + } else { + return in.readString(length, nativeEncoding); + } + } + + private StrSXP readStrs(Flags flags) throws IOException { + var length = in.readInt(); + var strings = new ArrayList(length); + + for (int i = 0; i < length; i++) { + strings.add(readChars()); + } + + var item = new StrSXP(strings); + + readAttributes(flags).ifPresent(item::setAttributes); + + return item; + } + + private EnvSXP readEnv(Flags flags) throws IOException { + var item = new EnvSXP(); + refTable.add(item); + + var locked = in.readInt(); + var enclos = readItem(); + var frame = readItem(); + var hashtab = readItem(); + + item.setAttributes(readAttributes()); + + // TODO: add content to the environment + return item; + } + + private VecSXP readVec(Flags flags) throws IOException { + var length = in.readInt(); + var data = new ArrayList(length); + for (int i = 0; i < length; i++) { + data.add(readItem()); + } + var item = new VecSXP(data); + readAttributes(flags).ifPresent(item::setAttributes); + return item; + } + + private LglSXP readBools(Flags flags) throws IOException { + var length = in.readInt(); + var data = in.readInts(length); + var item = new LglSXP(Arrays.stream(data).boxed().collect(Collectors.toList())); + readAttributes(flags).ifPresent(item::setAttributes); + return item; + } + + private RealSXP readReals(Flags flags) throws IOException { + var length = in.readInt(); + var data = in.readDoubles(length); + var item = new RealSXP(Arrays.stream(data).boxed().collect(Collectors.toList())); + readAttributes(flags).ifPresent(item::setAttributes); + return item; + } + + private IntSXP readInts(Flags flags) throws IOException { + var length = in.readInt(); + var data = in.readInts(length); + var item = new IntSXP(Arrays.stream(data).boxed().collect(Collectors.toList())); + readAttributes(flags).ifPresent(item::setAttributes); + return item; + } + + private ListSXP readList(Flags flags) throws IOException { + var data = new ArrayList, SEXP>>(); + + while (flags.getType() != RDSItemType.NILVALUE_SXP) { + var attributes = readAttributes(flags); + + var tag = switch (readTag(flags)) { + case SymSXP s -> Optional.of(s.getName()); + case NilSXP ignored -> Optional.empty(); + default -> throw new RDSException("Expected symbol or nil"); + }; + + var item = readItem(); + + attributes.ifPresent(item::setAttributes); + + data.add(Pair.of(tag, item)); + + flags = readFlags(); + } + + return new ListSXP(data); + } + + private CloSXP readClosure(Flags flags) throws IOException { + var attributes = readAttributes(flags); + var env = readItem(); + assert env.getType() == SEXPTypes.ENVSXP; + var formals = readItem(); + assert formals.getType() == SEXPTypes.LISTSXP; + var body = readItem(); + + var item = new CloSXP((ListSXP) formals, body, (EnvSXP) env); + + attributes.ifPresent(item::setAttributes); + + return item; + } + + private SEXP readTag(Flags flags) throws IOException { + if (flags.hasTag()) { + return readItem(); + } else { + return NULL; + } + } + + private Optional readAttributes(Flags flags) throws IOException { + if (flags.hasAttributes()) { + return Optional.of(readAttributes()); + } else { + return Optional.empty(); + } + } + + private Attributes readAttributes() throws IOException { + if (readItem() instanceof ListSXP xs) { + var attrs = new Attributes(); + + for (var x : xs) { + var attr = x.a().orElseThrow(() -> new RDSException("Expected tag")); + attrs.put(attr, x.b()); + } + + return attrs; + } else { + throw new RDSException("Expected list"); + } + } + + private SymSXP readSymbol() throws IOException { + var flags = readFlags(); + var s = readString(flags); + var item = new SymSXP(s); + + refTable.add(item); + + return item; + } + + private String readString(Flags flags) throws IOException { + var len = in.readInt(); + var charset = StandardCharsets.US_ASCII; + + if (flags.isUTF8()) { + charset = StandardCharsets.UTF_8; + } + + return in.readString(len, charset); + } + + private Flags readFlags() throws IOException { + var raw = in.readInt(); + return new Flags(raw); + } + + @Override + public void close() throws Exception { + in.close(); + } +} diff --git a/src/main/java/nrc/sexp/AbstractSXP.java b/src/main/java/nrc/sexp/AbstractSXP.java new file mode 100644 index 000000000..36caaa49c --- /dev/null +++ b/src/main/java/nrc/sexp/AbstractSXP.java @@ -0,0 +1,22 @@ +package nrc.sexp; + +public abstract class AbstractSXP implements SEXP { + private Attributes attributes = Attributes.EMPTY; + + @Override + public Attributes getAttributes() { + return attributes; + } + + public void setAttributes(Attributes attributes) { + this.attributes = attributes; + } + + @Override + public String toString() { + return SEXPTypes.toString(getType()) + "(" + contentToString() + ", attrs=" + getAttributes() + ")"; + } + + public abstract String contentToString(); + +} diff --git a/src/main/java/nrc/sexp/Attributes.java b/src/main/java/nrc/sexp/Attributes.java new file mode 100644 index 000000000..5408af932 --- /dev/null +++ b/src/main/java/nrc/sexp/Attributes.java @@ -0,0 +1,54 @@ +package nrc.sexp; + +import java.util.HashMap; + +public class Attributes { + private final HashMap attrs; + + public static final Attributes EMPTY = new Attributes() { + @Override + public String toString() { + return "EMPTY_ATTR"; + } + }; + + // TODO: prohibit modifications + public static final Attributes NONE = new Attributes(); + + public Attributes() { + attrs = new HashMap<>(); + } + + public void put(String key, SEXP value) { + attrs.put(key, value); + } + + public String toString() { + return attrs.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((attrs == null) ? 0 : attrs.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Attributes other = (Attributes) obj; + if (attrs == null) { + if (other.attrs != null) + return false; + } else if (!attrs.equals(other.attrs)) + return false; + return true; + } +} diff --git a/src/main/java/nrc/sexp/BCodeSXP.java b/src/main/java/nrc/sexp/BCodeSXP.java new file mode 100644 index 000000000..e6388b55d --- /dev/null +++ b/src/main/java/nrc/sexp/BCodeSXP.java @@ -0,0 +1,24 @@ +package nrc.sexp; + +import java.util.List; + +public final class BCodeSXP implements SEXP { + private final List code; + private final List consts; + + public BCodeSXP(List code, List consts) { + this.code = code; + this.consts = consts; + } + + @Override + public int getType() { + return SEXPTypes.BCODESXP; + } + + @Override + public String toString() { + return "BCodeSXP(code=" + code + ", consts=" + consts + ""; + } + +} \ No newline at end of file diff --git a/src/main/java/nrc/sexp/CloSXP.java b/src/main/java/nrc/sexp/CloSXP.java new file mode 100644 index 000000000..fbf7171f8 --- /dev/null +++ b/src/main/java/nrc/sexp/CloSXP.java @@ -0,0 +1,61 @@ +package nrc.sexp; + +import java.util.Objects; + +public class CloSXP extends AbstractSXP { + private final ListSXP formals; + private final SEXP body; + private final EnvSXP env; + + public CloSXP(ListSXP formals, SEXP body, EnvSXP env) { + this.formals = formals; + this.body = body; + this.env = env; + } + + @Override + public int getType() { + return SEXPTypes.CLOSXP; + } + + @Override + public String contentToString() { + return "formals=" + formals + ", body=" + body + ", env=" + env; + } + + @Override + public int hashCode() { + return Objects.hash(formals, body, env); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CloSXP other = (CloSXP) obj; + if (formals == null) { + if (other.formals != null) + return false; + } else if (!formals.equals(other.formals)) + return false; + if (body == null) { + if (other.body != null) + return false; + } else if (!body.equals(other.body)) + return false; + if (env == null) { + if (other.env != null) + return false; + } else if (!env.equals(other.env)) + return false; + return true; + } + + public SEXP getBody() { + return body; + } +} diff --git a/src/main/java/nrc/sexp/EnvSXP.java b/src/main/java/nrc/sexp/EnvSXP.java new file mode 100644 index 000000000..dececc1ed --- /dev/null +++ b/src/main/java/nrc/sexp/EnvSXP.java @@ -0,0 +1,15 @@ +package nrc.sexp; + +public class EnvSXP extends AbstractSXP { + + @Override + public int getType() { + return SEXPTypes.ENVSXP; + } + + @Override + public String contentToString() { + return ""; + } + +} diff --git a/src/main/java/nrc/sexp/IntSXP.java b/src/main/java/nrc/sexp/IntSXP.java new file mode 100644 index 000000000..c847a44e1 --- /dev/null +++ b/src/main/java/nrc/sexp/IntSXP.java @@ -0,0 +1,9 @@ +package nrc.sexp; + +import java.util.List; + +public class IntSXP extends VectorSXP { + public IntSXP(List data) { + super(SEXPTypes.INTSXP, data); + } +} diff --git a/src/main/java/nrc/sexp/LangSXP.java b/src/main/java/nrc/sexp/LangSXP.java new file mode 100644 index 000000000..da787e582 --- /dev/null +++ b/src/main/java/nrc/sexp/LangSXP.java @@ -0,0 +1,62 @@ +package nrc.sexp; + +import java.util.Objects; + +import nrc.util.Either; + +public class LangSXP extends AbstractSXP { + + private final Either fun; + private final ListSXP args; + + public LangSXP(Either fun, ListSXP args) { + this.fun = fun; + this.args = args; + } + + @Override + public int getType() { + return SEXPTypes.LANGSXP; + } + + public Either getFun() { + return fun; + } + + public ListSXP getArgs() { + return args; + } + + @Override + public String contentToString() { + return "fun=" + fun + ", args=" + args; + } + + @Override + public int hashCode() { + return Objects.hash(fun, args); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LangSXP other = (LangSXP) obj; + if (fun == null) { + if (other.fun != null) + return false; + } else if (!fun.equals(other.fun)) + return false; + if (args == null) { + if (other.args != null) + return false; + } else if (!args.equals(other.args)) + return false; + return true; + } + +} diff --git a/src/main/java/nrc/sexp/LglSXP.java b/src/main/java/nrc/sexp/LglSXP.java new file mode 100644 index 000000000..612e0395e --- /dev/null +++ b/src/main/java/nrc/sexp/LglSXP.java @@ -0,0 +1,9 @@ +package nrc.sexp; + +import java.util.List; + +public final class LglSXP extends VectorSXP { + public LglSXP(List data) { + super(SEXPTypes.LGLSXP, data); + } +} \ No newline at end of file diff --git a/src/main/java/nrc/sexp/ListSXP.java b/src/main/java/nrc/sexp/ListSXP.java new file mode 100644 index 000000000..278d97f46 --- /dev/null +++ b/src/main/java/nrc/sexp/ListSXP.java @@ -0,0 +1,24 @@ +package nrc.sexp; + +import nrc.util.Pair; + +import java.util.List; +import java.util.Optional; + + +public class ListSXP extends VectorSXP, SEXP>> { + + public ListSXP(List, SEXP>> data) { + super(SEXPTypes.LISTSXP, data); + } + + public static void flatten(ListSXP src, List, SEXP>> target) { + for (var i : src) { + if (i.b() instanceof ListSXP lst) { + flatten(lst, target); + } else { + target.add(i); + } + } + } +} diff --git a/src/main/java/nrc/sexp/NilSXP.java b/src/main/java/nrc/sexp/NilSXP.java new file mode 100644 index 000000000..96fcba18c --- /dev/null +++ b/src/main/java/nrc/sexp/NilSXP.java @@ -0,0 +1,18 @@ +package nrc.sexp; + +public final class NilSXP implements SEXP { + private NilSXP() { + } + + static final NilSXP INSTANCE = new NilSXP(); + + @Override + public int getType() { + return SEXPTypes.NILSXP; + } + + @Override + public String toString() { + return "NilSXP"; + } +} diff --git a/src/main/java/nrc/sexp/RealSXP.java b/src/main/java/nrc/sexp/RealSXP.java new file mode 100644 index 000000000..d21af086f --- /dev/null +++ b/src/main/java/nrc/sexp/RealSXP.java @@ -0,0 +1,9 @@ +package nrc.sexp; + +import java.util.List; + +public class RealSXP extends VectorSXP { + public RealSXP(List data) { + super(SEXPTypes.REALSXP, data); + } +} \ No newline at end of file diff --git a/src/main/java/nrc/sexp/SEXP.java b/src/main/java/nrc/sexp/SEXP.java new file mode 100644 index 000000000..58415232f --- /dev/null +++ b/src/main/java/nrc/sexp/SEXP.java @@ -0,0 +1,13 @@ +package nrc.sexp; + +public interface SEXP { + int getType(); + + default Attributes getAttributes() { + return Attributes.NONE; + } + + default void setAttributes(Attributes attributes) { + throw new UnsupportedOperationException(SEXPTypes.toString(getType()) + " do not support attributes"); + } +} diff --git a/src/main/java/nrc/sexp/SEXPConsts.java b/src/main/java/nrc/sexp/SEXPConsts.java new file mode 100644 index 000000000..7545fc5e8 --- /dev/null +++ b/src/main/java/nrc/sexp/SEXPConsts.java @@ -0,0 +1,42 @@ +package nrc.sexp; + +import java.util.List; + +public interface SEXPConsts { + public static final LglSXP TRUE = new LglSXP(List.of(1)); + + public static final LglSXP FALSE = new LglSXP(List.of(0)); + + public static final NilSXP NULL = NilSXP.INSTANCE; + + public static final SymSXP MISSING_ARG = new SymSXP("") { + @Override + public String toString() { + return "MISSING_ARG"; + } + }; + + public static final SymSXP ELIPSIS = new SymSXP("...") { + @Override + public String toString() { + return "ELIPSIS"; + } + }; + + public static final EnvSXP GLOBAL_ENV = new EnvSXP() { + @Override + public String toString() { + return "GLOBAL_ENV"; + } + }; + + // according to Arith.h + int NA_INT = Integer.MIN_VALUE; + + // according to Arith.h + int NA_LOGICAL = Integer.MIN_VALUE; + + Double NA_REAL = Double.NaN; + + String NA_STRING = new String("NA_STRING"); +} diff --git a/src/main/java/nrc/sexp/SEXPTypes.java b/src/main/java/nrc/sexp/SEXPTypes.java new file mode 100644 index 000000000..82c8c2ba5 --- /dev/null +++ b/src/main/java/nrc/sexp/SEXPTypes.java @@ -0,0 +1,95 @@ +package nrc.sexp; + +public class SEXPTypes { + private SEXPTypes() { + } + + public static final int NILSXP = 0; /* nil = NULL */ + public static final int SYMSXP = 1; /* symbols */ + public static final int LISTSXP = 2; /* lists of dotted pairs */ + public static final int CLOSXP = 3; /* closures */ + public static final int ENVSXP = 4; /* environments */ + public static final int PROMSXP = 5; /* promises: [un]evaluated closure arguments */ + public static final int LANGSXP = 6; /* language constructs (special lists) */ + public static final int SPECIALSXP = 7; /* special forms */ + public static final int BUILTINSXP = 8; /* builtin non-special forms */ + public static final int CHARSXP = 9; /* "scalar" string type (internal only) */ + public static final int LGLSXP = 10; /* logical vectors */ + public static final int INTSXP = 13; /* integer vectors */ + public static final int REALSXP = 14; /* real variables */ + public static final int CPLXSXP = 15; /* complex variables */ + public static final int STRSXP = 16; /* string vectors */ + public static final int DOTSXP = 17; /* dot-dot-dot object */ + public static final int ANYSXP = 18; /* make "any" args work */ + public static final int VECSXP = 19; /* generic vectors */ + public static final int EXPRSXP = 20; /* expressions vectors */ + public static final int BCODESXP = 21; /* byte code */ + public static final int EXTPTRSXP = 22; /* external pointer */ + public static final int WEAKREFSXP = 23; /* weak reference */ + public static final int RAWSXP = 24; /* raw bytes */ + public static final int S4SXP = 25; /* S4 non-vector */ + public static final int NEWSXP = 30; /* fresh node creaed in new page */ + public static final int FREESXP = 31; /* node released by GC */ + public static final int FUNSXP = 99; + + public static String toString(int type) { + switch (type) { + case NILSXP: + return "NILSXP"; + case SYMSXP: + return "SYMSXP"; + case LISTSXP: + return "LISTSXP"; + case CLOSXP: + return "CLOSXP"; + case ENVSXP: + return "ENVSXP"; + case PROMSXP: + return "PROMSXP"; + case LANGSXP: + return "LANGSXP"; + case SPECIALSXP: + return "SPECIALSXP"; + case BUILTINSXP: + return "BUILTINSXP"; + case CHARSXP: + return "CHARSXP"; + case LGLSXP: + return "LGLSXP"; + case INTSXP: + return "INTSXP"; + case REALSXP: + return "REALSXP"; + case CPLXSXP: + return "CPLXSXP"; + case STRSXP: + return "STRSXP"; + case DOTSXP: + return "DOTSXP"; + case ANYSXP: + return "ANYSXP"; + case VECSXP: + return "VECSXP"; + case EXPRSXP: + return "EXPRSXP"; + case BCODESXP: + return "BCODESXP"; + case EXTPTRSXP: + return "EXTPTRSXP"; + case WEAKREFSXP: + return "WEAKREFSXP"; + case RAWSXP: + return "RAWSXP"; + case S4SXP: + return "S4SXP"; + case NEWSXP: + return "NEWSXP"; + case FREESXP: + return "FREESXP"; + case FUNSXP: + return "FUNSXP"; + default: + throw new IllegalArgumentException("Unknown SEXP type: " + type); + } + } +} diff --git a/src/main/java/nrc/sexp/StrSXP.java b/src/main/java/nrc/sexp/StrSXP.java new file mode 100644 index 000000000..f830e0e4d --- /dev/null +++ b/src/main/java/nrc/sexp/StrSXP.java @@ -0,0 +1,9 @@ +package nrc.sexp; + +import java.util.List; + +public class StrSXP extends VectorSXP { + public StrSXP(List data) { + super(SEXPTypes.STRSXP, data); + } +} diff --git a/src/main/java/nrc/sexp/SymSXP.java b/src/main/java/nrc/sexp/SymSXP.java new file mode 100644 index 000000000..5cdbbef0e --- /dev/null +++ b/src/main/java/nrc/sexp/SymSXP.java @@ -0,0 +1,41 @@ +package nrc.sexp; + +import java.util.Objects; + +public class SymSXP implements SEXP { + private final String name; + + public SymSXP(String name) { + this.name = name; + } + + @Override + public int getType() { + return SEXPTypes.SYMSXP; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "SymSXP(" + name + ")"; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + SymSXP symSXP = (SymSXP) o; + return Objects.equals(name, symSXP.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + +} diff --git a/src/main/java/nrc/sexp/VecSXP.java b/src/main/java/nrc/sexp/VecSXP.java new file mode 100644 index 000000000..8d9755a42 --- /dev/null +++ b/src/main/java/nrc/sexp/VecSXP.java @@ -0,0 +1,9 @@ +package nrc.sexp; + +import java.util.List; + +public class VecSXP extends VectorSXP { + public VecSXP(List data) { + super(SEXPTypes.VECSXP, data); + } +} \ No newline at end of file diff --git a/src/main/java/nrc/sexp/VectorSXP.java b/src/main/java/nrc/sexp/VectorSXP.java new file mode 100644 index 000000000..aa7425ff7 --- /dev/null +++ b/src/main/java/nrc/sexp/VectorSXP.java @@ -0,0 +1,67 @@ +package nrc.sexp; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +public abstract class VectorSXP extends AbstractSXP implements Iterable { + private final List data; + private final int type; + + public VectorSXP(int type, List data) { + this.type = type; + this.data = Collections.unmodifiableList(data); + } + + @Override + public String contentToString() { + return data.toString(); + } + + @Override + public int getType() { + return type; + } + + public T get(int i) { + return data.get(i); + } + + public List getData() { + return data; + } + + public int size() { + return data.size(); + } + + @Override + public Iterator iterator() { + return data.iterator(); + } + + @Override + public int hashCode() { + return Objects.hash(type, data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + VectorSXP other = (VectorSXP) obj; + if (data == null) { + if (other.data != null) + return false; + } else if (!data.equals(other.data)) + return false; + if (type != other.type) + return false; + return true; + } +} diff --git a/src/main/java/nrc/util/Either.java b/src/main/java/nrc/util/Either.java new file mode 100644 index 000000000..b1c20bb3f --- /dev/null +++ b/src/main/java/nrc/util/Either.java @@ -0,0 +1,11 @@ +package nrc.util; + +public sealed interface Either permits Left, Right { + public static Either left(L left) { + return new Left<>(left); + } + + public static Either right(R right) { + return new Right<>(right); + } +} diff --git a/src/main/java/nrc/util/IO.java b/src/main/java/nrc/util/IO.java new file mode 100644 index 000000000..4b3216360 --- /dev/null +++ b/src/main/java/nrc/util/IO.java @@ -0,0 +1,28 @@ +package nrc.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.util.zip.GZIPInputStream; + +public final class IO { + private IO() { + } + + public static InputStream maybeDecompress(InputStream input) throws IOException { + final PushbackInputStream pb = new PushbackInputStream(input, 2); + byte[] buff = pb.readNBytes(2); + pb.unread(buff); + if (buff.length != 2) { + return pb; + } + + int magick = ((buff[1] & 0xFF) << 8) | buff[0]; + + if (magick == GZIPInputStream.GZIP_MAGIC) { + return new GZIPInputStream(pb); + } else { + return pb; + } + } +} diff --git a/src/main/java/nrc/util/Left.java b/src/main/java/nrc/util/Left.java new file mode 100644 index 000000000..043a771e9 --- /dev/null +++ b/src/main/java/nrc/util/Left.java @@ -0,0 +1,4 @@ +package nrc.util; + +public record Left(L left) implements Either { +} \ No newline at end of file diff --git a/src/main/java/nrc/util/Pair.java b/src/main/java/nrc/util/Pair.java new file mode 100644 index 000000000..538aa18fc --- /dev/null +++ b/src/main/java/nrc/util/Pair.java @@ -0,0 +1,7 @@ +package nrc.util; + +public record Pair(A a, B b) { + public static Pair of(A a, B b) { + return new Pair<>(a, b); + } +} diff --git a/src/main/java/nrc/util/Right.java b/src/main/java/nrc/util/Right.java new file mode 100644 index 000000000..dd367c797 --- /dev/null +++ b/src/main/java/nrc/util/Right.java @@ -0,0 +1,4 @@ +package nrc.util; + +public record Right(R right) implements Either { +} \ No newline at end of file diff --git a/src/test/java/nrc/bc/CompilerTest.java b/src/test/java/nrc/bc/CompilerTest.java new file mode 100644 index 000000000..f4f575c2a --- /dev/null +++ b/src/test/java/nrc/bc/CompilerTest.java @@ -0,0 +1,11 @@ +package nrc.bc; + +public class CompilerTest { +// @Test +// public void testBasic() throws Exception { +// var source = (CloSXP) RDSReader.readStream(getClass().getResourceAsStream("f1.rds")); +// var compiler = new Compiler(); +// var bc = compiler.compileFun(source); +// System.out.println(bc); +// } +} diff --git a/src/test/java/nrc/rds/RDSReaderTest.java b/src/test/java/nrc/rds/RDSReaderTest.java new file mode 100644 index 000000000..14c1e0cfd --- /dev/null +++ b/src/test/java/nrc/rds/RDSReaderTest.java @@ -0,0 +1,91 @@ +package nrc.rds; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +import nrc.RPlatform; +import nrc.sexp.IntSXP; +import nrc.sexp.LglSXP; +import nrc.sexp.RealSXP; +import nrc.sexp.SEXPConsts; + +public class RDSReaderTest implements SEXPConsts { + + @Test + public void testInts() throws Exception { + var sexp = RDSReader.readStream(getClass().getResourceAsStream("ints.rds")); + if (sexp instanceof IntSXP ints) { + assertEquals(-RPlatform.INT_MAX, ints.get(0)); + assertEquals(-1, ints.get(1)); + assertEquals(0, ints.get(2)); + assertEquals(NA_INT, ints.get(3)); + assertEquals(1, ints.get(4)); + assertEquals(RPlatform.INT_MAX, ints.get(5)); + } else { + fail("Expected IntSXP"); + } + } + + @Test + public void testLgls() throws Exception { + var sexp = RDSReader.readStream(getClass().getResourceAsStream("lgls.rds")); + if (sexp instanceof LglSXP logs) { + assertEquals(1, logs.get(0)); + assertEquals(0, logs.get(1)); + assertEquals(NA_LOGICAL, logs.get(2)); + } else { + fail("Expected LglSXP"); + } + } + + @Test + public void testReals() throws Exception { + var sexp = RDSReader.readStream(getClass().getResourceAsStream("reals.rds")); + if (sexp instanceof RealSXP reals) { + // TODO: R double min are different - should be all be based on RPlatform + // assertEquals(nums[0], Double.MIN_VALUE); + assertEquals(-1.0, reals.get(1)); + assertEquals(.0, reals.get(2)); + assertEquals(SEXPConsts.NA_REAL, reals.get(3)); + assertEquals(1.0, reals.get(4)); + assertEquals(Double.MAX_VALUE, reals.get(5)); + } else { + fail("Expected RealSXP"); + } + } + + @Test + public void testList() throws Exception { + var sexp = RDSReader.readStream(getClass().getResourceAsStream("list.rds")); + + System.out.println(sexp); + } + + @Test + public void testClosure() throws Exception { + var sexp = RDSReader.readStream(getClass().getResourceAsStream("closure.rds")); + + System.out.println(sexp); + } + + @Test + public void testClosureBC() throws Exception { + // function(x, y) "abc" + x + length(y) + var sexp = RDSReader.readStream(getClass().getResourceAsStream("closure-bc.rds")); + + System.out.println(sexp); + + } + + @Test + public void testClosureBC2() throws Exception { + // function(a,b,c) f(a+b,length(b),c) + var sexp = RDSReader.readStream(getClass().getResourceAsStream("closure-bc2.rds")); + + System.out.println(sexp); + } + + // TODO: "closure-install.packages.rds" +} diff --git a/src/test/resources/nrc/bc/f1.rds b/src/test/resources/nrc/bc/f1.rds new file mode 100644 index 0000000000000000000000000000000000000000..ae9eb025a5098bf616268d4074d459038e06eb61 GIT binary patch literal 463 zcmZuuJqyAx5WVz6t5R`t&Zvlkn}guy=%nB(HGx8{VioJZ7FV(Q&ZJ_+z~z#=ckkXM zt0ob-r1F z6R?(ay=6VjQVxkD6~-!fKT6gaYx-{4vNJ`y{l)hA8WS{3QGeR1vc=~e%SQD05hrq# b+>HwRz~&3Y06}mD%*lV#hKcDFyII8xoBT9a literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/bc/f1c.rds b/src/test/resources/nrc/bc/f1c.rds new file mode 100644 index 0000000000000000000000000000000000000000..874baa21624ba56af906e36a77a0ac124918386a GIT binary patch literal 833 zcmb7DO-sW-6nyz=qqfq6_MD?qd+_E#@FsZaNx^GLHxP(v$tuR1KUutrA2Z2rn}i7Z z;B~)dXXd?4@ApN-6W?>i3ow=7?)G|qAu{kpd>0#z__Bv_po_Rr3D9G#!s-OOW}Fg% zIkLfx; zDNmy^nnma46u_39kY4iT1O1T27Ky2R)!D)aakkX-)Ra6hHO1@Ai_vM62^vxizs)Tx zvF)Q3lFyDgr^J#@ zMgUy39F=Sv*?tg8xJ-cdT5B-ygG4Sbhw+LfrYqr2_>Bz21f>rIV$m%7cOGbEVJkp zV|uG~TJ$MRjxsD^e-~M*#qMcUBk@@yrzJ@Fs9&b}WtMe_JtPwpX9VYqbsl{Fe?lXVe*Ta0 G%i#yPrftvw literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/rds/closure-bc2.rds b/src/test/resources/nrc/rds/closure-bc2.rds new file mode 100644 index 0000000000000000000000000000000000000000..1cea0ead517f2fae441bb309875830786aeb392e GIT binary patch literal 2045 zcmcIkO;5r=5M5~bhQx!$#KZ(If&uRyJezp&q>0y3w@8A;P>dKa{ATrL{CKb3AuTQf z2PT=$e$39id9&Nws#3~Pu9H_z0Yg=|xxP3(Q>y4F<>s-FQ?9DO7NfC0)&aa5n3dxl ztQy;*2b4CKXy$YYr-Q*zW6On7-WC!xg27bZUFqlHAk+~Y2+bC&APzfl$QyXAe)Iga z89aslWH1WbUbolvPntp7>-4%q9rh>p?cNENj5|(d@RbQJ^d1P1VWnye)pw#9|AIgC zB7#g~sF@=D(c_EBld2nG7^3uJPDzS6v64uAEr82;IP$r;*t0GA$zmL84`Y(ee$|cc z!;;fOC^{Tl`f$$vA44hhVUIsJfv~safg-?j0Vcd6+@K>5Ssgpr5JwElq#!k~a86+$ zS!Y0wZ2^l(M^oMyTBBgSnvM>LHO|4wCiggxGY)i(29kAoA!L0QN@8fqco#|$vfj-Y z^O%?IAw!0kqj`oIaU@8HhUlm%vDUkzr;lSDMR;e?Wq7BjIKa*=#%1@Gs5Ndq`NL#l z3Y&6&Uk$h#?170d%pPIR{||-r@aB+RVwhvYSoUa_{I@pfH=;CnqhK@a)@NAUOsuS| zVq5aCnl;s$=RfxOhmgC>l9-2o3)uK|k!8yESeD4{jCnGZTB}*bnmc2Z%>A^+v9Tgp ag!v@WT3>69Rahm@lDA}gW7a%>YWW546uyoC literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/rds/closure-install.packages.rds b/src/test/resources/nrc/rds/closure-install.packages.rds new file mode 100644 index 0000000000000000000000000000000000000000..241802f45129faa2a9fe8b74f094669e03d72b61 GIT binary patch literal 206042 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⁢h@ot|wj6@!6La3 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 N){Yr`do0Rd`#+bIUDE&n literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/rds/closure.rds b/src/test/resources/nrc/rds/closure.rds new file mode 100644 index 0000000000000000000000000000000000000000..860677e08a4aefb09ff0a216d1ace130afa2d5f3 GIT binary patch literal 512 zcmZ8eO>crg6nwCNsAulKbHt#$s%x5|`VF_R%Yd~p;tGJHV=(HTp*zcq z6e$nj?=}(ei(IEA67N8-`jN@6SZ04(Rry7)>rdH4a+Un3mE6c0C{lf_PU|MZA|u6+ z<#cjp$>Bs?sWlkY`>IIFUf#7!FYU9{<+riaU=xht5y4|3@v^EHqW{U`oEk0nDcKmZ o>oUd~>zxe}{4YSxS&YxaodgUq^o-NIo-KbF85pBXChV))Czmxn0{{R3 literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/rds/ints.rds b/src/test/resources/nrc/rds/ints.rds new file mode 100644 index 0000000000000000000000000000000000000000..7b5c96b24e5b166a4d1d27f84275676cf0610758 GIT binary patch literal 55 ycma#xVqjokW?*4vVqj(kG8tGyL)>&NfDB$BW@}(zVEhjRKsE!A4FZhyKt2GjMG9R2 literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/rds/lgls.rds b/src/test/resources/nrc/rds/lgls.rds new file mode 100644 index 0000000000000000000000000000000000000000..1d9414228345de162f80a7f14c92765e170f66a3 GIT binary patch literal 43 ncma#xVqjokW?*4vVqj(kG8tGyL)>&NfDDKP0|O&Sv;l|#HHrdW literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/rds/list.rds b/src/test/resources/nrc/rds/list.rds new file mode 100644 index 0000000000000000000000000000000000000000..baa6a345bfa5639be6266c96d8ec4a5d120fba60 GIT binary patch literal 69 zcma#xVqjokW?*4vVqj(kG8tGyL)>&N7#LWXfE-2!76wibT>&J1qYG9-1R4GT0GI&< A-v9sr literal 0 HcmV?d00001 diff --git a/src/test/resources/nrc/rds/reals.rds b/src/test/resources/nrc/rds/reals.rds new file mode 100644 index 0000000000000000000000000000000000000000..961b838d8a943b70311a87f681077b456a3a3c8a GIT binary patch literal 79 zcma#xVqjokW?*4vVqj(kG8tGyL)>&NfDAq$W@8Wli7_zj{{W$3WIdSAzQ`UbUjP0- G7ytkphYnHz literal 0 HcmV?d00001 From 07207ea9ec9ac0d3480fb4949474a3e16fef3694 Mon Sep 17 00:00:00 2001 From: jakobeha Date: Thu, 18 Jan 2024 15:43:46 -0500 Subject: [PATCH 02/10] refactoring from filip --- .idea/misc.xml | 48 ++ src/main/java/Main.java | 8 +- src/main/java/nrc/bc/Compiler.java | 200 ------- src/main/java/nrc/bc/OpCodes.java | 133 ----- src/main/java/nrc/rds/RDSReader.java | 552 ------------------ src/main/java/nrc/sexp/AbstractSXP.java | 22 - src/main/java/nrc/sexp/Attributes.java | 54 -- src/main/java/nrc/sexp/BCodeSXP.java | 24 - src/main/java/nrc/sexp/CloSXP.java | 61 -- src/main/java/nrc/sexp/EnvSXP.java | 15 - src/main/java/nrc/sexp/IntSXP.java | 9 - src/main/java/nrc/sexp/LangSXP.java | 62 -- src/main/java/nrc/sexp/LglSXP.java | 9 - src/main/java/nrc/sexp/ListSXP.java | 24 - src/main/java/nrc/sexp/NilSXP.java | 18 - src/main/java/nrc/sexp/RealSXP.java | 9 - src/main/java/nrc/sexp/SEXP.java | 13 - src/main/java/nrc/sexp/SEXPTypes.java | 95 --- src/main/java/nrc/sexp/StrSXP.java | 9 - src/main/java/nrc/sexp/SymSXP.java | 41 -- src/main/java/nrc/sexp/VecSXP.java | 9 - src/main/java/nrc/sexp/VectorSXP.java | 67 --- .../java/{nrc => org/prlprg}/RPlatform.java | 4 +- src/main/java/org/prlprg/bc/Bc.java | 61 +- src/main/java/org/prlprg/bc/BcCode.java | 20 +- src/main/java/org/prlprg/bc/BcData.java | 9 +- .../org/prlprg/bc/BcFromRawException.java | 11 + src/main/java/org/prlprg/bc/BcInstr.java | 209 ++++++- src/main/java/org/prlprg/bc/BcOp.java | 7 +- src/main/java/org/prlprg/bc/ConstPool.java | 215 +++++++ .../java/org/prlprg/compile/Compiler.java | 145 +++++ .../prlprg/compile}/CompilerException.java | 2 +- src/main/java/org/prlprg/compile/Context.java | 9 + .../java/org/prlprg/compile/package-info.java | 9 + .../java/org/prlprg/primitive/Complex.java | 9 + .../java/org/prlprg/primitive/Logical.java | 25 + .../org/prlprg/primitive/package-info.java | 9 + src/main/java/org/prlprg/rds/Flags.java | 47 ++ .../{nrc => org/prlprg}/rds/RDSException.java | 3 +- .../java/org/prlprg/rds/RDSInputStream.java | 60 ++ src/main/java/org/prlprg/rds/RDSItemType.java | 79 +++ src/main/java/org/prlprg/rds/RDSReader.java | 441 ++++++++++++++ .../java/org/prlprg/rds/package-info.java | 9 + src/main/java/org/prlprg/sexp/Attributes.java | 69 +++ src/main/java/org/prlprg/sexp/BCodeSXP.java | 16 + src/main/java/org/prlprg/sexp/CloSXP.java | 17 + src/main/java/org/prlprg/sexp/ComplexSXP.java | 46 ++ src/main/java/org/prlprg/sexp/EnvSXP.java | 5 + src/main/java/org/prlprg/sexp/ExprSXP.java | 36 ++ src/main/java/org/prlprg/sexp/IntSXP.java | 45 ++ src/main/java/org/prlprg/sexp/LangSXP.java | 17 + src/main/java/org/prlprg/sexp/LglSXP.java | 46 ++ .../java/org/prlprg/sexp/ListOrVectorSXP.java | 11 + src/main/java/org/prlprg/sexp/ListSXP.java | 32 + .../java/org/prlprg/sexp/ListSXPImpl.java | 41 ++ src/main/java/org/prlprg/sexp/NilSXP.java | 45 ++ src/main/java/org/prlprg/sexp/RealSXP.java | 45 ++ src/main/java/org/prlprg/sexp/SEXP.java | 12 + .../{nrc => org/prlprg}/sexp/SEXPConsts.java | 17 +- src/main/java/org/prlprg/sexp/SEXPType.java | 111 ++++ src/main/java/org/prlprg/sexp/StrSXP.java | 45 ++ .../java/org/prlprg/sexp/SymOrLangSXP.java | 4 + src/main/java/org/prlprg/sexp/SymSXP.java | 8 + src/main/java/org/prlprg/sexp/TaggedElem.java | 6 + src/main/java/org/prlprg/sexp/VecSXP.java | 36 ++ src/main/java/org/prlprg/sexp/VectorSXP.java | 4 + .../java/org/prlprg/sexp/package-info.java | 9 + .../java/{nrc => org/prlprg}/util/Either.java | 6 +- .../java/org/prlprg/util/EmptyIterator.java | 16 + .../java/{nrc => org/prlprg}/util/IO.java | 4 +- .../java/{nrc => org/prlprg}/util/Left.java | 4 +- .../prlprg/util/NotImplementedException.java | 7 + src/main/java/org/prlprg/util/Nothing.java | 12 + .../java/org/prlprg/util/PackagePrivate.java | 8 + .../java/{nrc => org/prlprg}/util/Pair.java | 2 +- .../java/{nrc => org/prlprg}/util/Right.java | 4 +- src/test/java/nrc/bc/CompilerTest.java | 11 - src/test/java/org/prlprg/bc/BcTests.java | 47 +- src/test/java/org/prlprg/bc/CompilerTest.java | 17 + .../prlprg}/rds/RDSReaderTest.java | 35 +- .../resources/{nrc => org.prlprg}/bc/f1.rds | Bin .../resources/{nrc => org.prlprg}/bc/f1c.rds | Bin .../{nrc => org.prlprg}/rds/closure-bc.rds | Bin .../{nrc => org.prlprg}/rds/closure-bc2.rds | Bin .../rds/closure-install.packages.rds | Bin .../{nrc => org.prlprg}/rds/closure.rds | Bin .../{nrc => org.prlprg}/rds/ints.rds | Bin .../{nrc => org.prlprg}/rds/lgls.rds | Bin .../{nrc => org.prlprg}/rds/list.rds | Bin .../{nrc => org.prlprg}/rds/reals.rds | Bin 90 files changed, 2253 insertions(+), 1508 deletions(-) delete mode 100644 src/main/java/nrc/bc/Compiler.java delete mode 100644 src/main/java/nrc/bc/OpCodes.java delete mode 100644 src/main/java/nrc/rds/RDSReader.java delete mode 100644 src/main/java/nrc/sexp/AbstractSXP.java delete mode 100644 src/main/java/nrc/sexp/Attributes.java delete mode 100644 src/main/java/nrc/sexp/BCodeSXP.java delete mode 100644 src/main/java/nrc/sexp/CloSXP.java delete mode 100644 src/main/java/nrc/sexp/EnvSXP.java delete mode 100644 src/main/java/nrc/sexp/IntSXP.java delete mode 100644 src/main/java/nrc/sexp/LangSXP.java delete mode 100644 src/main/java/nrc/sexp/LglSXP.java delete mode 100644 src/main/java/nrc/sexp/ListSXP.java delete mode 100644 src/main/java/nrc/sexp/NilSXP.java delete mode 100644 src/main/java/nrc/sexp/RealSXP.java delete mode 100644 src/main/java/nrc/sexp/SEXP.java delete mode 100644 src/main/java/nrc/sexp/SEXPTypes.java delete mode 100644 src/main/java/nrc/sexp/StrSXP.java delete mode 100644 src/main/java/nrc/sexp/SymSXP.java delete mode 100644 src/main/java/nrc/sexp/VecSXP.java delete mode 100644 src/main/java/nrc/sexp/VectorSXP.java rename src/main/java/{nrc => org/prlprg}/RPlatform.java (66%) create mode 100644 src/main/java/org/prlprg/bc/BcFromRawException.java create mode 100644 src/main/java/org/prlprg/bc/ConstPool.java create mode 100644 src/main/java/org/prlprg/compile/Compiler.java rename src/main/java/{nrc/bc => org/prlprg/compile}/CompilerException.java (83%) create mode 100644 src/main/java/org/prlprg/compile/Context.java create mode 100644 src/main/java/org/prlprg/compile/package-info.java create mode 100644 src/main/java/org/prlprg/primitive/Complex.java create mode 100644 src/main/java/org/prlprg/primitive/Logical.java create mode 100644 src/main/java/org/prlprg/primitive/package-info.java create mode 100644 src/main/java/org/prlprg/rds/Flags.java rename src/main/java/{nrc => org/prlprg}/rds/RDSException.java (90%) create mode 100644 src/main/java/org/prlprg/rds/RDSInputStream.java create mode 100644 src/main/java/org/prlprg/rds/RDSItemType.java create mode 100644 src/main/java/org/prlprg/rds/RDSReader.java create mode 100644 src/main/java/org/prlprg/rds/package-info.java create mode 100644 src/main/java/org/prlprg/sexp/Attributes.java create mode 100644 src/main/java/org/prlprg/sexp/BCodeSXP.java create mode 100644 src/main/java/org/prlprg/sexp/CloSXP.java create mode 100644 src/main/java/org/prlprg/sexp/ComplexSXP.java create mode 100644 src/main/java/org/prlprg/sexp/EnvSXP.java create mode 100644 src/main/java/org/prlprg/sexp/ExprSXP.java create mode 100644 src/main/java/org/prlprg/sexp/IntSXP.java create mode 100644 src/main/java/org/prlprg/sexp/LangSXP.java create mode 100644 src/main/java/org/prlprg/sexp/LglSXP.java create mode 100644 src/main/java/org/prlprg/sexp/ListOrVectorSXP.java create mode 100644 src/main/java/org/prlprg/sexp/ListSXP.java create mode 100644 src/main/java/org/prlprg/sexp/ListSXPImpl.java create mode 100644 src/main/java/org/prlprg/sexp/NilSXP.java create mode 100644 src/main/java/org/prlprg/sexp/RealSXP.java create mode 100644 src/main/java/org/prlprg/sexp/SEXP.java rename src/main/java/{nrc => org/prlprg}/sexp/SEXPConsts.java (73%) create mode 100644 src/main/java/org/prlprg/sexp/SEXPType.java create mode 100644 src/main/java/org/prlprg/sexp/StrSXP.java create mode 100644 src/main/java/org/prlprg/sexp/SymOrLangSXP.java create mode 100644 src/main/java/org/prlprg/sexp/SymSXP.java create mode 100644 src/main/java/org/prlprg/sexp/TaggedElem.java create mode 100644 src/main/java/org/prlprg/sexp/VecSXP.java create mode 100644 src/main/java/org/prlprg/sexp/VectorSXP.java create mode 100644 src/main/java/org/prlprg/sexp/package-info.java rename src/main/java/{nrc => org/prlprg}/util/Either.java (53%) create mode 100644 src/main/java/org/prlprg/util/EmptyIterator.java rename src/main/java/{nrc => org/prlprg}/util/IO.java (96%) rename src/main/java/{nrc => org/prlprg}/util/Left.java (68%) create mode 100644 src/main/java/org/prlprg/util/NotImplementedException.java create mode 100644 src/main/java/org/prlprg/util/Nothing.java create mode 100644 src/main/java/org/prlprg/util/PackagePrivate.java rename src/main/java/{nrc => org/prlprg}/util/Pair.java (83%) rename src/main/java/{nrc => org/prlprg}/util/Right.java (69%) delete mode 100644 src/test/java/nrc/bc/CompilerTest.java create mode 100644 src/test/java/org/prlprg/bc/CompilerTest.java rename src/test/java/{nrc => org/prlprg}/rds/RDSReaderTest.java (62%) rename src/test/resources/{nrc => org.prlprg}/bc/f1.rds (100%) rename src/test/resources/{nrc => org.prlprg}/bc/f1c.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/closure-bc.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/closure-bc2.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/closure-install.packages.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/closure.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/ints.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/lgls.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/list.rds (100%) rename src/test/resources/{nrc => org.prlprg}/rds/reals.rds (100%) diff --git a/.idea/misc.xml b/.idea/misc.xml index 20e4bc582..7ff59b18f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,6 +9,54 @@