diff --git a/CHANGELOG.md b/CHANGELOG.md index 86dc732a7845..59451bd7d65e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -498,6 +498,7 @@ - [IGV can jump to JMH sources & more][4008] - [Basic support of VSCode integration][4014] - [Sync language server with file system after VCS restore][4020] +- [Introducing Meta.atom_with_hole][4023] - [Report failures in name resolution in type signatures][4030] [3227]: https://github.com/enso-org/enso/pull/3227 @@ -580,6 +581,7 @@ [4008]: https://github.com/enso-org/enso/pull/4008 [4014]: https://github.com/enso-org/enso/pull/4014 [4020]: https://github.com/enso-org/enso/pull/4020 +[4023]: https://github.com/enso-org/enso/pull/4023 [4030]: https://github.com/enso-org/enso/pull/4030 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso index 8b0b1b3b31d0..237ae78c51ff 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso @@ -7,7 +7,7 @@ import project.Data.Vector.Vector import project.Error.Error import project.Function.Function import project.Nothing.Nothing -import project.Runtime.Unsafe +import project.Meta from project.Data.Boolean import Boolean, True, False @@ -185,13 +185,19 @@ type List filter : (Filter_Condition | (Any -> Boolean)) -> Vector Any filter self filter = case filter of _ : Filter_Condition -> self.filter filter.to_predicate - predicate : Function -> - go_filter list = case list of - Nil -> Nil - Cons h t -> - rest = go_filter t - if predicate h then Cons h rest else rest - go_filter self + predicate : Function -> case self of + Nil -> Nil + Cons x xs -> if predicate x . not then @Tail_Call xs.filter filter else + go list fill = case list of + Nil -> fill Nil + Cons h t -> if predicate h . not then @Tail_Call go t fill else + res = Meta.atom_with_hole (Cons h _) + fill res.value + @Tail_Call go t res.fill + + res = Meta.atom_with_hole (Cons x _) + go xs res.fill + res.value ## Applies a function to each element of the list, returning the list of results. @@ -209,9 +215,18 @@ type List map self f = case self of Nil -> Nil Cons h t -> - res = Cons (f h) Nil - map_helper t res f - res + go : List -> Any -> (Any -> Any) -> Nothing + go list fill = case list of + Nil -> fill Nil + Cons h t -> + v = f h + res = Meta.atom_with_hole (Cons v _) + fill res.value + @Tail_Call go t res.fill + v = f h + res = Meta.atom_with_hole (Cons v _) + go t res.fill + res.value ## Applies a function to each element of the list. @@ -281,7 +296,16 @@ type List take_start : Integer -> List take_start self count = if count <= 0 then Nil else case self of Nil -> Nil - Cons a b -> Cons a (b.take_start count-1) + Cons a b -> + go c l fill = if c <= 0 then fill Nil else case l of + Nil -> fill Nil + Cons a b -> + res = Meta.atom_with_hole (Cons a _) + fill res.value + @Tail_Call go c-1 b res.fill + res = Meta.atom_with_hole (Cons a _) + go count-1 b res.fill + res.value ## Get the first element from the list. @@ -319,12 +343,21 @@ type List example_init = Examples.list.init init : List ! Empty_Error init self = - init_fn x y = case y of - Nil -> Nil - Cons a b -> Cons x (init_fn a b) case self of Nil -> Error.throw Empty_Error - Cons a b -> init_fn a b + Cons _ Nil -> Nil + Cons a b -> + go l fill = case l of + Cons x xs -> case xs of + Nil -> fill Nil + Cons _ _ -> + res = Meta.atom_with_hole (Cons x _) + fill res.value + @Tail_Call go xs res.fill + + res = Meta.atom_with_hole (Cons a _) + go b res.fill + res.value ## Get the last element of the list. @@ -369,6 +402,13 @@ type List builder.append elem builder.to_vector + to_text : Text + to_text self = + go l t = case l of + Nil -> t + "Nil" + Cons x xs -> @Tail_Call go xs (t + "Cons " + x.to_text + " ") + go self "" + ## UNSTABLE An error representing that the list is empty. @@ -379,21 +419,3 @@ type Empty_Error to_display_text : Text to_display_text self = "The List is empty." -## PRIVATE - A helper for the `map` function. - - Arguments: - - list: The list to map over. - - cons: The current field to set. - - f: The function to apply to the value. - - Uses unsafe field mutation under the hood, to rewrite `map` in - a tail-recursive manner. The mutation is purely internal and does not leak - to the user-facing API. -map_helper : List -> Any -> (Any -> Any) -> Nothing -map_helper list cons f = case list of - Nil -> Unsafe.set_atom_field cons 1 Nil - Cons h t -> - res = Cons (f h) Nil - Unsafe.set_atom_field cons 1 res - @Tail_Call map_helper t res f diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index 439ebab8de1f..4a6652e27d68 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -273,6 +273,17 @@ get_constructor_name atom_constructor = @Builtin_Method "Meta.get_constructor_na new_atom : Any -> Array -> Atom new_atom constructor fields = @Builtin_Method "Meta.new_atom" +## PRIVATE + + Constructs a new atom with a "hole". Returns an object with `value` and + `fill` properties. Value contains the created atom and `fill` holds a + function to "fill the hole" later. + + Arguments: + - factory: a function that takes the "hole" element and returns newly created atom +atom_with_hole : (Any -> Atom) -> Any +atom_with_hole factory = @Builtin_Method "Meta.atom_with_hole_builtin" + ## UNSTABLE ADVANCED diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso deleted file mode 100644 index 6dbee4406a78..000000000000 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso +++ /dev/null @@ -1,18 +0,0 @@ -## Unsafe operations. - A container for unsafe operations that operate based on implementation - details of the language. - -import project.Any.Any -import project.Data.Numbers.Integer -import project.Meta.Atom - -## PRIVATE - - Sets the atom field at the provided index to have the provided value. - - Arguments: - - atom: The atom to set the field in. - - index: The index of the field to set (zero-based). - - value: The value to set the field at index to. -set_atom_field : Atom -> Integer -> Any -> Atom -set_atom_field atom index value = @Builtin_Method "Unsafe.set_atom_field" diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java new file mode 100644 index 000000000000..0ac40ed24f1f --- /dev/null +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/ListBenchmarks.java @@ -0,0 +1,107 @@ +package org.enso.interpreter.bench.benchmarks.semantic; + +import java.io.ByteArrayOutputStream; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.BenchmarkParams; +import org.openjdk.jmh.infra.Blackhole; + + +@BenchmarkMode(Mode.AverageTime) +@Fork(1) +@Warmup(iterations = 3) +@Measurement(iterations = 5) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class ListBenchmarks { + private final int LENGTH_OF_EXPERIMENT = 1_000_000; + private Value list; + private Value plusOne; + private Value self; + private Value sum; + private Value oldSum; + + @Setup + public void initializeBenchmark(BenchmarkParams params) throws Exception { + var ctx = Context.newBuilder() + .allowExperimentalOptions(true) + .allowIO(true) + .allowAllAccess(true) + .logHandler(new ByteArrayOutputStream()) + .option( + "enso.languageHomeOverride", + Paths.get("../../distribution/component").toFile().getAbsolutePath() + ).build(); + + var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", ""); + var code = """ + from Standard.Base.Data.List.List import Cons, Nil + import Standard.Base.IO + + plus_one list = list.map (x -> x + 1) + + sum list acc = + case list of + Nil -> acc + Cons x xs -> @Tail_Call sum xs acc+x + + generator n = + go x v l = if x > n then l else + @Tail_Call go x+1 v+1 (Cons v l) + go 1 1 Nil + """; + + var module = ctx.eval(SrcUtil.source(benchmarkName, code)); + + this.self = module.invokeMember("get_associated_type"); + Function getMethod = (name) -> module.invokeMember("get_method", self, name); + + Value longList = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT); + + this.plusOne = getMethod.apply("plus_one"); + this.sum = getMethod.apply("sum"); + + switch (benchmarkName) { + case "mapOverList": { + this.list = longList; + this.oldSum = sum.execute(self, longList, 0); + if (!this.oldSum.fitsInLong()) { + throw new AssertionError("Expecting a number " + this.oldSum); + } + break; + } + default: + throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark()); + } + } + + @Benchmark + public void mapOverList(Blackhole matter) { + performBenchmark(matter); + } + + private void performBenchmark(Blackhole hole) throws AssertionError { + var newList = plusOne.execute(self, list); + var newSum = sum.execute(self, newList, 0); + + var result = newSum.asLong() - oldSum.asLong(); + if (result != LENGTH_OF_EXPERIMENT) { + throw new AssertionError("Unexpected result " + result); + } + hole.consume(result); + } +} + diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java index 7597439b02a8..2213245e1857 100644 --- a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/VectorBenchmarks.java @@ -1,4 +1,4 @@ - package org.enso.interpreter.bench.benchmarks.semantic; +package org.enso.interpreter.bench.benchmarks.semantic; import java.io.ByteArrayOutputStream; import java.nio.file.Paths; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java new file mode 100644 index 000000000000..82409941cde8 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/AtomWithAHoleNode.java @@ -0,0 +1,196 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.data.Vector; +import org.enso.interpreter.runtime.error.PanicException; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.profiles.ValueProfile; +import org.enso.interpreter.node.callable.InvokeCallableNode; +import org.enso.interpreter.runtime.state.State; + +@BuiltinMethod( + type = "Meta", + name = "atom_with_hole_builtin", + description = "Creates a new atom with given constructor and fields.", + autoRegister = false) +public abstract class AtomWithAHoleNode extends Node { + + static AtomWithAHoleNode build() { + return AtomWithAHoleNodeGen.create(); + } + + abstract Object execute(VirtualFrame frame, Object factory); + + static InvokeCallableNode callWithHole() { + return InvokeCallableNode.build( + new CallArgumentInfo[] {new CallArgumentInfo()}, + InvokeCallableNode.DefaultsExecutionMode.EXECUTE, + InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED); + } + + @Specialization + Object doExecute( + VirtualFrame frame, + Object factory, + @Cached("callWithHole()") InvokeCallableNode iop, + @Cached SwapAtomFieldNode swapNode + ) { + var ctx = EnsoContext.get(this); + var lazy = new HoleInAtom(); + var result = iop.execute(factory, frame, State.create(ctx), new Object[] { lazy }); + if (result instanceof Atom atom) { + var index = swapNode.findHoleIndex(atom, lazy); + if (index >= 0) { + var function = swapNode.createFn(lazy); + lazy.init(atom, index, function); + return lazy; + } + } + throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(result), this); + } + + @ExportLibrary(InteropLibrary.class) + static final class HoleInAtom implements TruffleObject { + Atom result; + int index; + Function function; + + HoleInAtom() { + } + + void init(Atom result, int index, Function function) { + this.result = result; + this.index = index; + this.function = function; + } + + @ExportMessage boolean hasMembers() { + return true; + } + + @ExportMessage boolean isMemberReadable(String member) { + return switch (member) { + case "value", "fill" -> true; + default -> false; + }; + } + + @ExportMessage Object getMembers(boolean includeInternal) throws UnsupportedMessageException { + return new Array("value", "fill"); + } + + @ExportMessage + Object readMember(String name) throws UnknownIdentifierException { + if ("value".equals(name)) { + return result; + } + if ("fill".equals(name)) { + return function; + } + throw UnknownIdentifierException.create(name); + } + + @ExportMessage + String toDisplayString(boolean pure) { + return "Meta.atom_with_hole"; + } + } + static final class SwapAtomFieldNode extends RootNode { + private final FunctionSchema schema; + private final ValueProfile sameAtom = ValueProfile.createClassProfile(); + @CompilerDirectives.CompilationFinal + private int lastIndex = -1; + + private SwapAtomFieldNode() { + super(null); + this.schema = new FunctionSchema(FunctionSchema.CallerFrameAccess.NONE, new ArgumentDefinition[]{ + new ArgumentDefinition(0, "lazy", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE) + }, new boolean[]{ + true, false + }, new CallArgumentInfo[0]); + } + + static SwapAtomFieldNode create() { + return new SwapAtomFieldNode(); + } + + int findHoleIndex(Atom atom, HoleInAtom lazy) { + var arr = atom.getFields(); + if (lastIndex >= 0 && lastIndex < arr.length) { + if (arr[lastIndex] == lazy) { + return lastIndex; + } + } + int index = findHoleIndexLoop(arr, lazy); + if (index == -1) { + return -1; + } + if (lastIndex == -1) { + lastIndex = index; + CompilerDirectives.transferToInterpreterAndInvalidate(); + return index; + } else { + if (lastIndex != -2) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + lastIndex = -2; + } + } + return index; + } + + @CompilerDirectives.TruffleBoundary + private int findHoleIndexLoop(Object[] arr, HoleInAtom lazy) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] == lazy) { + return i; + } + } + return -1; + } + + Function createFn(HoleInAtom lazy) { + var preArgs = new Object[]{lazy, null}; + return new Function( + getCallTarget(), + null, + schema, + preArgs, + new Object[]{} + ); + } + + @Override + public Object execute(VirtualFrame frame) { + var args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + if (args[0] instanceof HoleInAtom lazy) { + var fields = lazy.result.getFields(); + var newValue = args[1]; + if (fields[lazy.index] == lazy) { + fields[lazy.index] = newValue; + } + return newValue; + } + return EnsoContext.get(this).getBuiltins().nothing(); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/SetAtomFieldNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/SetAtomFieldNode.java deleted file mode 100644 index 22d72bc8748a..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/unsafe/SetAtomFieldNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.unsafe; - -import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.runtime.callable.atom.Atom; - -@BuiltinMethod( - type = "Unsafe", - name = "set_atom_field", - description = "Unsafely, in place, sets the value of an atom field by index.", - autoRegister = false) -public class SetAtomFieldNode extends Node { - Atom execute(Atom atom, long index, Object value) { - atom.getFields()[(int) index] = value; - return atom; - } -} diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java index b9c5c7569e3d..529179bf814d 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java @@ -613,69 +613,6 @@ public String toString() { } } - // TODO[PM]: Re-enable (https://www.pivotaltracker.com/story/show/183854585) - @Test - @Ignore - public void unsafeRecursiveAtom() throws Exception { - Engine eng = Engine.newBuilder() - .allowExperimentalOptions(true) - .option( - RuntimeOptions.LANGUAGE_HOME_OVERRIDE, - Paths.get("../../test/micro-distribution/component").toFile().getAbsolutePath() - ).build(); - Context ctx = Context.newBuilder() - .engine(eng) - .allowIO(true) - .allowHostClassLoading(true) - .allowHostClassLookup((c) -> true) - .build(); - final Map langs = ctx.getEngine().getLanguages(); - org.junit.Assert.assertNotNull("Enso found: " + langs, langs.get("enso")); - - final URI onceUri = new URI("memory://once.enso"); - final Source onesSrc = Source.newBuilder("enso", """ - import Standard.Base.Runtime.Unsafe - - type Gen - Empty - Generator a:Int tail:Gen - - ones : Gen - ones = - g = Gen.Generator 1 Gen.Empty - Unsafe.set_atom_field g 1 g - g - - next g = case g of - Gen.Generator a tail -> a - Gen.Empty -> -1 - """, "ones.enso") - .uri(onceUri) - .buildLiteral(); - - var module = ctx.eval(onesSrc); - var ones = module.invokeMember("eval_expression", "ones"); - var next = module.invokeMember("eval_expression", "next"); - - - final var dbg = Debugger.find(eng); - final var values = new HashSet(); - try (var session = dbg.startSession((event) -> { - final DebugValue gVariable = findDebugValue(event, "g"); - if (gVariable != null) { - final String str = gVariable.toDisplayString(false); - assertNotNull("The string shall always be computed for " + gVariable, str); - values.add(str); - } - event.getSession().suspendNextExecution(); - })) { - session.suspendNextExecution(); - var one = next.execute(ones); - Assert.assertEquals("First element from list of ones", 1, one.asInt()); - } - Assert.assertEquals("Some values of g variable found: " + values, 1, values.size()); - } - private static DebugValue findDebugValue(SuspendedEvent event, final String n) throws DebugException { for (var v : event.getTopStackFrame().getScope().getDeclaredValues()) { if (v.getName().contains(n)) { diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java new file mode 100644 index 000000000000..bce689576a5f --- /dev/null +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/ListTest.java @@ -0,0 +1,165 @@ +package org.enso.interpreter.test; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Map; +import org.enso.polyglot.RuntimeOptions; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Language; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + +public class ListTest { + private Context ctx; + private final int size = 100_000; + private Value generator; + private Value plusOne; + private Value evenOnes; + private Value taken; + private Value init; + private Value asVector; + private Value asText; + + @Before + public void prepareCtx() throws Exception { + this.ctx = Context.newBuilder() + .allowExperimentalOptions(true) + .allowIO(true) + .allowAllAccess(true) + .logHandler(new ByteArrayOutputStream()) + .option( + RuntimeOptions.LANGUAGE_HOME_OVERRIDE, + Paths.get("../../distribution/component").toFile().getAbsolutePath() + ).build(); + + final Map langs = ctx.getEngine().getLanguages(); + assertNotNull("Enso found: " + langs, langs.get("enso")); + + final String code = """ + from Standard.Base.Data.List.List import Cons, Nil + + init list = list.init + + taken list n = list.take_start n + + even_ones list = list.filter (x -> x % 2 == 0) + + plus_one list = list.map (x -> x + 1) + + as_vector list = list.to_vector + + as_text list = list.to_text + + generator n = + go x v l = if x > n then l else + @Tail_Call go x+1 v+1 (Cons v l) + go 1 1 Nil + """; + + generator = evalCode(code, "generator"); + plusOne = evalCode(code, "plus_one"); + evenOnes = evalCode(code, "even_ones"); + taken = evalCode(code, "taken"); + init = evalCode(code, "init"); + asVector = evalCode(code, "as_vector"); + asText = evalCode(code, "as_text"); + } + + @Test + public void mapPlusOneAndIterate() throws Exception { + var list = generator.execute(size); + assertEquals("Size is OK", size, list.invokeMember("length").asInt()); + + var values = new ArrayList(); + { + var it = plusOne.execute(list); + + for (long i = 0; i < size; i++) { + var e = it.getMember("x"); + assertTrue("All numbers are numbers, but " + e + " is not", e.isNumber()); + var n = e.as(Number.class); + assertNotNull("All numbers can be seen as java.lang.Number", n); + var b = new BigInteger(n.toString()); + assertNotNull("Each Enso number can be parsed as big integer", b); + assertEquals("Textual values are the same", n.toString(), b.toString()); + values.add(b); + it = it.getMember("xs"); + } + } + assertEquals("Two hundred numbers collected", size, values.size()); + for (int i = 1; i < values.size(); i++) { + var prev = values.get(i - 1); + var next = values.get(i); + + assertEquals("Each value is accurate", next.add(BigInteger.ONE), prev); + } + } + + @Test + public void filterEvenOnes() throws Exception { + var list = generator.execute(size); + assertLength("Generated", size, list); + var even = evenOnes.execute(list); + assertLength("Half the size", size / 2, even); + } + + @Test + public void takeHalf() throws Exception { + var list = generator.execute(size); + assertLength("Generated", size, list); + var even = taken.execute(list, size / 2); + assertLength("Half the size", size / 2, even); + } + + @Test + public void initAllButLast() throws Exception { + var list = generator.execute(size); + assertLength("Generated all", size, list); + var shorterByOne = init.execute(list); + assertLength("Last element is gone", size - 1, shorterByOne); + } + + @Test + public void toVector() throws Exception { + var list = generator.execute(size); + assertLength("Generated all", size, list); + var vec = asVector.execute(list); + assertEquals("It's vector", "Vector", vec.getMetaObject().getMetaSimpleName()); + assertTrue("And an array like object", vec.hasArrayElements()); + assertEquals("The same size remains", size, vec.getArraySize()); + } + + @Test + public void toText() throws Exception { + var list = generator.execute(size); + assertLength("Generated all", size, list); + var str = asText.execute(list); + assertTrue("It is a string", str.isString()); + } + + private Value evalCode(final String code, final String methodName) throws URISyntaxException { + final var testName = "test.enso"; + final URI testUri = new URI("memory://" + testName); + final Source src = Source.newBuilder("enso", code, testName) + .uri(testUri) + .buildLiteral(); + var module = ctx.eval(src); + var powers = module.invokeMember("eval_expression", methodName); + return powers; + } + + private void assertLength(String msg, long expected, Value list) { + var actual = list.invokeMember("length"); + assertTrue("Size fits into number", actual.fitsInLong()); + assertEquals(msg, expected, actual.asLong()); + } +} diff --git a/project/FrgaalJavaCompiler.scala b/project/FrgaalJavaCompiler.scala index 2dca3850c0cb..7e3188799c10 100644 --- a/project/FrgaalJavaCompiler.scala +++ b/project/FrgaalJavaCompiler.scala @@ -118,7 +118,11 @@ object FrgaalJavaCompiler { } val (withTarget, noTarget) = sources0.partition(checkTarget) - val in = findUnder(3, noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path]) + val in = if (noTarget.isEmpty) { + None + } else { + Some(findUnder(3, noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path])) + } val generated = if (withTarget.isEmpty) { None } else { @@ -133,8 +137,9 @@ object FrgaalJavaCompiler { def storeArray(name: String, values : Seq[String]) = { values.zipWithIndex.foreach { case (value, idx) => ensoProperties.setProperty(s"$name.$idx", value) } } - - ensoProperties.setProperty("input", in.toString()) + if (in.isDefined) { + ensoProperties.setProperty("input", in.get.toString()) + } if (generated.isDefined) { ensoProperties.setProperty("generated", generated.get.toString()) } diff --git a/test/Tests/src/Data/List_Spec.enso b/test/Tests/src/Data/List_Spec.enso index 5e129aef979e..00e825394c93 100644 --- a/test/Tests/src/Data/List_Spec.enso +++ b/test/Tests/src/Data/List_Spec.enso @@ -115,6 +115,10 @@ spec = Test.group "List" <| Test.specify "should allow getting the tail of the list with `.tail`" <| l.tail . should_equal (List.Cons 2 (List.Cons 3 List.Nil)) empty.tail.should_fail_with Empty_Error + Test.specify "single element list.init yields Nil" <| + (List.Cons 1 List.Nil).init . should_equal List.Nil + Test.specify "two element list.init yields one element" <| + (List.Cons 1 (List.Cons 2 List.Nil)).init . should_equal (List.Cons 1 List.Nil) Test.specify "should allow getting the init of the list with `.init`" <| l.init . should_equal (List.Cons 1 (List.Cons 2 List.Nil)) empty.init.should_fail_with Empty_Error diff --git a/test/Tests/src/Semantic/Meta_Spec.enso b/test/Tests/src/Semantic/Meta_Spec.enso index bbf83e5b8fb5..eb512bab7f77 100644 --- a/test/Tests/src/Semantic/Meta_Spec.enso +++ b/test/Tests/src/Semantic/Meta_Spec.enso @@ -11,6 +11,7 @@ polyglot java import java.util.Locale as JavaLocale from Standard.Test import Test, Test_Suite import Standard.Test.Extensions +from Standard.Base.Error.Common import Uninitialized_State type My_Type Value foo bar baz @@ -20,154 +21,222 @@ My_Type.my_method self = self.foo + self.bar + self.baz type Test_Type Value x -spec = Test.group "Meta-Value Manipulation" <| - Test.specify "should allow manipulating unresolved symbols" <| - sym = .does_not_exist - meta_sym = Meta.meta sym - meta_sym.name.should_equal "does_not_exist" - new_sym = meta_sym . rename "my_method" - object = My_Type.Value 1 2 3 - new_sym object . should_equal 6 - Test.specify "should allow manipulating atoms" <| - atom = My_Type.Value 1 "foo" Nothing - meta_atom = Meta.meta atom - meta_atom.constructor.value.should_equal My_Type.Value - meta_atom.fields.should_equal [1, "foo", Nothing] - Meta.meta (meta_atom.constructor.value) . new [1, "foo", Nothing] . should_equal atom - Test.specify "should allow getting a value's constructor's name" <| - Meta.meta List.Nil . constructor . name . should_equal "Nil" - Meta.meta (List.Cons 1 List.Nil) . constructor . name . should_equal "Cons" - Test.specify "should allow getting a value's constructor's fields" <| - Meta.meta List.Nil . constructor . fields . should_equal [] - Meta.meta (List.Cons 1 List.Nil) . constructor . fields . should_equal ["x", "xs"] - Test.specify "should allow creating atoms from atom constructors" <| - atom_1 = Meta.new_atom My_Type.Value [1,"foo", Nothing] - (Meta.meta atom_1).constructor.value . should_equal My_Type.Value - atom_2 = Meta.new_atom My_Type.Value [1,"foo", Nothing].to_array - (Meta.meta atom_2).constructor.value . should_equal My_Type.Value - Test.specify "should correctly return representations of different classes of objects" <| - Meta.meta 1 . should_equal (Meta.Primitive.Value 1) - Meta.meta "foo" . should_equal (Meta.Primitive.Value "foo") - Test.specify "should allow manipulation of error values" <| - err = Error.throw "My Error" - meta_err = Meta.meta err - meta_err.is_a Meta.Error.Value . should_be_true - meta_err.value . should_equal "My Error" - Test.specify "should allow checking if a value is of a certain type" <| - 1.is_a Any . should_be_true - 1.2.is_a Any . should_be_true - (My_Type.Value 1 "foo" Nothing).is_a Any . should_be_true - - Array.is_a Array . should_be_false - [].to_array.is_a Array . should_be_true - [].to_array.is_a Decimal . should_be_false - [1,2,3].is_a Vector . should_be_true - [1,2,3].is_a Array . should_be_false - - Boolean.is_a Boolean . should_be_false - True.is_a Boolean . should_be_true - False.is_a Boolean . should_be_true - True.is_a Integer . should_be_false - - "".is_a Text . should_be_true - "".is_a Decimal . should_be_false - - 1.is_a Array . should_be_false - 1.is_a Integer . should_be_true - 1.is_a Number . should_be_true - 1.is_a Decimal . should_be_false - 1.is_a Date . should_be_false - - 1.0.is_a Number . should_be_true - 1.0.is_a Decimal . should_be_true - 1.0.is_a Integer . should_be_false - 1.0.is_a Text . should_be_false - - random_gen = Random.new - Meta.is_a random_gen Random . should_be_true - Meta.is_a random_gen Integer . should_be_false - - (My_Type.Value 1 "foo" Nothing).is_a My_Type.Value . should_be_true - (My_Type.Value 1 "foo" Nothing).is_a Test_Type.Value . should_be_false - (My_Type.Value 1 "foo" Nothing).is_a Number . should_be_false - - err = Error.throw "Error Value" - 1.is_a Error . should_be_false - err.is_a Error . should_be_true - err.is_a Text . should_be_false - Meta.is_a err Error . should_be_true - Meta.is_a err Text . should_be_false - - Meta.is_a Date.now Date . should_be_true - Meta.is_a Date_Time.now Date_Time . should_be_true - Meta.is_a Date_Time.now Date . should_be_false - Meta.is_a Time_Of_Day.now Time_Of_Day . should_be_true - Meta.is_a Time_Of_Day.now Date . should_be_false - Meta.is_a Date_Time.now.zone Time_Zone . should_be_true - Meta.is_a Date_Time.now.zone Date . should_be_false - - Test.specify "should allow for returning the type of a value" <| - n_1 = Meta.type_of 42 - n_1 . should_equal_type Integer - n_1 . should_not_equal_type Decimal - - n_2 = Meta.type_of 2.81 - n_2 . should_equal_type Decimal - n_2 . should_not_equal_type Integer - - n_3 = Meta.type_of (JLong.MAX_VALUE * 2) - n_3 . should_equal_type Integer - n_3 . should_not_equal_type Decimal - - v_tpe = Meta.type_of [1,2,3] - v_tpe . should_equal_type Vector - v_tpe . should_not_equal_type Array - Meta.type_of [1,2,3].to_array . should_equal_type Array - - Meta.type_of "foo" . should_equal_type Text - Meta.type_of "0" . should_not_equal_type Integer - - Meta.type_of True . should_equal_type Boolean - Meta.type_of False . should_not_equal_type Any - - (Meta.type_of Date.now) . should_equal_type Date - (Meta.type_of Date.now) . should_not_equal_type Date_Time - (Meta.type_of Date_Time.now) . should_equal_type Date_Time - (Meta.type_of Date_Time.now) . should_not_equal_type Date - (Meta.type_of Time_Of_Day.now) . should_equal_type Time_Of_Day - (Meta.type_of Time_Of_Day.now) . should_not_equal_type Date - (Meta.type_of Date_Time.now.zone) . should_equal_type Time_Zone - (Meta.type_of Date_Time.now.zone) . should_not_equal_type Date - (Meta.type_of Time_Zone.local) . should_equal_type Time_Zone - (Meta.type_of Time_Zone.system) . should_equal_type Time_Zone - - list = ArrayList.new - list.add 123 - list_tpe = Meta.type_of list - list_tpe . should_not_equal_type JObject - list_tpe . should_equal_type ArrayList - - e = IOException.new "meh" - e_tpe = Meta.type_of e - e_tpe . should_equal_type IOException - e_tpe . should_not_equal_type JException - - Test.specify "should correctly handle Java values" <| - java_meta = Meta.meta Random.new - java_meta . should_be_a Meta.Polyglot.Value - java_meta . get_language . should_equal Meta.Language.Java - - Test.specify "should correctly handle equality of Java values" <| - a = JavaLocale.new "en" - b = JavaLocale.new "en" - c = JavaLocale.new "pl" - - a==a . should_be_true - a==b . should_be_true - a==c . should_be_false - - (Test_Type.Value a)==(Test_Type.Value a) . should_be_true - (Test_Type.Value a)==(Test_Type.Value b) . should_be_true - (Test_Type.Value a)==(Test_Type.Value c) . should_be_false +spec = + Test.group "Meta-Value Manipulation" <| + Test.specify "should allow manipulating unresolved symbols" <| + sym = .does_not_exist + meta_sym = Meta.meta sym + meta_sym.name.should_equal "does_not_exist" + new_sym = meta_sym . rename "my_method" + object = My_Type.Value 1 2 3 + new_sym object . should_equal 6 + Test.specify "should allow manipulating atoms" <| + atom = My_Type.Value 1 "foo" Nothing + meta_atom = Meta.meta atom + meta_atom.constructor.value.should_equal My_Type.Value + meta_atom.fields.should_equal [1, "foo", Nothing] + Meta.meta (meta_atom.constructor.value) . new [1, "foo", Nothing] . should_equal atom + Test.specify "should allow getting a value's constructor's name" <| + Meta.meta List.Nil . constructor . name . should_equal "Nil" + Meta.meta (List.Cons 1 List.Nil) . constructor . name . should_equal "Cons" + Test.specify "should allow getting a value's constructor's fields" <| + Meta.meta List.Nil . constructor . fields . should_equal [] + Meta.meta (List.Cons 1 List.Nil) . constructor . fields . should_equal ["x", "xs"] + Test.specify "should allow creating atoms from atom constructors" <| + atom_1 = Meta.new_atom My_Type.Value [1,"foo", Nothing] + (Meta.meta atom_1).constructor.value . should_equal My_Type.Value + atom_2 = Meta.new_atom My_Type.Value [1,"foo", Nothing].to_array + (Meta.meta atom_2).constructor.value . should_equal My_Type.Value + Test.specify "should correctly return representations of different classes of objects" <| + Meta.meta 1 . should_equal (Meta.Primitive.Value 1) + Meta.meta "foo" . should_equal (Meta.Primitive.Value "foo") + Test.specify "should allow manipulation of error values" <| + err = Error.throw "My Error" + meta_err = Meta.meta err + meta_err.is_a Meta.Error.Value . should_be_true + meta_err.value . should_equal "My Error" + Test.specify "should allow checking if a value is of a certain type" <| + 1.is_a Any . should_be_true + 1.2.is_a Any . should_be_true + (My_Type.Value 1 "foo" Nothing).is_a Any . should_be_true + + Array.is_a Array . should_be_false + [].to_array.is_a Array . should_be_true + [].to_array.is_a Decimal . should_be_false + [1,2,3].is_a Vector . should_be_true + [1,2,3].is_a Array . should_be_false + + Boolean.is_a Boolean . should_be_false + True.is_a Boolean . should_be_true + False.is_a Boolean . should_be_true + True.is_a Integer . should_be_false + + "".is_a Text . should_be_true + "".is_a Decimal . should_be_false + + 1.is_a Array . should_be_false + 1.is_a Integer . should_be_true + 1.is_a Number . should_be_true + 1.is_a Decimal . should_be_false + 1.is_a Date . should_be_false + + 1.0.is_a Number . should_be_true + 1.0.is_a Decimal . should_be_true + 1.0.is_a Integer . should_be_false + 1.0.is_a Text . should_be_false + + random_gen = Random.new + Meta.is_a random_gen Random . should_be_true + Meta.is_a random_gen Integer . should_be_false + + (My_Type.Value 1 "foo" Nothing).is_a My_Type.Value . should_be_true + (My_Type.Value 1 "foo" Nothing).is_a Test_Type.Value . should_be_false + (My_Type.Value 1 "foo" Nothing).is_a Number . should_be_false + + err = Error.throw "Error Value" + 1.is_a Error . should_be_false + err.is_a Error . should_be_true + err.is_a Text . should_be_false + Meta.is_a err Error . should_be_true + Meta.is_a err Text . should_be_false + + Meta.is_a Date.now Date . should_be_true + Meta.is_a Date_Time.now Date_Time . should_be_true + Meta.is_a Date_Time.now Date . should_be_false + Meta.is_a Time_Of_Day.now Time_Of_Day . should_be_true + Meta.is_a Time_Of_Day.now Date . should_be_false + Meta.is_a Date_Time.now.zone Time_Zone . should_be_true + Meta.is_a Date_Time.now.zone Date . should_be_false + + Test.specify "should allow for returning the type of a value" <| + n_1 = Meta.type_of 42 + n_1 . should_equal_type Integer + n_1 . should_not_equal_type Decimal + + n_2 = Meta.type_of 2.81 + n_2 . should_equal_type Decimal + n_2 . should_not_equal_type Integer + + n_3 = Meta.type_of (JLong.MAX_VALUE * 2) + n_3 . should_equal_type Integer + n_3 . should_not_equal_type Decimal + + v_tpe = Meta.type_of [1,2,3] + v_tpe . should_equal_type Vector + v_tpe . should_not_equal_type Array + Meta.type_of [1,2,3].to_array . should_equal_type Array + + Meta.type_of "foo" . should_equal_type Text + Meta.type_of "0" . should_not_equal_type Integer + + Meta.type_of True . should_equal_type Boolean + Meta.type_of False . should_not_equal_type Any + + (Meta.type_of Date.now) . should_equal_type Date + (Meta.type_of Date.now) . should_not_equal_type Date_Time + (Meta.type_of Date_Time.now) . should_equal_type Date_Time + (Meta.type_of Date_Time.now) . should_not_equal_type Date + (Meta.type_of Time_Of_Day.now) . should_equal_type Time_Of_Day + (Meta.type_of Time_Of_Day.now) . should_not_equal_type Date + (Meta.type_of Date_Time.now.zone) . should_equal_type Time_Zone + (Meta.type_of Date_Time.now.zone) . should_not_equal_type Date + (Meta.type_of Time_Zone.local) . should_equal_type Time_Zone + (Meta.type_of Time_Zone.system) . should_equal_type Time_Zone + + list = ArrayList.new + list.add 123 + list_tpe = Meta.type_of list + list_tpe . should_not_equal_type JObject + list_tpe . should_equal_type ArrayList + + e = IOException.new "meh" + e_tpe = Meta.type_of e + e_tpe . should_equal_type IOException + e_tpe . should_not_equal_type JException + + Test.specify "should correctly handle Java values" <| + java_meta = Meta.meta Random.new + java_meta . should_be_a Meta.Polyglot.Value + java_meta . get_language . should_equal Meta.Language.Java + + Test.specify "should correctly handle equality of Java values" <| + a = JavaLocale.new "en" + b = JavaLocale.new "en" + c = JavaLocale.new "pl" + + a==a . should_be_true + a==b . should_be_true + a==c . should_be_false + + (Test_Type.Value a)==(Test_Type.Value a) . should_be_true + (Test_Type.Value a)==(Test_Type.Value b) . should_be_true + (Test_Type.Value a)==(Test_Type.Value c) . should_be_false + + Test.group "Atom with holes" <| + Test.specify "construct and fill" <| + pair = Meta.atom_with_hole (e -> My_Type.Value 1 e 3) + + atom = pair.value + fill = pair.fill + + Meta.is_atom atom . should_be_true + + atom.foo . should_equal 1 + atom.baz . should_equal 3 + case atom.bar of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + + fill 2 + atom.bar . should_equal 2 + + fill 10 # no change + atom.bar . should_equal 2 + + Test.specify "fail if atom_with_hole isn't used" <| + key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <| + Meta.atom_with_hole (_ -> My_Type.Value 1 2 3) + case key of + t : My_Type -> + t.foo . should_equal 1 + t.bar . should_equal 2 + t.baz . should_equal 3 + + Test.specify "fail if non-atom is created" <| + key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <| + Meta.atom_with_hole (_ -> 2) + case key of + t : Number -> + t . should_equal 2 + + Test.specify "only one atom_with_hole is used" <| + pair = Meta.atom_with_hole (e -> My_Type.Value e e e) + atom = pair.value + fill = pair.fill + + Meta.is_atom atom . should_be_true + + case atom.foo of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + case atom.baz of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + case atom.bar of + n : Number -> Test.fail "Shouldn't be number yet: "+n + _ -> Nothing + + fill 2 + atom.foo . should_equal 2 + + fill 10 # no change + atom.foo . should_equal 2 + + case atom.baz of + n : Number -> Test.fail "Not changed to number: "+n + _ -> Nothing + case atom.bar of + n : Number -> Test.fail "Not changed to number: "+n + _ -> Nothing main = Test_Suite.run_main spec diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso deleted file mode 100644 index 74beb65048f3..000000000000 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Unsafe.enso +++ /dev/null @@ -1 +0,0 @@ -set_atom_field atom index value = @Builtin_Method "Unsafe.set_atom_field"