├── benchmark ├── build.sbt └── src │ └── main │ └── scala │ └── io │ └── github │ └── metarank │ └── cfor │ └── CforBenchmark.scala ├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── .scalafmt.conf ├── macros ├── src │ ├── main │ │ ├── scala-2 │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── metarank │ │ │ │ └── cfor │ │ │ │ ├── package.scala │ │ │ │ └── Syntax.scala │ │ └── scala-3 │ │ │ └── io │ │ │ └── github │ │ │ └── metarank │ │ │ └── cfor │ │ │ └── package.scala │ └── test │ │ ├── scala-2.13 │ │ └── io │ │ │ └── github │ │ │ └── metarank │ │ │ └── cfor │ │ │ └── Cfor213Test.scala │ │ └── scala │ │ └── io │ │ └── github │ │ └── metarank │ │ └── cfor │ │ └── CforTest.scala └── build.sbt ├── .github └── workflows │ └── ci.yml ├── README.md └── LICENSE /benchmark/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(JmhPlugin) -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.8.0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | project/project 3 | project/target 4 | target 5 | .DS_STORE 6 | .git 7 | macros/project 8 | macros/target 9 | benchmark/project 10 | benchmark/target 11 | .bsp -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | runner.dialect = scala212 2 | fileOverride { 3 | "glob:**/src/main/scala-3/**" { 4 | runner.dialect = scala3 5 | } 6 | } 7 | 8 | style = defaultWithAlign 9 | maxColumn = 120 10 | version = 3.5.9 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") 2 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.15") 3 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") 4 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") 5 | -------------------------------------------------------------------------------- /macros/src/main/scala-2/io/github/metarank/cfor/package.scala: -------------------------------------------------------------------------------- 1 | package io.github.metarank 2 | 3 | import scala.language.experimental.macros 4 | 5 | package object cfor { 6 | def cfor[A](init: A)(test: A => Boolean, next: A => A)(body: A => Unit): Unit = macro Syntax.cforMacro[A] 7 | 8 | def cfor[A](array: Array[A])(body: A => Unit): Unit = macro Syntax.cforArrayMacro[A] 9 | 10 | def cfor(r: Range)(body: Int => Unit): Unit = macro Syntax.cforRangeMacro 11 | } 12 | -------------------------------------------------------------------------------- /macros/src/test/scala-2.13/io/github/metarank/cfor/Cfor213Test.scala: -------------------------------------------------------------------------------- 1 | package io.github.metarank.cfor 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | import scala.collection.mutable 7 | 8 | class Cfor213Test extends AnyFlatSpec with Matchers { 9 | it should "iterate over array with anonymous function" in { 10 | val l = mutable.ListBuffer[Int]() 11 | val arr = Array(0, 1, 2, 3, 4) 12 | cfor(arr)(l.append) 13 | l.toList shouldBe List(0, 1, 2, 3, 4) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macros/build.sbt: -------------------------------------------------------------------------------- 1 | name := "cfor" 2 | 3 | publishMavenStyle := true 4 | 5 | publishTo := sonatypePublishToBundle.value 6 | 7 | licenses := Seq("APL2" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt")) 8 | 9 | homepage := Some(url("https://github.com/metarank/cfor")) 10 | scmInfo := Some( 11 | ScmInfo( 12 | url("https://github.com/metarank/cfor"), 13 | "scm:git@github.com:metarank/cfor.git" 14 | ) 15 | ) 16 | developers := List( 17 | Developer(id = "romangrebennikov", name = "Roman Grebennikov", email = "grv@dfdx.me", url = url("https://dfdx.me/")) 18 | ) 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.platform }} 15 | strategy: 16 | matrix: 17 | java: [11, 17] 18 | platform: [ubuntu-22.04] 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | lfs: true 23 | - name: Set up JDK 24 | uses: actions/setup-java@v1 25 | with: 26 | java-version: ${{ matrix.java }} 27 | 28 | - name: Cache maven packages 29 | uses: actions/cache@v2 30 | env: 31 | cache-name: cache-sbt 32 | with: 33 | path: ~/.m2 ~/.coursier ~/.cache/coursier ~/.ivy2 ~/.sbt 34 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/pom.xml') }} 35 | restore-keys: | 36 | ${{ runner.os }}-build-${{ env.cache-name }}- 37 | ${{ runner.os }}-build- 38 | ${{ runner.os }}- 39 | - name: Run tests 40 | run: sbt "+test" 41 | -------------------------------------------------------------------------------- /benchmark/src/main/scala/io/github/metarank/cfor/CforBenchmark.scala: -------------------------------------------------------------------------------- 1 | package io.github.metarank.cfor 2 | 3 | import org.openjdk.jmh.annotations._ 4 | 5 | import java.util.concurrent.TimeUnit 6 | import scala.util.Random 7 | 8 | @State(Scope.Benchmark) 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 11 | @Warmup(iterations = 5, time = 1) 12 | @Measurement(iterations = 10, time = 1) 13 | @Fork(value = 1) 14 | class CforBenchmark { 15 | @Param(Array("1000")) 16 | var length: Int = _ 17 | 18 | var array: Array[Int] = _ 19 | 20 | @Setup 21 | def setup = { 22 | array = new Array[Int](length) 23 | var i = 0 24 | while (i < length) { 25 | array(i) = if (Random.nextBoolean()) 1 else 0 26 | i += 1 27 | } 28 | } 29 | 30 | @Benchmark 31 | def cforSum() = { 32 | var sum = 0 33 | cfor(0)(_ < array.length, _ + 1) { i => sum += array(i) } 34 | sum 35 | } 36 | 37 | @Benchmark 38 | def cforForeachSum() = { 39 | var sum = 0 40 | cfor(array) { sum += _ } 41 | sum 42 | } 43 | 44 | @Benchmark 45 | def scalaCollectionsSum() = { 46 | array.sum 47 | } 48 | 49 | @Benchmark 50 | def scalaForeachSum() = { 51 | var sum = 0 52 | for { 53 | value <- array 54 | } { 55 | sum += value 56 | } 57 | sum 58 | } 59 | 60 | @Benchmark 61 | def scalaWhileSum() = { 62 | var sum = 0 63 | var i = 0 64 | while (i < array.length) { 65 | sum += array(i) 66 | i += 1 67 | } 68 | sum 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /macros/src/test/scala/io/github/metarank/cfor/CforTest.scala: -------------------------------------------------------------------------------- 1 | package io.github.metarank.cfor 2 | 3 | import org.scalatest.flatspec.AnyFlatSpec 4 | import org.scalatest.matchers.should.Matchers 5 | 6 | import scala.collection.mutable 7 | 8 | class CforTest extends AnyFlatSpec with Matchers { 9 | it should "simple cfor" in { 10 | val l = mutable.ListBuffer[Int]() 11 | cfor(0)(_ < 5, _ + 1) { x => 12 | l.append(x) 13 | } 14 | l.toList shouldBe List(0, 1, 2, 3, 4) 15 | } 16 | 17 | it should "nested cfor" in { 18 | val s = mutable.Set.empty[Int] 19 | cfor(0)(_ < 10, _ + 1) { x => 20 | cfor(10)(_ < 100, _ + 10) { y => 21 | s.add(x + y) 22 | } 23 | } 24 | s.toSet shouldBe (10 to 99).toSet 25 | } 26 | 27 | it should "symbol collision cfor" in { 28 | val b = mutable.ArrayBuffer.empty[Int] 29 | cfor(0)(_ < 3, _ + 1) { x => 30 | cfor(0)(_ < 2, _ + 1) { y => 31 | val x = y 32 | b += x 33 | } 34 | } 35 | b.toList shouldBe List(0, 1, 0, 1, 0, 1) 36 | } 37 | 38 | it should "functions with side effects in cfor" in { 39 | val b = mutable.ArrayBuffer.empty[Int] 40 | var v = 0 41 | cfor(0)({ v += 1; _ < 3 }, { v += 10; _ + 1 }) { 42 | v += 100 43 | x => { 44 | b += x 45 | } 46 | } 47 | v shouldBe 111 48 | b.toList shouldBe List(0, 1, 2) 49 | } 50 | 51 | it should "functions with side effects function values in cfor" in { 52 | val b = mutable.ArrayBuffer.empty[Int] 53 | var v = 0 54 | def test: Int => Boolean = { v += 1; _ < 3 } 55 | def incr: Int => Int = { v += 10; _ + 1 } 56 | def body: Int => Unit = { 57 | v += 100 58 | x => { 59 | b += x 60 | } 61 | } 62 | cfor(0)(test, incr)(body) 63 | v shouldBe 111 64 | b.toList shouldBe List(0, 1, 2) 65 | } 66 | 67 | it should "functions with side effects function by-value params in cfor" in { 68 | val b = mutable.ArrayBuffer.empty[Int] 69 | var v = 0 70 | def run(test: => (Int => Boolean), incr: => (Int => Int), body: => (Int => Unit)): Unit = { 71 | cfor(0)(test, incr)(body) 72 | } 73 | run( 74 | { v += 1; _ < 3 }, 75 | { v += 10; _ + 1 }, { 76 | v += 100 77 | x => { 78 | b += x 79 | } 80 | } 81 | ) 82 | v shouldBe 111 83 | b.toList shouldBe List(0, 1, 2) 84 | } 85 | 86 | it should "capture value in closure" in { 87 | val b1 = collection.mutable.ArrayBuffer.empty[() => Int] 88 | cfor(0)(_ < 3, _ + 1) { x => 89 | b1 += (() => x) 90 | } 91 | val b2 = collection.mutable.ArrayBuffer[() => Int]() 92 | var i = 0 93 | while (i < 3) { 94 | b2 += (() => i) 95 | i += 1 96 | } 97 | b1.map(_.apply()).toList shouldBe b2.map(_.apply()).toList 98 | } 99 | 100 | it should "capture value in inner class" in { 101 | val b = collection.mutable.ArrayBuffer[Int]() 102 | cfor(0)(_ < 3, _ + 1) { x => 103 | { 104 | class A { def f = x } 105 | b += (new A().f) 106 | } 107 | } 108 | b.toList shouldBe List(0, 1, 2) 109 | } 110 | 111 | it should "type tree bug fixed" in { 112 | val arr = Array((1, 2), (2, 3), (4, 5)) 113 | var t = 0 114 | cfor(0)(_ < arr.length, _ + 1) { i => 115 | val (a, b) = arr(i) 116 | t += a + 2 * b 117 | } 118 | t shouldBe 27 119 | } 120 | 121 | it should "destructure tuples" in { 122 | var t = 0 123 | cfor((0, 0))(_._1 < 3, t => (t._1 + 1, t._2 + 2)) { case (a, b) => 124 | t += 3 * a + b 125 | } 126 | t shouldBe 15 127 | } 128 | 129 | it should "iterate over array" in { 130 | val l = mutable.ListBuffer[Int]() 131 | val arr = Array(0, 1, 2, 3, 4) 132 | cfor(arr) { x => 133 | l.append(x) 134 | } 135 | l.toList shouldBe List(0, 1, 2, 3, 4) 136 | } 137 | 138 | it should "sum over array with anonymous function" in { 139 | var sum = 0 140 | val arr = Array(0, 1, 2, 3, 4) 141 | cfor(arr)(sum += _) 142 | sum shouldBe 10 143 | } 144 | 145 | it should "functions with side effects in cfor(arr)" in { 146 | var sum = 0 147 | var v = 0 148 | val arr = Array(0, 1, 2, 3, 4) 149 | cfor(arr) { 150 | v += 100 151 | x => { 152 | sum += x 153 | } 154 | } 155 | sum shouldBe 10 156 | v shouldBe 100 157 | } 158 | 159 | it should "cforRange(1 until 4)" in { 160 | var t = 0 161 | cfor(1 until 4) { x => 162 | t += x 163 | } 164 | t shouldBe 6 165 | } 166 | 167 | it should "cforRange(0 to 10 by 2)" in { 168 | var t = 0 169 | cfor(0 to 10 by 2) { x => 170 | t += x 171 | } 172 | t shouldBe 30 173 | } 174 | 175 | it should "cforRange(3 to 1 by -1)" in { 176 | var t = 0 177 | cfor(3 to 1 by -1) { x => 178 | t += x 179 | } 180 | t shouldBe 6 181 | } 182 | 183 | it should "cforRange(0 to 0 by -1)" in { 184 | var t = 0 185 | cfor(0 to 0 by -1) { x => 186 | t += 1 187 | } 188 | t shouldBe 1 189 | } 190 | 191 | // it should "functions with side effects in cfor(range)" in { 192 | // var t = 0 193 | // var v = 0 194 | // cfor(1 until 4) { 195 | // v += 100 196 | // x => { 197 | // t += x 198 | // } 199 | // } 200 | // t shouldBe 6 201 | // v shouldBe 100 202 | // } 203 | } 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cfor: fast java-style for and foreach loop in scala 2 | 3 | [![CI Status](https://github.com/metarank/cfor/workflows/CI/badge.svg)](https://github.com/metarank/cfor/actions) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.metarank/cfor_2.13/badge.svg?style=plastic)](https://maven-badges.herokuapp.com/maven-central/io.github.metarank/cfor_2.13) 5 | [![License: Apache 2](https://img.shields.io/badge/License-Apache2-green.svg)](https://opensource.org/licenses/Apache-2.0) 6 | 7 | This tiny project is a cfor macro inspired by the one 8 | originally implemented in [Typelevel spire](https://github.com/typelevel/spire/blob/master/macros/src/main/scala/spire/macros/Syntax.scala) 9 | library. Supports Scala 2.12 and 2.13 and has zero dependencies. 10 | 11 | ## Example 12 | 13 | An index loop: 14 | ```scala 15 | import io.github.metarank.cfor._ 16 | def sum(values: Array[Int]) = { 17 | var result = 0 18 | cfor(0)(_ < values.length, _ + 1) { i => result += values(i) } 19 | result 20 | } 21 | ``` 22 | 23 | A foreach loop: 24 | ```scala 25 | import io.github.metarank.cfor._ 26 | def sum(values: Array[Int]) = { 27 | var result = 0 28 | cfor(values) { result += _ } 29 | result 30 | } 31 | ``` 32 | 33 | A range loop: 34 | ```scala 35 | import io.github.metarank.cfor._ 36 | def sum(values: Array[Int]) = { 37 | var result = 0 38 | cfor(0 to values.length by 2) { i => result += values(i) } 39 | result 40 | } 41 | ``` 42 | 43 | 44 | All these `cfor` calls are expanded into a while loop with no boxing, no extra allocations and 45 | with the same performance as a pure java for loop. 46 | 47 | ## Installation 48 | 49 | Available on maven central for scala 2.12 and 2.13 50 | ```scala 51 | libraryDependencies += "io.github.metarank" %% "cfor" % "0.2" 52 | ``` 53 | 54 | ## Purpose 55 | 56 | In Java, there is a well-known for-loop: 57 | ```java 58 | public int sum(int[] values) { 59 | int result = 0; 60 | for (int i=0; i Int 9 | case NumericRange[Long] => Long 10 | 11 | inline def cfor[A](inline init: A)(inline test: A => Boolean, inline next: A => A)(inline body: A => Unit): Unit = 12 | ${ cforImpl('init, 'test, 'next, 'body) } 13 | 14 | inline def cfor[A](inline array: Array[A])(body: A => Unit): Unit = 15 | cfor(0)(_ < array.length, _ + 1)(i => body(array(i))) 16 | 17 | inline def cfor[R <: RangeLike](inline r: R)(inline body: RangeElem[R] => Unit): Unit = 18 | ${ cforRangeMacroGen('r, 'body) } 19 | 20 | 21 | //-------------------------------------------------------------------------- 22 | // 23 | // Code from below are based on file 24 | // `core/src/main/scala-3/spire/syntax/macros/cforMacros.scala` 25 | // From project Spire (https://github.com/typelevel/spire) 26 | // 27 | // Original copyright and license noted 28 | // 29 | 30 | /* 31 | * **********************************************************************\ 32 | * * Project ** 33 | * * ______ ______ __ ______ ____ ** 34 | * * / ____/ / __ / / / / __ / / __/ (c) 2011-2021 ** 35 | * * / /__ / /_/ / / / / /_/ / / /_ ** 36 | * * /___ / / ____/ / / / __ / / __/ Erik Osheim, Tom Switzer ** 37 | * * ____/ / / / / / / / | | / /__ ** 38 | * * /_____/ /_/ /_/ /_/ |_| /____/ All rights reserved. ** 39 | * * ** 40 | * * Redistribution and use permitted under the MIT license. ** 41 | * * ** 42 | * \*********************************************************************** 43 | */ 44 | 45 | import scala.PartialFunction.cond 46 | import scala.quoted.* 47 | 48 | private def cforImpl[R: Type](init: Expr[R], test: Expr[R => Boolean], next: Expr[R => R], body: Expr[R => Unit])(using 49 | Quotes 50 | ): Expr[Unit] = 51 | import quotes.reflect.* 52 | 53 | def code(testRef: Expr[R => Boolean], nextRef: Expr[R => R], bodyRef: Expr[R => Unit]): Expr[Unit] = '{ 54 | var index = $init 55 | while $testRef(index) do 56 | $bodyRef(index) 57 | index = $nextRef(index) 58 | } 59 | 60 | letFunc("test", test)(t => letFunc("next", next)(n => letFunc("body", body)(b => code(t, n, b)))) 61 | end cforImpl 62 | 63 | private def cforRangeMacroGen[R <: RangeLike: Type](r: Expr[R], body: Expr[RangeElem[R] => Unit])(using 64 | quotes: Quotes 65 | ): Expr[Unit] = 66 | import quotes.reflect.* 67 | 68 | r match 69 | case '{ $r: Range } => RangeForImpl.ofInt(r, body.asExprOf[Int => Unit]) 70 | case '{ $r: NumericRange[Long] } => RangeForImpl.ofLong(r, body.asExprOf[Long => Unit]) 71 | case '{ $r } => report.error(s"Ineligible Range type ", r); '{} 72 | 73 | end cforRangeMacroGen 74 | 75 | private object RangeForImpl: 76 | type Code[T] = Expr[T => Unit] => Expr[Unit] 77 | type Test[T] = (Expr[T], Expr[T]) => Expr[Boolean] 78 | 79 | def ofInt(r: Expr[Range], body: Expr[Int => Unit])(using Quotes): Expr[Unit] = 80 | val code: Code[Int] = r match 81 | case '{ ($i: Int) to $j } => loopCode(i, j, 1, (x, y) => '{ $x <= $y }) 82 | case '{ ($i: Int) to $j by ${ Expr(k) } } if k > 0 => loopCode(i, j, k, (x, y) => '{ $x <= $y }) 83 | case '{ ($i: Int) to $j by ${ Expr(k) } } if k < 0 => loopCode(i, j, k, (x, y) => '{ $x >= $y }) 84 | case '{ ($i: Int) to $j by ${ Expr(k) } } if k == 0 => zeroStride(r) 85 | case '{ ($i: Int) until $j } => loopCode(i, j, 1, (x, y) => '{ $x < $y }) 86 | case '{ ($i: Int) until $j by ${ Expr(k) } } if k > 0 => loopCode(i, j, k, (x, y) => '{ $x < $y }) 87 | case '{ ($i: Int) until $j by ${ Expr(k) } } if k < 0 => loopCode(i, j, k, (x, y) => '{ $x > $y }) 88 | case '{ ($i: Int) until $j by ${ Expr(k) } } if k == 0 => zeroStride(r) 89 | case _ => deOpt(r, '{ $r.foreach($body) }) 90 | 91 | letFunc("body", body)(code) 92 | end ofInt 93 | 94 | def ofLong(r: Expr[NumericRange[Long]], body: Expr[Long => Unit])(using quotes: Quotes): Expr[Unit] = 95 | val code: Code[Long] = r match 96 | case '{ ($i: Long) to $j } => loopCode(i, j, 1L, (x, y) => '{ $x <= $y }) 97 | case '{ ($i: Long) to $j by ${ Expr(k) } } if k > 0 => loopCode(i, j, k, (x, y) => '{ $x <= $y }) 98 | case '{ ($i: Long) to $j by ${ Expr(k) } } if k < 0 => loopCode(i, j, k, (x, y) => '{ $x >= $y }) 99 | case '{ ($i: Long) to $j by ${ Expr(k) } } if k == 0 => zeroStride(r) 100 | case '{ ($i: Long) until $j } => loopCode(i, j, 1L, (x, y) => '{ $x < $y }) 101 | case '{ ($i: Long) until $j by ${ Expr(k) } } if k > 0 => loopCode(i, j, k, (x, y) => '{ $x < $y }) 102 | case '{ ($i: Long) until $j by ${ Expr(k) } } if k < 0 => loopCode(i, j, k, (x, y) => '{ $x > $y }) 103 | case '{ ($i: Long) until $j by ${ Expr(k) } } if k == 0 => zeroStride(r) 104 | case _ => deOpt(r, '{ $r.foreach($body) }) 105 | 106 | letFunc("body", body)(code) 107 | 108 | end ofLong 109 | 110 | def loopCode[T: Type: ToExpr: CanLoop](i: Expr[T], j: Expr[T], s: T, test: Test[T])(using Quotes): Code[T] = 111 | body => 112 | '{ 113 | var index = $i 114 | val limit = $j 115 | while ${ test('index, 'limit) } do 116 | $body(index) 117 | index = ${ 'index.stepBy(Expr(s)) } 118 | } 119 | 120 | def zeroStride[T, R](orig: Expr[R])(using Quotes): Code[T] = _ => 121 | import quotes.reflect.* 122 | report.error("zero stride", orig) 123 | '{} 124 | 125 | def deOpt[T, R](orig: Expr[R], foreach: Expr[Unit])(using Quotes): Code[T] = _ => 126 | import quotes.reflect.* 127 | report.warning(s"defaulting to foreach, can not optimise range expression", orig) 128 | foreach 129 | 130 | trait CanLoop[T]: 131 | extension (x: Expr[T]) def stepBy(y: Expr[T])(using Quotes): Expr[T] 132 | 133 | object CanLoop: 134 | given CanLoop[Int] with 135 | extension (x: Expr[Int]) def stepBy(y: Expr[Int])(using Quotes): Expr[Int] = '{ $x + $y } 136 | 137 | given CanLoop[Long] with 138 | extension (x: Expr[Long]) def stepBy(y: Expr[Long])(using Quotes): Expr[Long] = '{ $x + $y } 139 | 140 | end RangeForImpl 141 | 142 | 143 | /** 144 | * Equivalent to `'{ val name: A => B = $rhs; ${in('name)} }`, except when `rhs` is a function literal, then equivalent 145 | * to `in(rhs)`. 146 | * 147 | * This allows inlined function arguments to perform side-effects only once before their first evaluation, while still 148 | * avoiding the creation of closures for function literal arguments. 149 | */ 150 | private def letFunc[A, B, C](using Quotes)(name: String, rhs: Expr[A => B])(in: Expr[A => B] => Expr[C]): Expr[C] = 151 | import quotes.reflect.* 152 | 153 | extension (t: Term) def unsafeAsExpr[A] = t.asExpr.asInstanceOf[Expr[A]] // cast without `quoted.Type[A]` 154 | 155 | def isFunctionLiteral[A, B](f: Expr[A => B]): Boolean = cond(f.asTerm.underlyingArgument) { case Lambda(_, _) => 156 | true 157 | } 158 | 159 | def let[A, B](name: String, rhs: Expr[A])(in: Expr[A] => Expr[B])(using Quotes): Expr[B] = 160 | // Equivalent to `'{ val name = $rhs; ${in('name)} }` 161 | ValDef.let(Symbol.spliceOwner, name, rhs.asTerm)(ref => in(ref.unsafeAsExpr[A]).asTerm).unsafeAsExpr[B] 162 | 163 | if isFunctionLiteral(rhs) then in(Expr.betaReduce(rhs)) 164 | else let(name, rhs)(in) 165 | -------------------------------------------------------------------------------- /macros/src/main/scala-2/io/github/metarank/cfor/Syntax.scala: -------------------------------------------------------------------------------- 1 | package io.github.metarank.cfor 2 | 3 | import Syntax._ 4 | 5 | case class SyntaxUtil[C <: Context with Singleton](val c: C) { 6 | 7 | import c.universe._ 8 | 9 | def freshTermName[C <: Context](c: C)(s: String) = 10 | c.universe.TermName(c.freshName(s)) 11 | 12 | def name(s: String) = freshTermName(c)(s + "$") 13 | 14 | def names(bs: String*) = bs.toList.map(name) 15 | 16 | def isClean(es: c.Expr[_]*): Boolean = 17 | es.forall { 18 | _.tree match { 19 | case t @ Ident(_: TermName) if t.symbol.asTerm.isStable => true 20 | case Function(_, _) => true 21 | case _ => false 22 | } 23 | } 24 | } 25 | 26 | class InlineUtil[C <: Context with Singleton](val c: C) { 27 | import c.universe._ 28 | 29 | def resetLocalAttrs[C <: Context](c: C)(t: c.Tree) = 30 | c.untypecheck(t) 31 | 32 | def termName[C <: Context](c: C)(s: String) = 33 | c.universe.TermName(s) 34 | def setOrig[C <: Context](c: C)(tt: c.universe.TypeTree, t: c.Tree) = 35 | c.universe.internal.setOriginal(tt, t) 36 | 37 | def inlineAndReset[T](tree: Tree): c.Expr[T] = { 38 | val inlined = inlineApplyRecursive(tree) 39 | c.Expr[T](resetLocalAttrs(c)(inlined)) 40 | } 41 | 42 | def inlineApplyRecursive(tree: Tree): Tree = { 43 | val ApplyName = termName(c)("apply") 44 | 45 | class InlineSymbol(name: TermName, symbol: Symbol, value: Tree) extends Transformer { 46 | override def transform(tree: Tree): Tree = tree match { 47 | case tree: Ident if tree.symbol == symbol => 48 | if (tree.name == name) { 49 | value 50 | } else { 51 | super.transform(tree) 52 | } 53 | 54 | case tt: TypeTree if tt.original != null => 55 | //super.transform(TypeTree().setOriginal(transform(tt.original))) 56 | super.transform(setOrig(c)(TypeTree(), transform(tt.original))) 57 | case _ => 58 | super.transform(tree) 59 | } 60 | } 61 | 62 | object InlineApply extends Transformer { 63 | def inlineSymbol(name: TermName, symbol: Symbol, body: Tree, arg: Tree): Tree = 64 | new InlineSymbol(name, symbol, arg).transform(body) 65 | 66 | override def transform(tree: Tree): Tree = tree match { 67 | case Apply(Select(Function(params, body), ApplyName), args) => 68 | params.zip(args).foldLeft(body) { case (b, (param, arg)) => 69 | inlineSymbol(param.name, param.symbol, b, arg) 70 | } 71 | 72 | case Apply(Function(params, body), args) => 73 | params.zip(args).foldLeft(body) { case (b, (param, arg)) => 74 | inlineSymbol(param.name, param.symbol, b, arg) 75 | } 76 | 77 | case _ => 78 | super.transform(tree) 79 | } 80 | } 81 | 82 | InlineApply.transform(tree) 83 | } 84 | } 85 | 86 | object Syntax { 87 | type Context = scala.reflect.macros.whitebox.Context 88 | 89 | def cforArrayMacro[A](c: Context)(array: c.Expr[Array[A]])(body: c.Expr[A => Unit]): c.Expr[Unit] = { 90 | import c.universe._ 91 | val util = SyntaxUtil[c.type](c) 92 | val index = util.name("index") 93 | 94 | val tree = if (util.isClean(body)) { 95 | q""" 96 | var $index: Int = 0 97 | while ($index < $array.length) { 98 | $body($array($index)) 99 | $index += 1 100 | } 101 | """ 102 | } else { 103 | val bodyName = util.name("body") 104 | q""" 105 | val $bodyName: Int => Unit = $body 106 | var $index: Int = 0 107 | while ($index < $array.length) { 108 | $bodyName($array($index)) 109 | $index += 1 110 | } 111 | """ 112 | } 113 | new InlineUtil[c.type](c).inlineAndReset[Unit](tree) 114 | } 115 | 116 | def cforMacro[A]( 117 | c: Context 118 | )(init: c.Expr[A])(test: c.Expr[A => Boolean], next: c.Expr[A => A])(body: c.Expr[A => Unit]): c.Expr[Unit] = { 119 | 120 | import c.universe._ 121 | val util = SyntaxUtil[c.type](c) 122 | val index = util.name("index") 123 | 124 | val tree = if (util.isClean(test, next, body)) { 125 | q""" 126 | var $index = $init 127 | while ($test($index)) { 128 | $body($index) 129 | $index = $next($index) 130 | } 131 | """ 132 | } else { 133 | val testName = util.name("test") 134 | val nextName = util.name("next") 135 | val bodyName = util.name("body") 136 | 137 | q""" 138 | val $testName: Int => Boolean = $test 139 | val $nextName: Int => Int = $next 140 | val $bodyName: Int => Unit = $body 141 | var $index: Int = $init 142 | while ($testName($index)) { 143 | $bodyName($index) 144 | $index = $nextName($index) 145 | } 146 | """ 147 | } 148 | new InlineUtil[c.type](c).inlineAndReset[Unit](tree) 149 | } 150 | 151 | def cforRangeMacro(c: Context)(r: c.Expr[Range])(body: c.Expr[Int => Unit]): c.Expr[Unit] = { 152 | 153 | import c.universe._ 154 | val util = SyntaxUtil[c.type](c) 155 | 156 | // names always contains 5 entries 157 | val names = util.names("range", "index", "end", "limit", "step") 158 | val index = names(1) 159 | val end = names(2) 160 | val limit = names(3) 161 | 162 | def isLiteral(t: Tree): Option[Int] = t match { 163 | case Literal(Constant(a)) => 164 | a match { 165 | case n: Int => Some(n) 166 | case _ => None 167 | } 168 | case _ => None 169 | } 170 | 171 | def strideUpTo(fromExpr: Tree, toExpr: Tree, stride: Int): Tree = 172 | q""" 173 | var $index: Int = $fromExpr 174 | val $end: Int = $toExpr 175 | while ($index <= $end) { 176 | $body($index) 177 | $index += $stride 178 | }""" 179 | 180 | def strideUpUntil(fromExpr: Tree, untilExpr: Tree, stride: Int): Tree = 181 | q""" 182 | var $index: Int = $fromExpr 183 | val $limit: Int = $untilExpr 184 | while ($index < $limit) { 185 | $body($index) 186 | $index += $stride 187 | }""" 188 | 189 | def strideDownTo(fromExpr: Tree, toExpr: Tree, stride: Int): Tree = 190 | q""" 191 | var $index: Int = $fromExpr 192 | val $end: Int = $toExpr 193 | while ($index >= $end) { 194 | $body($index) 195 | $index -= $stride 196 | }""" 197 | 198 | def strideDownUntil(fromExpr: Tree, untilExpr: Tree, stride: Int): Tree = 199 | q""" 200 | var $index: Int = $fromExpr 201 | val $limit: Int = $untilExpr 202 | while ($index > $limit) { 203 | $body($index) 204 | $index -= $stride 205 | }""" 206 | 207 | val tree: Tree = r.tree match { 208 | 209 | case q"$predef.intWrapper($i).until($j)" => 210 | strideUpUntil(i, j, 1) 211 | 212 | case q"$predef.intWrapper($i).to($j)" => 213 | strideUpTo(i, j, 1) 214 | 215 | case r @ q"$predef.intWrapper($i).until($j).by($step)" => 216 | isLiteral(step) match { 217 | case Some(k) if k > 0 => strideUpUntil(i, j, k) 218 | case Some(k) if k < 0 => strideDownUntil(i, j, -k) 219 | case Some(k) => 220 | c.error(c.enclosingPosition, "zero stride") 221 | q"()" 222 | case None => 223 | c.info(c.enclosingPosition, "non-literal stride", true) 224 | q"$r.foreach($body)" 225 | } 226 | 227 | case r @ q"$predef.intWrapper($i).to($j).by($step)" => 228 | isLiteral(step) match { 229 | case Some(k) if k > 0 => strideUpTo(i, j, k) 230 | case Some(k) if k < 0 => strideDownTo(i, j, -k) 231 | case Some(k) => 232 | c.error(c.enclosingPosition, "zero stride") 233 | q"()" 234 | case None => 235 | c.info(c.enclosingPosition, "non-literal stride", true) 236 | q"$r.foreach($body)" 237 | } 238 | 239 | case r => 240 | c.info(c.enclosingPosition, "non-literal range", true) 241 | q"$r.foreach($body)" 242 | } 243 | 244 | new InlineUtil[c.type](c).inlineAndReset[Unit](tree) 245 | } 246 | 247 | def cforRange2Macro( 248 | c: Context 249 | )(r1: c.Expr[Range], r2: c.Expr[Range])(body: c.Expr[(Int, Int) => Unit]): c.Expr[Unit] = { 250 | 251 | import c.universe._ 252 | c.Expr[Unit](q"cforRange($r1)(i => cforRange($r2)(j => $body(i, j)))") 253 | } 254 | 255 | } 256 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------