Created
October 27, 2012 16:09
-
-
Save fizzy33/3965184 to your computer and use it in GitHub Desktop.
Chord's an ast for memoizing and reifying string ops
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object Chord { | |
import Impl._ | |
def ~(s: String): Chord = stringToChord(s) | |
implicit def stringToChord(s: String): Chord = ChordedString(s) | |
case class PimpedIterable[T](iter: Iterable[T]) { | |
def mkChord(seperator: String) = { | |
val seperatorChord: Chord = seperator | |
iter.foldLeft(emptyChord) { (acc, i) => | |
(acc, i) match { | |
case (EmptyChord, ch: Chord) => ch | |
case (_, ch: Chord) => acc ~ seperatorChord ~ ch | |
case (EmptyChord, _) => ChordedString(String.valueOf(i)) | |
case (_, _) => acc ~ seperatorChord ~ i.toString | |
} | |
} | |
} | |
} | |
implicit def pimpedIterable[T](iter: Iterable[T]) = PimpedIterable(iter) | |
val emptyChord: Chord = EmptyChord | |
object Impl { | |
case object EmptyChord extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
} | |
} | |
case class CatStr(left: Chord, right: String) extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
left.writeTo(csb) | |
csb.append(right) | |
} | |
} | |
case class TrunCatStr(left: Chord, right: String, separator: String = "") extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
left.writeTo(csb) | |
csb.trimRight | |
csb.append(separator) | |
csb.trimIncomingWhitespace = true | |
csb.append(right) | |
csb.trimIncomingWhitespace = false | |
} | |
} | |
case class Cat(left: Chord, right: Chord) extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
left.writeTo(csb) | |
right.writeTo(csb) | |
} | |
} | |
case class TrunCat(left: Chord, right: Chord, separator: String = "") extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
left.writeTo(csb) | |
csb.trimRight | |
csb.append(separator) | |
csb.trimIncomingWhitespace = true | |
right.writeTo(csb) | |
csb.trimIncomingWhitespace = false | |
} | |
} | |
case class TrimRight(chord: Chord) extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
chord.writeTo(csb) | |
csb.trimRight | |
} | |
} | |
case class TrimLeadingWhitespace(chord: Chord) extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
csb.trimIncomingWhitespace = true | |
chord.writeTo(csb) | |
csb.trimIncomingWhitespace = false | |
} | |
} | |
case class ChordedString(value: String) extends Chord { | |
override def writeTo(csb: ChordStringBuilder) = { | |
csb.append(value) | |
} | |
} | |
class ChordStringBuilder { | |
val sb = new StringBuilder | |
var start = 0 | |
var trimIncomingWhitespace = false | |
def charAt(i: Int) = sb.charAt(i) | |
def length = sb.length | |
def trimRight = { | |
val end = sb.length | |
var trimStart = end | |
while (trimStart > start && Character.isWhitespace(sb.charAt(trimStart - 1))) { | |
trimStart -= 1 | |
} | |
if (trimStart != end) | |
sb.delete(trimStart, end) | |
} | |
def append(s: String) = { | |
if (trimIncomingWhitespace) { | |
var i = 0 | |
while (i < s.length && Character.isWhitespace(s.charAt(i))) { | |
i += 1 | |
} | |
while (i < s.length) { | |
trimIncomingWhitespace = false | |
sb.append(s.charAt(i)) | |
i += 1 | |
} | |
} else { | |
sb.append(s) | |
} | |
} | |
override def toString = sb.substring(start) | |
} | |
} | |
} | |
trait Chord { | |
import Chord.Impl._ | |
/** | |
* concatenate with a string equivalent of s0 + s1 | |
*/ | |
def ~(s: String): Chord = CatStr(this, s) | |
/** | |
* concatenate with a chord equivalent of s0 + s1 | |
*/ | |
def ~(c: Chord): Chord = Cat(this, c) | |
/** | |
* concatenate with a string equivalent of trimRight(s0) + trimLeft(s1) | |
*/ | |
def ~~(s: String): Chord = TrunCatStr(this, s) | |
/** | |
* concatenate with a chord equivalent of trimRight(s0) + trimLeft(s1) | |
*/ | |
def ~~(c: Chord): Chord = TrunCat(this, c) | |
/** | |
* concatenate with a string equivalent of trimRight(s0) + " " + trimLeft(s1) | |
*/ | |
def ~*~(s: String): Chord = TrunCatStr(this, s, " ") | |
/** | |
* concatenate with a chord equivalent of trimRight(s0) + " " + trimLeft(s1) | |
*/ | |
def ~*~(c: Chord): Chord = TrunCat(this, c, " ") | |
def trimRight: Chord = TrimRight(this) | |
def trimLeft: Chord = TrimLeadingWhitespace(this) | |
def trim = trimLeft.trimRight | |
def asString = { | |
val csb = new ChordStringBuilder | |
writeTo(csb) | |
csb.toString | |
} | |
def originalToString = | |
super.toString | |
def writeTo(csb: ChordStringBuilder) | |
override def toString = asString | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Chord._ | |
import org.scalatest.junit.JUnitSuite | |
import org.junit.Assert._ | |
import org.junit.Test | |
class ChordTest extends JUnitSuite { | |
@Test def chordSquiglyStarSquigly { | |
val tests = List[((Chord, Chord), String)]( | |
(("a", "b"), "a b") | |
, (("a ", "b"), "a b") | |
, (("a ", " b"), "a b") | |
, (("a", " b"), "a b") | |
, (("a ", "b"), "a b") | |
, (("a", " b"), "a b") | |
, (("a ", " b"), "a b") | |
) | |
runTests(tests) { input => | |
(input._1 ~*~ input._2).asString | |
} | |
} | |
@Test def stringSquiglyStarSquigly { | |
val tests = List[((Chord, String), String)]( | |
(("a", "b"), "a b") | |
, (("a ", "b"), "a b") | |
, (("a ", " b"), "a b") | |
, (("a", " b"), "a b") | |
, (("a ", " b"), "a b") | |
, (("a", " b"), "a b") | |
, (("a ", " b"), "a b") | |
) | |
runTests(tests) { input => | |
(input._1 ~*~ input._2).asString | |
} | |
} | |
@Test def mkChord { | |
assertEquals("a 1 2 b", ("a" ~*~ List("1": Chord,"2": Chord).mkChord(" ") ~*~ "b").toString) | |
assertEquals("a b c", ("a" ~*~ "b" ~*~ List().mkChord(" ") ~*~ "c").toString) | |
} | |
@Test def chordSquiglySquigly { | |
val tests = List[((Chord, Chord), String)]( | |
(("a", "b"), "ab") | |
, (("a ", "b"), "ab") | |
, (("a ", " b"), "ab") | |
, (("a", " b"), "ab") | |
, (("a", " b"), "ab") | |
, (("a ", "b"), "ab") | |
, (("a ", " b"), "ab") | |
) | |
runTests(tests) { input => | |
(input._1 ~~ input._2).asString | |
} | |
} | |
@Test def stringSquiglySquigly { | |
val tests = List[((Chord, String), String)]( | |
(("a", "b"), "ab") | |
, (("a ", "b"), "ab") | |
, (("a ", " b"), "ab") | |
, (("a", " b"), "ab") | |
, (("a", " b"), "ab") | |
, (("a ", "b"), "ab") | |
, (("a ", " b"), "ab") | |
) | |
runTests(tests) { input => | |
(input._1 ~~ input._2).asString | |
} | |
} | |
@Test def chordSquigly { | |
val tests = List[(List[Chord], String)]( | |
(List("a", "b"), "ab") | |
, (List("a ", "b"), "a b") | |
, (List("a ", " b"), "a b") | |
, (List("a", " b"), "a b") | |
, (List("a", " b"), "a b") | |
, (List("a ", "b"), "a b") | |
, (List("a ", " b"), "a b") | |
) | |
runTests(tests) { input => | |
input.foldLeft("": Chord)((acc, s) => acc ~ s).asString | |
} | |
} | |
@Test def stringSquigly { | |
val tests = List[((Chord, String), String)]( | |
(("a", "b"), "ab") | |
, (("a ", "b"), "a b") | |
, (("a ", " b"), "a b") | |
, (("a", " b"), "a b") | |
, (("a", " b"), "a b") | |
, (("a ", "b"), "a b") | |
, (("a ", " b"), "a b") | |
) | |
runTests(tests) { input => | |
(input._1 ~ input._2).asString | |
} | |
} | |
def runTests[TInput,TOutput](tests: List[(TInput,TOutput)])( code: TInput => TOutput ) = { | |
tests.foreach { case (input, expectedOutput) => | |
val actualOutput = code(input) | |
assertEquals(expectedOutput, actualOutput) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment