diff --git a/libs/base/System.idr b/libs/base/System.idr index d6f6e150d..789d46d97 100644 --- a/libs/base/System.idr +++ b/libs/base/System.idr @@ -10,9 +10,13 @@ support fn = "C:" ++ fn ++ ", libidris2_support" libc : String -> String libc fn = "C:" ++ fn ++ ", libc 6" -%foreign support "idris2_sleep" +%foreign + support "idris2_sleep" + "jvm:sleep(int void),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_sleep : Int -> PrimIO () -%foreign support "idris2_usleep" +%foreign + support "idris2_usleep" + "jvm:usleep(int void),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_usleep : Int -> PrimIO () export @@ -25,20 +29,30 @@ usleep sec = primIO (prim_usleep sec) -- This one is going to vary for different back ends. Probably needs a -- better convention. Will revisit... -%foreign "scheme:blodwen-args" +%foreign + "scheme:blodwen-args" + "jvm:getProgramArgs(io/github/mmhelloworld/idris2boot/runtime/IdrisList),io/github/mmhelloworld/idris2boot/runtime/Runtime" prim__getArgs : PrimIO (List String) export getArgs : IO (List String) getArgs = primIO prim__getArgs -%foreign libc "getenv" +%foreign + libc "getenv" + "jvm:getEnv(java/lang/String java/lang/String),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_getEnv : String -> PrimIO (Ptr String) -%foreign support "idris2_getEnvPair" +%foreign + support "idris2_getEnvPair" + "jvm:getEnvPair(int java/lang/String),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_getEnvPair : Int -> PrimIO (Ptr String) -%foreign libc "setenv" +%foreign + libc "setenv" + "jvm:setEnv(java/lang/String java/lang/String int int),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_setEnv : String -> String -> Int -> PrimIO Int -%foreign libc "setenv" +%foreign + libc "setenv" + "jvm:clearEnv(java/lang/String int),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_unsetEnv : String -> PrimIO Int export @@ -84,6 +98,7 @@ unsetEnv var %foreign libc "system" "scheme:blodwen-system" + "jvm:runCommand(java/lang/String int),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_system : String -> PrimIO Int export @@ -92,13 +107,16 @@ system cmd = primIO (prim_system cmd) %foreign support "idris2_time" "scheme:blodwen-time" + "jvm:time(int),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_time : PrimIO Int export time : IO Integer time = pure $ cast !(primIO prim_time) -%foreign libc "exit" +%foreign + libc "exit" + "jvm:exit(int void),io/github/mmhelloworld/idris2boot/runtime/IdrisSystem" prim_exit : Int -> PrimIO () ||| Programs can either terminate successfully, or end in a caught diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Console.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Console.java index da1c5069d..21336d265 100644 --- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Console.java +++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Console.java @@ -7,9 +7,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; public final class Console { - private static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in, UTF_8)); - - private static boolean isStdinEof = false; + private static final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in, UTF_8)); private Console() { } @@ -21,7 +19,6 @@ public static void printString(String string) { public static String getString() throws IOException { String line = stdin.readLine(); - isStdinEof = line == null; - return isStdinEof ? "" : line; + return line == null ? "" : line; } } diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Files.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Files.java deleted file mode 100644 index 072cd20db..000000000 --- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Files.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.github.mmhelloworld.idris2boot.runtime; - -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Collection; - -import static io.github.mmhelloworld.idris2boot.runtime.Paths.createPath; -import static io.github.mmhelloworld.idris2boot.runtime.Runtime.setErrorNumber; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; - -public final class Files { - private Files() { - } - - -} diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisList.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisList.java new file mode 100644 index 000000000..1e8db7dc6 --- /dev/null +++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisList.java @@ -0,0 +1,205 @@ +package io.github.mmhelloworld.idris2boot.runtime; + +import java.util.AbstractSequentialList; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import static java.util.Collections.emptyListIterator; + +public abstract class IdrisList extends AbstractSequentialList implements IdrisObject { + public abstract int getConstructorId(); + + public static IdrisList fromIterable(Iterable iterable) { + Iterator iterator = iterable.iterator(); + IdrisList list = Nil.INSTANCE; + while (iterator.hasNext()) { + list = new Cons(iterator.next(), list); + } + return reverse(list); + } + + public static IdrisList reverse(IdrisList list) { + IdrisList current = list; + IdrisList result = Nil.INSTANCE; + while (current != Nil.INSTANCE) { + Cons cons = ((Cons) current); + result = new Cons(cons.head, result); + current = cons.tail; + } + return result; + } + + public static class Nil extends IdrisList { + public static final Nil INSTANCE = new Nil(); + + private Nil() { + } + + @Override + public int size() { + return 0; + } + + @Override + public Object get(int index) { + throw new IndexOutOfBoundsException("Index: " + index); + } + + @Override + public ListIterator listIterator(int i) { + return emptyListIterator(); + } + + @Override + public int getConstructorId() { + return 0; + } + } + + public static class Cons extends IdrisList { + private final Object head; + private final IdrisList tail; + + public Cons(Object head, IdrisList tail) { + this.head = head; + this.tail = tail; + } + + @Override + public int getConstructorId() { + return 1; + } + + @Override + public Object getProperty(int index) { + switch (index) { + case 0: + return head; + case 1: + return tail; + default: + throw new NoSuchElementException("No property at " + index + " for an Idris list"); + } + } + + @Override + public ListIterator listIterator(int index) { + int size = size(); + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); + } + return new Iterator(index, this); + } + + @Override + public int size() { + return 1 + tail.size(); + } + + private static class Node { + private Node previous; + private final Object element; + private Node next; + + private Node(Node previous, Object element, Node next) { + this.previous = previous; + this.element = element; + this.next = next; + } + + private Node(Object element) { + this(null, element, null); + } + } + + private static class Iterator implements ListIterator { + private int index; + private Node node; + + private Iterator(int index, IdrisList idrisList) { + this.index = index; + this.node = createNode(index, idrisList); + } + + private static Node createNode(int index, IdrisList idrisList) { + if (idrisList == Nil.INSTANCE) { + return null; + } + Cons cons = (Cons) idrisList; + Node currNode = new Node(cons.head); + int startIndex = -1; + Node start = new Node(null, null, currNode); + for (IdrisList currList = cons.tail; currList != Nil.INSTANCE; currList = ((Cons) currList).tail) { + Cons newCons = (Cons) currList; + Node newNode = new Node(newCons.head); + currNode.next = newNode; + newNode.previous = currNode; + if (startIndex >= 0 && startIndex < index) { + start = currNode.previous; + } + currNode = currNode.next; + startIndex++; + } + return startIndex < index ? currNode : start; + } + + @Override + public boolean hasNext() { + return node != null && node.next != null; + } + + @Override + public Object next() { + if (node == null || node.next == null) { + throw new NoSuchElementException(); + } + Object element = node.next.element; + node = node.next; + index++; + return element; + } + + @Override + public boolean hasPrevious() { + return index > 0; + } + + @Override + public Object previous() { + if (node == null) { + throw new NoSuchElementException(); + } + Object element = node.element; + node = node.previous; + index--; + return element; + } + + @Override + public int nextIndex() { + return index; + } + + @Override + public int previousIndex() { + return index - 1; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("immutable list"); + } + + @Override + public void set(Object o) { + throw new UnsupportedOperationException("immutable list"); + } + + @Override + public void add(Object o) { + throw new UnsupportedOperationException("immutable list"); + } + } + } +} diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisSystem.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisSystem.java new file mode 100644 index 000000000..6a181e4de --- /dev/null +++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/IdrisSystem.java @@ -0,0 +1,107 @@ +package io.github.mmhelloworld.idris2boot.runtime; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static java.lang.System.currentTimeMillis; + +public class IdrisSystem { + private static final Map environmentVariables; + private static final List environmentVariableNames; + + static { + environmentVariables = new LinkedHashMap<>(System.getenv()); + environmentVariables.putAll((Map) System.getProperties()); + environmentVariableNames = new ArrayList<>(environmentVariables.keySet()); + } + + public static int time() { + return (int) Duration.ofMillis(currentTimeMillis()).getSeconds(); + } + + public static int runCommand(String command) throws IOException, InterruptedException { + String[] cmdarray = parseCommand(command).toArray(new String[0]); + ProcessBuilder processBuilder = new ProcessBuilder(cmdarray) + .inheritIO() + .directory(new File(Directories.workingDir)); + return processBuilder.start().waitFor(); + } + + public static void usleep(int microseconds) throws InterruptedException { + TimeUnit.MICROSECONDS.sleep(microseconds); + } + + public static void sleep(int seconds) throws InterruptedException { + TimeUnit.SECONDS.sleep(seconds); + } + + public static String getEnv(String name) { + return System.getProperty(name, System.getenv(name)); + } + + public static int clearEnv(String name) { + System.clearProperty(name); + return 0; + } + + public static int setEnv(String name, String value, int shouldOverwrite) { + System.setProperty(name, value); + return 0; + } + + public static String getEnvPair(int index) { + if (index >= environmentVariableNames.size()) { + return null; + } else { + String name = environmentVariableNames.get(index); + return name + "=" + environmentVariables.get(name); + } + } + + public static void exit(int exitCode) { + System.exit(exitCode); + } + + // This may not be adequate but simple enough for basic cases + private static List parseCommand(final String command) { + List commandWithArgs = new ArrayList<>(); + int start = 0; + boolean inQuotes = false; + for (int current = 0; current < command.length(); current++) { + if (isUnescapedDoubleQuotes(command, current)) { + inQuotes = !inQuotes; + } + + boolean atLastChar = current == command.length() - 1; + if (atLastChar) { + commandWithArgs.add(unescapeDoubleQuotes(trimDoubleQuotes(command.substring(start)))); + } else if (command.charAt(current) == ' ' && !inQuotes) { + commandWithArgs.add(unescapeDoubleQuotes(trimDoubleQuotes(command.substring(start, current)))); + start = current + 1; + } + } + return commandWithArgs; + } + + private static boolean isUnescapedDoubleQuotes(final String str, final int index) { + return str.charAt(index) == '"' && (index == 0 || str.charAt(index - 1) != '\\'); + } + + private static String unescapeDoubleQuotes(String str) { + return str.replaceAll("\\\\\"", "\""); + } + + private static String trimDoubleQuotes(String str) { + if (str.startsWith("\"") && str.endsWith("\"")) { + return str.substring(1, str.length() - 1); + } else { + return str; + } + } +} diff --git a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java index c5bc8e0d1..83664ba96 100644 --- a/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java +++ b/runtime/src/main/java/io/github/mmhelloworld/idris2boot/runtime/Runtime.java @@ -1,7 +1,6 @@ package io.github.mmhelloworld.idris2boot.runtime; import java.nio.channels.Channels; -import java.util.List; import java.util.function.Function; import java.util.stream.Stream; @@ -12,19 +11,19 @@ public final class Runtime { private static final ChannelIo stdout = new ChannelIo(null, Channels.newChannel(System.out)); private static final ChannelIo stderr = new ChannelIo(null, Channels.newChannel(System.err)); private static final ThreadLocal ERROR_NUMBER = ThreadLocal.withInitial(() -> 0); - private static List programArgs; + private static IdrisList programArgs; private Runtime() { } - public static List getProgramArgs() { + public static IdrisList getProgramArgs() { return programArgs; } public static void setProgramArgs(String[] args) { // "java" as the executable name for the first argument to conform to Idris' getArgs function - programArgs = Stream.concat(Stream.of("java"), Stream.of(args)) - .collect(toList()); + programArgs = IdrisList.fromIterable(Stream.concat(Stream.of("java"), Stream.of(args)) + .collect(toList())); } public static ChannelIo getStdin() { @@ -104,4 +103,5 @@ public static Thread fork(Function action) { thread.start(); return thread; } + } diff --git a/src/Compiler/Jvm/Codegen.idr b/src/Compiler/Jvm/Codegen.idr index 0a8e2653d..21de7ff72 100644 --- a/src/Compiler/Jvm/Codegen.idr +++ b/src/Compiler/Jvm/Codegen.idr @@ -238,6 +238,22 @@ mutual asmCast inferredObjectType returnType when isTailCall $ asmReturn returnType + assembleExpr isTailCall returnType expr@(NmCon fc (NS ["Prelude"] (UN "Nil")) _ _) = do + Field GetStatic idrisNilClass "INSTANCE" + "Lio/github/mmhelloworld/idris2boot/runtime/IdrisList$Nil;" + asmCast idrisNilType returnType + when isTailCall $ asmReturn returnType + + assembleExpr isTailCall returnType expr@(NmCon fc (NS ["Prelude"] (UN "::")) _ [head, tail]) = do + New idrisConsClass + Dup + assembleExpr False inferredObjectType head + assembleExpr False idrisListType tail + InvokeMethod InvokeSpecial idrisConsClass "" + "(Ljava/lang/Object;Lio/github/mmhelloworld/idris2boot/runtime/IdrisList;)V" False + asmCast idrisConsType returnType + when isTailCall $ asmReturn returnType + assembleExpr isTailCall returnType expr@(NmCon fc name tag args) = do let fileName = fst $ getSourceLocation expr let constructorClassName = jvmSimpleName name @@ -705,12 +721,13 @@ mutual let variableTypes = SortedMap.values !(loadClosures declaringScope scope) maybe (Pure ()) id parameterValueExpr let invokeDynamicDescriptor = getMethodDescriptor $ MkInferredFunctionType lambdaInterfaceType variableTypes - let implementationMethodReturnType = getLambdaImplementationMethodReturnType lambdaType + let isExtracted = isJust parameterValueExpr + let implementationMethodReturnType = if isExtracted then lambdaBodyReturnType + else getLambdaImplementationMethodReturnType lambdaType let implementationMethodDescriptor = getMethodDescriptor $ MkInferredFunctionType implementationMethodReturnType (variableTypes ++ toList parameterTypes) let instantiatedMethodDescriptor = getMethodDescriptor $ MkInferredFunctionType implementationMethodReturnType $ toList parameterTypes - let isExtracted = isJust parameterValueExpr let methodPrefix = if isExtracted then "extracted" else "lambda" lambdaMethodName <- getLambdaImplementationMethodName methodPrefix let interfaceMethodName = getLambdaInterfaceMethodName lambdaType @@ -736,7 +753,9 @@ mutual let labelEnd = methodEndLabel addLambdaStartLabel scope labelStart maybe (Pure ()) (\parentScopeIndex => updateScopeStartLabel parentScopeIndex labelStart) (parentIndex scope) - let lambdaReturnType = if lambdaType == ThunkLambda then thunkType else inferredObjectType + let lambdaReturnType = if isExtracted then lambdaBodyReturnType + else if lambdaType == ThunkLambda then thunkType + else inferredObjectType assembleExpr True lambdaReturnType expr addLambdaEndLabel scope labelEnd maybe (Pure ()) (\parentScopeIndex => updateScopeEndLabel parentScopeIndex labelEnd) (parentIndex scope) diff --git a/src/Compiler/Jvm/InferredType.idr b/src/Compiler/Jvm/InferredType.idr index 276662e35..1c751935f 100644 --- a/src/Compiler/Jvm/InferredType.idr +++ b/src/Compiler/Jvm/InferredType.idr @@ -58,6 +58,24 @@ arrayListClass = "java/util/ArrayList" arrayListType : InferredType arrayListType = IRef arrayListClass +idrisListClass : String +idrisListClass = "io/github/mmhelloworld/idris2boot/runtime/IdrisList" + +idrisListType : InferredType +idrisListType = IRef idrisListClass + +idrisNilClass : String +idrisNilClass = "io/github/mmhelloworld/idris2boot/runtime/IdrisList$Nil" + +idrisNilType : InferredType +idrisNilType = IRef idrisNilClass + +idrisConsClass : String +idrisConsClass = "io/github/mmhelloworld/idris2boot/runtime/IdrisList$Cons" + +idrisConsType : InferredType +idrisConsType = IRef idrisConsClass + isPrimitive : InferredType -> Bool isPrimitive IBool = True isPrimitive IByte = True diff --git a/src/Compiler/Jvm/Optimizer.idr b/src/Compiler/Jvm/Optimizer.idr index 4f4f3669f..228447382 100644 --- a/src/Compiler/Jvm/Optimizer.idr +++ b/src/Compiler/Jvm/Optimizer.idr @@ -623,14 +623,16 @@ mutual let valueExpr = NmLocal (getFC expr) (UN generatedVariableName) parentScope <- getScope !getCurrentScopeIndex inferExprLamWithParameterType (Just (UN generatedVariableName, valueType)) - (Just (inferValue parentScope valueType)) + (Just (inferValue parentScope generatedVariableName valueType)) (substituteVariableSubMethodBody valueExpr expr) where - inferValue : Scope -> InferredType -> Asm () - inferValue enclosingScope valueType = do + inferValue : Scope -> String -> InferredType -> Asm () + inferValue enclosingScope variableName valueType = do lambdaScopeIndex <- getCurrentScopeIndex updateCurrentScopeIndex (index enclosingScope) inferExpr valueType value + createVariable variableName + addVariableType variableName valueType updateCurrentScopeIndex lambdaScopeIndex inferExprLam _ parameterName expr = inferExprLamWithParameterType ((\name => (name, inferredObjectType)) <$> parameterName) Nothing expr