diff --git a/build.sbt b/build.sbt index 99a98826a2308c1ebc895520b7cf567e194a2399..616f8ae565dc6f55800399f5ce5a86b3cdab94e5 100644 --- a/build.sbt +++ b/build.sbt @@ -3,3 +3,5 @@ name := "clcompat" version := "0.1" scalaVersion := "2.13.3" + +libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.0" % "test" diff --git a/src/main/scala/accumulators/Accumulators.scala b/src/main/scala/accumulators/Accumulators.scala new file mode 100644 index 0000000000000000000000000000000000000000..4ecff8405e3992cf7e3d687baf8f5ca706ecbfed --- /dev/null +++ b/src/main/scala/accumulators/Accumulators.scala @@ -0,0 +1,137 @@ +// Copyright (c) 2019 EPITA Research and Development Laboratory +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package accumulators + +case class successor[A](init:A){ + var current:A = init + + def apply(f:A=>A):A = { + current = f(current) + current + } +} + +object Accumulators { + def withCollector[A](f: (A => Unit) => Unit): List[A] = { + var objects: List[A] = Nil + + def collect(obj: A): Unit = { + objects = obj :: objects + } + + f(collect) + objects.reverse + } + + def withSetCollector[A](f: (A => Unit) => Unit): Set[A] = { + var objects: Set[A] = Set() + + def collect(obj: A): Unit = { + objects = objects+obj + } + + f(collect) + objects + } + + def withNconc[A](f: (List[A] => Unit) => Unit): List[A] = { + withCollector[List[A]] { nconc => + f(nconc) + }.flatten + } + + + def withReducer[A](init:A,folder:(A,A)=>A)(f:(A=>Unit)=>Unit):A = { + var acc = init + def reduce(obj:A):Unit = { + acc = folder(acc,obj) + } + f(reduce) + acc + } + + def withMaximizer[A <% Ordered[A]](init: A)(f: (A => Unit) => Unit): A = { + withReducer(init, (a:A,b:A)=>if (a < b) b else a)(f) + } + + def withMinimizer[A <% Ordered[A]](init: A)(f: (A => Unit) => Unit): A = { + withReducer(init, (a:A,b:A)=>if (a > b) b else a)(f) + } + + def withSummer[A](init:A,plus:(A,A)=>A)(f:(A=>Unit)=>Unit): A = { + withReducer(init,plus)(f) + } + + def withCounter(f:(()=>Unit)=>Unit):Int = { + var i = 0 + def reduce():Unit = { + i = i+1 + } + f(reduce) + i + } + + def withOutputToString(f:(String=>Unit)=>Unit):String = { + withOutputToString(f, java.nio.charset.StandardCharsets.UTF_8) + } + + def withOutputToString(f:(String=>Unit)=>Unit, + encoding:java.nio.charset.Charset):String = { + import java.io.{ByteArrayOutputStream, PrintWriter} + val baos = new ByteArrayOutputStream() + val pw = new PrintWriter(baos) + var str = "" + + def printer(str: String): Unit = { + pw.print(str) + } + + try { + f(printer) + pw.flush() + str = new String(baos.toByteArray, encoding) + } finally pw.close() + str + } + + def main(argv: Array[String]): Unit = { + + import java.io.{ByteArrayOutputStream, PrintWriter} + import java.nio.charset.StandardCharsets + + def printToOutputString(f: PrintWriter => Unit): String = { + val baos = new ByteArrayOutputStream() + val pw = new PrintWriter(baos) + try { + f(pw) + pw.flush() + new String(baos.toByteArray, StandardCharsets.UTF_8) + } finally pw.close() + } + + val helloWorld = printToOutputString { pw => + pw.print("hello ") + pw.print("world") + } + println(s"helloWorld=$helloWorld") + } +} \ No newline at end of file diff --git a/src/main/scala/cl/CLcompat.scala b/src/main/scala/cl/CLcompat.scala new file mode 100644 index 0000000000000000000000000000000000000000..fcc0bcf4fdbb813e57bb04a2b5b390d1b6172150 --- /dev/null +++ b/src/main/scala/cl/CLcompat.scala @@ -0,0 +1,84 @@ +// Copyright (c) 2019,20 EPITA Research and Development Laboratory +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package cl + +object CLcompat { + + def prog1[A,B](val1:A, code2: => B):A = { + code2 // eval for side effect + val1 + } + def prog2[A,B,C](val1:A, val2:B, code2: => C):B = { + code2 // eval for side effect + val2 + } + def every[A, B](L1: List[A], L2: List[B])(f: (A, B) => Boolean): Boolean = { + (L1, L2) match { + case (a :: as, b :: bs) => f(a, b) && every(as, bs)(f) + case (_, _) => true + } + } + + def merge[A](clauses1: List[A], clauses2: List[A], clauseLess:(A,A)=>Boolean): List[A] = { + // Merge two lists which are already in sorted order, according to clauseLess + // into a new list which is likewise in sorted order. + def loop(clauses1: List[A], clauses2: List[A], acc: List[A]): List[A] = { + (clauses1, clauses2) match { + case (Nil, Nil) => acc.reverse + case (a :: as, Nil) => loop(as, Nil, a :: acc) + case (Nil, b :: bs) => loop(Nil, bs, b :: acc) + case (a :: as, b :: bs) if clauseLess(a, b) => loop(as, b :: bs, a :: acc) + case (a :: as, b :: bs) => loop(a :: as, bs, b :: acc) + } + } + + loop(clauses1, clauses2, Nil) + } + + def mapcan[A1, A2, B](f: (A1, A2) => List[B], L1: List[A1], L2: List[A2]): List[B] = { + (L1, L2).zipped.flatMap(f) + } + + def block[A](body:(A=>Nothing)=>A):A = { + // CL like block/return, the name of the return() function is provided + // by the caller. + // Usage: block{ ret => ... ret(someValue) ...} + + // extending Exception with NoStackTrace prevents throwing the + // exception from computing the stacktrace and storing the information. + // We don't need a stacktrace because the purpose of this exception + // is simply to perform a non-local exit. + import scala.util.control.NoStackTrace + + class NonLocalExit(val data:A,val ident:(A=>Nothing)=>A) extends Exception with NoStackTrace {} + + def ret(data:A):Nothing = { + throw new NonLocalExit(data,body) + } + try{ + body(ret) + } + catch{ + case nonLocalExit: NonLocalExit if nonLocalExit.ident eq body => nonLocalExit.data + } + } +} diff --git a/src/test/scala/AccumulatorSuite.scala b/src/test/scala/AccumulatorSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..fde3e0c580b5763aad043d0ac21d41d25016e0d8 --- /dev/null +++ b/src/test/scala/AccumulatorSuite.scala @@ -0,0 +1,95 @@ +// Copyright (c) 2019 EPITA Research and Development Laboratory +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import org.scalatest.funsuite.AnyFunSuite + +import accumulators.Accumulators._ +import accumulators._ + +class AccumulatorSuite extends AnyFunSuite { + test("successor"){ + + val primes = List(11,13,17,19) + val odds = Array(3,5,7) + + + val s1 = successor(0) + val accumulated = for { + w <- primes + x = s1(_+3) + s2 = successor(false) + y <- odds + z = s2(!_) + } yield (w,x,y,z) + + assert( accumulated == List((11,3,3,true), (11,3,5,false), (11,3,7,true), (13,6,3,true), (13,6,5,false), (13,6,7,true), + (17,9,3,true), (17,9,5,false), (17,9,7,true), (19,12,3,true), (19,12,5,false), (19,12,7,true))) + } + test("collector") { + assert(List(1, 2, 3) == withCollector[Int](collect => + (1 to 3).foreach { i => collect(i) } + )) + } + test("maximize") { + assert(10 == withMaximizer[Int](0)( + maximize => { + maximize(1) + maximize(3) + maximize(10) + maximize(4) + })) + } + test("minimize") { + assert(-10 == withMinimizer[Int](0)( + maximize => { + maximize(-1) + maximize(-3) + maximize(-10) + maximize(-4) + })) + } + test("nconc") { + assert(List(1, 2, 3, 4, 5, 6) == withNconc[Int]( + nconc => { + nconc(List(1, 2, 3)) + nconc(List(4, 5)) + nconc(List(6)) + } + )) + } + test("summer") { + assert(8 == withSummer[Int](0, (a: Int, b: Int) => a + b)(summing => { + summing(1) + summing(3) + summing(4) + })) + } + test("printer") { + assert("hello world" == withOutputToString(printer => { + printer("hello") + printer(" ") + printer("world") + })) + } + +} + + diff --git a/src/test/scala/CLcompatTestSuite.scala b/src/test/scala/CLcompatTestSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..29e689eb78f4405eeb9510b57465e67938beae99 --- /dev/null +++ b/src/test/scala/CLcompatTestSuite.scala @@ -0,0 +1,51 @@ +// Copyright (c) 2020 EPITA Research and Development Laboratory +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, +// and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +import org.scalatest.funsuite.AnyFunSuite +import cl.CLcompat._ + +class CLcompatTestSuite extends AnyFunSuite { + type T= Int=>Nothing + assert(42 == block{ret:T => ret(42)}) + assert(43 == block{ret:T => 43}) + assert(44 == block{ret1:T => + block{ret2:T => + ret1(44) + }}) + + assert(45 == block{ret1:T => + block{ret2:T => + ret1(45) + } + ret1(46)}) + + assert(48 == block{ret1:T => + block{ret2:T => + ret2(47) + } + ret1(48)}) + + assert(49 == block{ret1:T => + block{ret2:T => + ret1(49) + } + assert(false, "this line should never be reached") + }) +}