Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.platform }}
strategy:
matrix:
java: [11, 17]
java: [17, 21, 25]
platform: [ubuntu-24.04]
steps:
- uses: actions/checkout@v7
Expand All @@ -37,5 +37,9 @@ jobs:
- name: Set up sbt
uses: sbt/setup-sbt@v1

- name: Check formatting
run: sbt -mem 3000 scalafmtCheckAll
if: matrix.java == 21

- name: Run tests
run: sbt -mem 3000 "+test"
4 changes: 2 additions & 2 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
runner.dialect = scala212
runner.dialect = scala213
fileOverride {
"glob:**/src/main/scala-3/**" {
runner.dialect = scala3
Expand All @@ -7,4 +7,4 @@ fileOverride {

style = defaultWithAlign
maxColumn = 120
version = 3.5.9
version = 3.11.5
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

This tiny project is a cfor macro inspired by the one
originally implemented in [Typelevel spire](https://github.com/typelevel/spire/blob/master/macros/src/main/scala/spire/macros/Syntax.scala)
library. Supports Scala 2.12 and 2.13 and has zero dependencies.
library. Supports Scala 2.13 and 3 and has zero dependencies.

## Example

Expand Down Expand Up @@ -46,9 +46,9 @@ with the same performance as a pure java for loop.

## Installation

Available on maven central for scala 2.12 and 2.13
Available on maven central for scala 2.13 and 3
```scala
libraryDependencies += "io.github.metarank" %% "cfor" % "0.2"
libraryDependencies += "io.github.metarank" %% "cfor" % "0.3"
```

## Purpose
Expand Down
23 changes: 12 additions & 11 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
name := "cfor"

val cforVersion = "0.3"

scalaVersion := "2.13.10"
version := cforVersion

sonatypeProfileName := "io.github.metarank"

def isScala2(scalaVersion: String): Boolean =
CrossVersion.partialVersion(scalaVersion) match {
case Some((2, _)) => true
case _ => false
}

lazy val sharedSettings = Seq(
crossScalaVersions := Seq("2.12.16", "2.13.10", "3.2.1"),
crossScalaVersions := Seq("2.13.18", "3.9.0"),
organization := "io.github.metarank",
version := cforVersion,
scalaVersion := "2.13.10",
scalaVersion := "2.13.18",
scalacOptions ++= Seq("-feature", "-deprecation"),
libraryDependencies ++= (if (isScala2(scalaVersion.value)) {
Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value)
} else {
Seq.empty[ModuleID]
}),
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.14" % "test"
"org.scalatest" %% "scalatest" % "3.2.20" % "test"
)
)

lazy val root = (project in file("."))
.settings(sharedSettings)
.settings(name := "cfor-root", publish / skip := true)
.aggregate(macros, benchmark)

lazy val macros = (project in file("macros")).settings(sharedSettings)

lazy val benchmark = (project in file("benchmark")).settings(sharedSettings).dependsOn(macros)
lazy val benchmark = (project in file("benchmark"))
.settings(sharedSettings)
.settings(name := "cfor-benchmark", publish / skip := true)
.dependsOn(macros)
2 changes: 1 addition & 1 deletion macros/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name := "cfor"

publishMavenStyle := true

publishTo := sonatypePublishToBundle.value
publishTo := localStaging.value

licenses := Seq("APL2" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt"))

Expand Down
6 changes: 3 additions & 3 deletions macros/src/main/scala-2/io/github/metarank/cfor/Syntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class InlineUtil[C <: Context with Singleton](val c: C) {
}

case tt: TypeTree if tt.original != null =>
//super.transform(TypeTree().setOriginal(transform(tt.original)))
// super.transform(TypeTree().setOriginal(transform(tt.original)))
super.transform(setOrig(c)(TypeTree(), transform(tt.original)))
case _ =>
super.transform(tree)
Expand Down Expand Up @@ -216,7 +216,7 @@ object Syntax {
isLiteral(step) match {
case Some(k) if k > 0 => strideUpUntil(i, j, k)
case Some(k) if k < 0 => strideDownUntil(i, j, -k)
case Some(k) =>
case Some(k) =>
c.error(c.enclosingPosition, "zero stride")
q"()"
case None =>
Expand All @@ -228,7 +228,7 @@ object Syntax {
isLiteral(step) match {
case Some(k) if k > 0 => strideUpTo(i, j, k)
case Some(k) if k < 0 => strideDownTo(i, j, -k)
case Some(k) =>
case Some(k) =>
c.error(c.enclosingPosition, "zero stride")
q"()"
case None =>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.metarank.cfor

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import scala.collection.mutable

class CforScala2Test extends AnyFlatSpec with Matchers {
it should "iterate over array with anonymous function" in {
val l = mutable.ListBuffer[Int]()
val arr = Array(0, 1, 2, 3, 4)
cfor(arr)(l.append)
l.toList shouldBe List(0, 1, 2, 3, 4)
}

// the scala 2 macro inlines the loop body, so a closure captures the mutable
// loop variable itself, exactly like a hand-written while loop does
it should "capture value in closure" in {
val b1 = collection.mutable.ArrayBuffer.empty[() => Int]
cfor(0)(_ < 3, _ + 1) { x =>
b1 += (() => x)
}
val b2 = collection.mutable.ArrayBuffer[() => Int]()
var i = 0
while (i < 3) {
b2 += (() => i)
i += 1
}
b1.map(_.apply()).toList shouldBe b2.map(_.apply()).toList
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.metarank.cfor

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class CforScala3Test extends AnyFlatSpec with Matchers {
// the scala 3 macro binds the loop value per iteration, so a closure captures
// the value of the current iteration rather than the mutable loop variable
it should "capture value in closure" in {
val b1 = collection.mutable.ArrayBuffer.empty[() => Int]
cfor(0)(_ < 3, _ + 1) { x =>
b1 += (() => x)
}
b1.map(_.apply()).toList shouldBe List(0, 1, 2)
}
}
24 changes: 5 additions & 19 deletions macros/src/test/scala/io/github/metarank/cfor/CforTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ class CforTest extends AnyFlatSpec with Matchers {
}

it should "functions with side effects function values in cfor" in {
val b = mutable.ArrayBuffer.empty[Int]
var v = 0
val b = mutable.ArrayBuffer.empty[Int]
var v = 0
def test: Int => Boolean = { v += 1; _ < 3 }
def incr: Int => Int = { v += 10; _ + 1 }
def body: Int => Unit = {
def incr: Int => Int = { v += 10; _ + 1 }
def body: Int => Unit = {
v += 100
x => {
b += x
Expand Down Expand Up @@ -83,20 +83,6 @@ class CforTest extends AnyFlatSpec with Matchers {
b.toList shouldBe List(0, 1, 2)
}

it should "capture value in closure" in {
val b1 = collection.mutable.ArrayBuffer.empty[() => Int]
cfor(0)(_ < 3, _ + 1) { x =>
b1 += (() => x)
}
val b2 = collection.mutable.ArrayBuffer[() => Int]()
var i = 0
while (i < 3) {
b2 += (() => i)
i += 1
}
b1.map(_.apply()).toList shouldBe b2.map(_.apply()).toList
}

it should "capture value in inner class" in {
val b = collection.mutable.ArrayBuffer[Int]()
cfor(0)(_ < 3, _ + 1) { x =>
Expand Down Expand Up @@ -144,7 +130,7 @@ class CforTest extends AnyFlatSpec with Matchers {

it should "functions with side effects in cfor(arr)" in {
var sum = 0
var v = 0
var v = 0
val arr = Array(0, 1, 2, 3, 4)
cfor(arr) {
v += 100
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version = 1.8.0
sbt.version = 2.0.8
8 changes: 4 additions & 4 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.15")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.8")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.2")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.7.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.2")