From c38c3cae7c240d75ce2cb6e81c27033455b63915 Mon Sep 17 00:00:00 2001 From: Robin Hillyard Date: Fri, 5 Nov 2021 17:15:39 -0400 Subject: [PATCH] Some minor refactoring --- build.sbt | 2 +- .../phasmidsoftware/parse/CellParsers.scala | 7 +- .../com/phasmidsoftware/parse/Parseable.scala | 19 ++-- .../phasmidsoftware/parse/TableParser.scala | 2 +- .../parse/CellParsersSpec.scala | 100 ++++++++---------- .../phasmidsoftware/parse/ParseableSpec.scala | 2 +- .../parse/TableParserSpec.scala | 8 +- .../com/phasmidsoftware/table/TableSpec.scala | 8 +- 8 files changed, 63 insertions(+), 85 deletions(-) diff --git a/build.sbt b/build.sbt index 7401aaf0..7a69dcb9 100755 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ organization := "com.phasmidsoftware" name := "TableParser" -version := "1.0.14" +version := "1.1.0" scalaVersion := "2.13.6" diff --git a/src/main/scala/com/phasmidsoftware/parse/CellParsers.scala b/src/main/scala/com/phasmidsoftware/parse/CellParsers.scala index ae97d366..4a37a272 100644 --- a/src/main/scala/com/phasmidsoftware/parse/CellParsers.scala +++ b/src/main/scala/com/phasmidsoftware/parse/CellParsers.scala @@ -6,6 +6,7 @@ package com.phasmidsoftware.parse import com.phasmidsoftware.table._ import com.phasmidsoftware.util.{FP, Reflection} + import scala.reflect.ClassTag import scala.util.control.NonFatal import scala.util.{Failure, Success, Try} @@ -580,11 +581,7 @@ trait CellParsers { } } -/** - * This companion object comprises CellParser[T] objects which represent conversions that are fixed, - * i.e. they don't depend on some other parameter such as the formatter in DateTime conversions. - */ -object CellParsers +object StdCellParsers extends CellParsers /** * CONSIDER: do we really need this exception? diff --git a/src/main/scala/com/phasmidsoftware/parse/Parseable.scala b/src/main/scala/com/phasmidsoftware/parse/Parseable.scala index 87ec94b5..996f4eaf 100644 --- a/src/main/scala/com/phasmidsoftware/parse/Parseable.scala +++ b/src/main/scala/com/phasmidsoftware/parse/Parseable.scala @@ -4,11 +4,10 @@ package com.phasmidsoftware.parse -import java.io.File -import java.net.URL - import org.joda.time.LocalDate +import java.io.File +import java.net.URL import scala.annotation.implicitNotFound import scala.util.parsing.combinator.JavaTokenParsers import scala.util.{Failure, Success, Try} @@ -175,17 +174,15 @@ object Parseable { * @param w the String to parse. * @return a Try[StringList]. */ - def split(w: String): Try[StringList] = parser.parseAll(parser.list, w) match { - case parser.Success(ws: StringList, _) => Success(ws) - case parser.Failure(msg, _) => Failure(ParseLogicException(s"cannot split string '$w': $msg")) - case parser.Error(msg, _) => Failure(ParseLogicException(s"cannot split string '$w': $msg")) + def split(w: String): Try[StringList] = ListParser.parseAll(ListParser.list, w) match { + case ListParser.Success(ws: StringList, _) => Success(ws) + case ListParser.Failure(msg, _) => Failure(ParseLogicException(s"cannot split string '$w': $msg")) + case ListParser.Error(msg, _) => Failure(ParseLogicException(s"cannot split string '$w': $msg")) case _ => Failure(ParseLogicException(s"cannot split string '$w'")) } private def lift[T](f: String => T): String => Try[T] = w => Try(f(w)) - private val parser = new ListParser() - private def parseAndRecover[T](w: String)(f: String => Try[T])(msg: String => String): Try[T] = f(w).recoverWith { case x: IllegalArgumentException => Failure(if (w.nonEmpty) InvalidParseException(msg(w), x) else BlankException(x)) @@ -236,10 +233,10 @@ object ParseableOption { } /** - * This class is a parser of lists. + * This object is a parser of lists. * A list is considered to be enclosed by {} and separated by commas. */ -class ListParser() extends JavaTokenParsers { +object ListParser extends JavaTokenParsers { lazy val list: Parser[StringList] = "{" ~> strings <~ "}" | singleton diff --git a/src/main/scala/com/phasmidsoftware/parse/TableParser.scala b/src/main/scala/com/phasmidsoftware/parse/TableParser.scala index f78d9df6..3294d956 100644 --- a/src/main/scala/com/phasmidsoftware/parse/TableParser.scala +++ b/src/main/scala/com/phasmidsoftware/parse/TableParser.scala @@ -172,7 +172,7 @@ case class RawTableParser(override protected val predicate: Try[RawRow] => Boole type Row = RawRow - implicit val stringSeqParser: CellParser[RawRow] = new CellParsers {}.cellParserSeq + implicit val stringSeqParser: CellParser[RawRow] = StdCellParsers.cellParserSeq val rowParser: RowParser[Row, String] = StandardRowParser[RawRow] diff --git a/src/test/scala/com/phasmidsoftware/parse/CellParsersSpec.scala b/src/test/scala/com/phasmidsoftware/parse/CellParsersSpec.scala index ad20539f..ca554910 100644 --- a/src/test/scala/com/phasmidsoftware/parse/CellParsersSpec.scala +++ b/src/test/scala/com/phasmidsoftware/parse/CellParsersSpec.scala @@ -183,7 +183,7 @@ class CellParsersSpec extends flatspec.AnyFlatSpec with should.Matchers { } it should "parse optional non-empty String" in { - val parser = new CellParsers {}.cellParserOptionNonEmptyString + val parser = StdCellParsers.cellParserOptionNonEmptyString parser.convertString("Hello") shouldBe Success(Some("Hello")) parser.convertString("") shouldBe Success(None) } @@ -195,13 +195,13 @@ class CellParsersSpec extends flatspec.AnyFlatSpec with should.Matchers { case class Int1(x: Int) extends IsInt case class Int2(x: Int) extends IsInt case class MyInt(s: Int, z: IsInt) - val cellParsers = new CellParsers {} - implicit val int1ColumnHelper: ColumnHelper[Int1] = cellParsers.columnHelper() - implicit val int2ColumnHelper: ColumnHelper[Int2] = cellParsers.columnHelper() - implicit val myIntColumnHelper: ColumnHelper[MyInt] = cellParsers.columnHelper() - implicit val int1Parser: CellParser[IsInt] = cellParsers.cellParser1(Int1) - implicit val int2Parser: CellParser[IsInt] = cellParsers.cellParser1(Int2) - implicit val parser: CellParser[MyInt] = cellParsers.cellParser2Conditional(MyInt.apply, Map(1 -> int1Parser, 2 -> int2Parser)) + val p = StdCellParsers + implicit val int1ColumnHelper: ColumnHelper[Int1] = p.columnHelper() + implicit val int2ColumnHelper: ColumnHelper[Int2] = p.columnHelper() + implicit val myIntColumnHelper: ColumnHelper[MyInt] = p.columnHelper() + implicit val int1Parser: CellParser[IsInt] = p.cellParser1(Int1) + implicit val int2Parser: CellParser[IsInt] = p.cellParser1(Int2) + implicit val parser: CellParser[MyInt] = p.cellParser2Conditional(MyInt.apply, Map(1 -> int1Parser, 2 -> int2Parser)) RowValues(Row(Seq("1", "2"), Header.create("s", "z"), 0)).convertTo[MyInt].get.z shouldBe Int1(2) RowValues(Row(Seq("2", "2"), Header.create("s", "z"), 1)).convertTo[MyInt].get.z shouldBe Int2(2) @@ -211,157 +211,145 @@ class CellParsersSpec extends flatspec.AnyFlatSpec with should.Matchers { it should "parse with cellParser1" in { case class T(a: Int) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser1(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser1(T) parser.toString shouldBe s"MultiCellParser: cellParser1 for ${classOf[T].getName}" val header = Header.create("a") val row = Row(Seq("1"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1)) - val badParser: CellParser[T] = cellParsers.cellParser1(T, Seq("a", "b")) + val badParser: CellParser[T] = StdCellParsers.cellParser1(T, Seq("a", "b")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser2" in { case class T(a: Int, b: Double) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser2(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser2(T) parser.toString shouldBe s"MultiCellParser: cellParser2 for ${classOf[T].getName}" val header = Header.create("a", "b") val row = Row(Seq("1", "2"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0)) - val badParser: CellParser[T] = cellParsers.cellParser2(T, Seq("a")) + val badParser: CellParser[T] = StdCellParsers.cellParser2(T, Seq("a")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser3" in { case class T(a: Int, b: Double, c: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser3(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser3(T) parser.toString shouldBe s"MultiCellParser: cellParser3 for ${classOf[T].getName}" val header = Header.create("a", "b", "c") val row = Row(Seq("1", "2", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, c = true)) - val badParser: CellParser[T] = cellParsers.cellParser3(T, Seq("a", "b")) + val badParser: CellParser[T] = StdCellParsers.cellParser3(T, Seq("a", "b")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser4" in { case class T(a: Int, b: Double, c: String, d: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser4(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser4(T) parser.toString shouldBe s"MultiCellParser: cellParser4 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d") val row = Row(Seq("1", "2", "x", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", d = true)) - val badParser: CellParser[T] = cellParsers.cellParser4(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser4(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser5" in { case class T(a: Int, b: Double, c: String, d: Short, e: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser5(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser5(T) parser.toString shouldBe s"MultiCellParser: cellParser5 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e") val row = Row(Seq("1", "2", "x", "128", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, e = true)) - val badParser: CellParser[T] = cellParsers.cellParser5(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser5(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser6" in { case class T(a: Int, b: Double, c: String, d: Short, e: Byte, f: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser6(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser6(T) parser.toString shouldBe s"MultiCellParser: cellParser6 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e", "f") val row = Row(Seq("1", "2", "x", "128", "7", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, 7, f = true)) - val badParser: CellParser[T] = cellParsers.cellParser6(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser6(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser7" in { case class T(a: Int, b: Double, c: String, d: Short, e: Byte, f: Float, g: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser7(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser7(T) parser.toString shouldBe s"MultiCellParser: cellParser7 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e", "f", "g") val row = Row(Seq("1", "2", "x", "128", "7", "3.14", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, 7, 3.14f, g = true)) - val badParser: CellParser[T] = cellParsers.cellParser7(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser7(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser8" in { case class T(a: Int, b: Double, c: String, d: Short, e: Byte, f: Float, g: BigInt, h: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser8(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser8(T) parser.toString shouldBe s"MultiCellParser: cellParser8 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e", "f", "g", "h") val row = Row(Seq("1", "2", "x", "128", "7", "3.14", "1000000", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, 7, 3.14f, BigInt(1000000), h = true)) - val badParser: CellParser[T] = cellParsers.cellParser8(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser8(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser9" in { case class T(a: Int, b: Double, c: String, d: Short, e: Byte, f: Float, g: BigInt, h: BigDecimal, i: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser9(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser9(T) parser.toString shouldBe s"MultiCellParser: cellParser9 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e", "f", "g", "h", "i") val row = Row(Seq("1", "2", "x", "128", "7", "3.14", "1000000", "3.1415927", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, 7, 3.14f, BigInt(1000000), BigDecimal(3.1415927), i = true)) - val badParser: CellParser[T] = cellParsers.cellParser9(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser9(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser10" in { case class T(a: Int, b: Double, c: String, d: Short, e: Byte, f: Float, g: BigInt, h: BigDecimal, i: Int, j: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser10(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser10(T) parser.toString shouldBe s"MultiCellParser: cellParser10 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e", "f", "g", "h", "i", "j") val row = Row(Seq("1", "2", "x", "128", "7", "3.14", "1000000", "3.1415927", "99", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, 7, 3.14f, BigInt(1000000), BigDecimal(3.1415927), 99, j = true)) - val badParser: CellParser[T] = cellParsers.cellParser10(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser10(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser11" in { case class T(a: Int, b: Double, c: String, d: Short, e: Byte, f: Float, g: BigInt, h: BigDecimal, i: Int, j: Char, k: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser11(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser11(T) parser.toString shouldBe s"MultiCellParser: cellParser11 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k") val row = Row(Seq("1", "2", "x", "128", "7", "3.14", "1000000", "3.1415927", "99", "a", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, 7, 3.14f, BigInt(1000000), BigDecimal(3.1415927), 99, 'a', k = true)) - val badParser: CellParser[T] = cellParsers.cellParser11(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser11(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } it should "parse with cellParser12" in { case class T(a: Int, b: Double, c: String, d: Short, e: Byte, f: Float, g: BigInt, h: BigDecimal, i: Int, j: Char, k: Double, l: Boolean) - val cellParsers = new CellParsers {} - implicit val columnHelper: ColumnHelper[T] = cellParsers.columnHelper() - val parser: CellParser[T] = cellParsers.cellParser12(T) + implicit val columnHelper: ColumnHelper[T] = StdCellParsers.columnHelper() + val parser: CellParser[T] = StdCellParsers.cellParser12(T) parser.toString shouldBe s"MultiCellParser: cellParser12 for ${classOf[T].getName}" val header = Header.create("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l") val row = Row(Seq("1", "2", "x", "128", "7", "3.14", "1000000", "3.1415927", "99", "a", "3.1415927", "true"), header, 0) parser.parse(None, row, header) shouldBe Success(T(1, 2.0, "x", 128, 7, 3.14f, BigInt(1000000), BigDecimal(3.1415927), 99, 'a', 3.1415927, l = true)) - val badParser: CellParser[T] = cellParsers.cellParser12(T, Seq("a", "b", "c")) + val badParser: CellParser[T] = StdCellParsers.cellParser12(T, Seq("a", "b", "c")) badParser.parse(None, row, header) should matchPattern { case Failure(_) => } } diff --git a/src/test/scala/com/phasmidsoftware/parse/ParseableSpec.scala b/src/test/scala/com/phasmidsoftware/parse/ParseableSpec.scala index b38adbd5..7739446a 100644 --- a/src/test/scala/com/phasmidsoftware/parse/ParseableSpec.scala +++ b/src/test/scala/com/phasmidsoftware/parse/ParseableSpec.scala @@ -59,7 +59,7 @@ class ParseableSpec extends flatspec.AnyFlatSpec with should.Matchers { behavior of "ListParser" it should "parse a list" in { - val p = new ListParser + val p = ListParser p.parseAll(p.list, "{1,2,3}") should matchPattern { case p.Success(_, _) => } p.parseAll(p.list, "{1,2-3,3}") should matchPattern { case p.Success(_, _) => } p.parseAll(p.list, "1") should matchPattern { case p.Success(_, _) => } diff --git a/src/test/scala/com/phasmidsoftware/parse/TableParserSpec.scala b/src/test/scala/com/phasmidsoftware/parse/TableParserSpec.scala index c17a8c3f..7845c83b 100644 --- a/src/test/scala/com/phasmidsoftware/parse/TableParserSpec.scala +++ b/src/test/scala/com/phasmidsoftware/parse/TableParserSpec.scala @@ -28,15 +28,13 @@ class TableParserSpec extends flatspec.AnyFlatSpec with should.Matchers { object IntPair { - class IntPairParser extends JavaTokenParsers { + object IntPairParser extends JavaTokenParsers { def pair: Parser[(Int, Int)] = wholeNumber ~ wholeNumber ^^ { case x ~ y => (x.toInt, y.toInt) } } - val intPairParser = new IntPairParser - trait IntPairRowParser extends StringParser[IntPair] { - def parse(indexedString: (String, Int))(header: Header): Try[IntPair] = intPairParser.parseAll(intPairParser.pair, indexedString._1) match { - case intPairParser.Success((x: Int, y: Int), _) => Success(IntPair(x, y)) + def parse(indexedString: (String, Int))(header: Header): Try[IntPair] = IntPairParser.parseAll(IntPairParser.pair, indexedString._1) match { + case IntPairParser.Success((x: Int, y: Int), _) => Success(IntPair(x, y)) case _ => Failure(TableException(s"unable to parse ${indexedString._1}")) } diff --git a/src/test/scala/com/phasmidsoftware/table/TableSpec.scala b/src/test/scala/com/phasmidsoftware/table/TableSpec.scala index 6201c328..e4e4f4a6 100644 --- a/src/test/scala/com/phasmidsoftware/table/TableSpec.scala +++ b/src/test/scala/com/phasmidsoftware/table/TableSpec.scala @@ -25,15 +25,13 @@ class TableSpec extends flatspec.AnyFlatSpec with should.Matchers { object IntPair { - class IntPairParser extends JavaTokenParsers { + object IntPairParser extends JavaTokenParsers { lazy val pair: Parser[(Int, Int)] = wholeNumber ~ wholeNumber ^^ { case x ~ y => (x.toInt, y.toInt) } } - val intPairParser = new IntPairParser - trait IntPairRowParser extends StringParser[IntPair] { - def parse(indexedString: (String, Int))(header: Header): Try[IntPair] = intPairParser.parseAll(intPairParser.pair, indexedString._1) match { - case intPairParser.Success((x, y), _) => Success(IntPair(x, y)) + def parse(indexedString: (String, Int))(header: Header): Try[IntPair] = IntPairParser.parseAll(IntPairParser.pair, indexedString._1) match { + case IntPairParser.Success((x, y), _) => Success(IntPair(x, y)) case _ => Failure(TableException(s"unable to parse ${indexedString._1}")) }