Suppose I've got following txt file:
--quest_29540602496284069
Operator Name : Kevin
Account Id: 1444
Text: This is Kevin and this my text.
Age: 16
--quest_=29540602496284069--
I want to transform it to simple scala map:
(pseudo-code)
{
quest_id: 29540602496284069
operation_name = Kevin
account_id: 1444
text: This is Kevin and this my text.
operator_age: 16
}
So, i started to created case content class in order to store it in target object for future use:
case class MapContent(map: Map[String, String])
Then, i have created scala class with extending RegexpParsers:
class OperatorParser extends RegexParsers {
def parseFullRequest(input: String): MapContent = parseAll(parseRequest, input) match {
case Success(result, _) => result
case NoSuccess(msg, _) => throw new SomeParserException(msg)
}
// main entry
def parseRequest: Parser[MapContent] = parseQuestBody ~ parseAnotherBody
def parseQuestBody: Parser[MapContent] = parseQuestId
def parseQuestId: Parser[MapContent] = "--quest_" ~> """.+\n?""".r ^^ { case res =>
MapContent(Map("quest_id" -> res.toString))
}
def parseAnotherBody: Parser[MapContent] = """.+""".r ^^ { case res =>
MapContent(Map("another_body" -> res.toString))
}
}
When i'm doing
parseQuestBody ~ parseAnotherBody
it causes an error that Operator[MapContent, MapContent] isn't an Operator[MapContent]
I need a solution how to store exactly in MapContent during the whole parse process. Is there a possible way to do that? For now i can only store the quest_id number and not able to continue next.
You can use ^^ like in other parsers:
def parseRequest: Parser[MapContent] = parseQuestBody ~ parseAnotherBody ^^ {
case res => MapContent(res._1.map ++ res._2.map)
}
or
def parseRequest: Parser[MapContent] = parseQuestBody ~ parseAnotherBody ^^ {
case a ~ b => MapContent(a.map ++ b.map)
}
It transforms "tuple" of MapContents into single MapContent
Edit:
what if i will have a lot of text values, so in scala it will be
looking like a.map ++ b.map ++ c.map ++ d... + f.. + ... + x1 ? is
there more common way?
Folding it is one of possibilities:
def parseRequest: Parser[MapContent] = parseQuestId ~ rep(parseAnotherBody) ~ parseQuestId ^^ {
case value1 ~ list1 ~ value2=> {
val merged = List(value1, value2) ++ list1
merged.foldLeft(MapContent(Map.empty[String, String]))((a, b) => MapContent(a.map ++ b.map))
}
}
It returns map with only 2 values because you can't store in map 2 values with same another_body key
Related
I am trying to learn some functional programming in Scala.
I have this Map:
val params: Map[String, QueryMap] = Map(
"a" -> SimpleQueryVal("1"),
"b" -> ComplexQueryVal("2", "3")
)
Where QueryMap is (might not be the best approach):
sealed trait QueryMap
case class SimpleQueryVal(value: String) extends QueryMap
case class ComplexQueryVal(values: String*) extends QueryMap
My result would be having a string like query parameters: ?a=1&b=2&b=3
I tried something, but my method return an Iterator[String] even I use mkString, looks ugly and I am sure that there's a very simple way of doing it.
def paramString(queryMap: Map[String, QueryMap]) = queryMap.keys.map { key =>
val params = queryMap(key) match {
case SimpleQueryVal(x) => "%s=%s".format(key, x)
case complexQuery: ComplexQueryVal => complexQuery.values.map { value =>
"%s=%s".format(key, value)
}
}
val result: String = params match {
case s: String => s + "&"
case s: ArrayBuffer[_] => s.mkString("&")
}
result.mkString
}
I would appreciate any idea that would make me learn something for today. :)
I think the result String can be built in a simpler, more straight forward, manner.
def paramString(queryMap: Map[String, QueryMap]): String = queryMap.map{
case (k, sq: SimpleQueryVal) => s"$k=${sq.value}"
case (k, cq: ComplexQueryVal)=> cq.values.map(k + "=" + _).mkString("&")
}.mkString("&")
A little cleaner:
def paramString(queryMap: Map[String, QueryMap]) = queryMap.flatMap {
case (key, SimpleQueryVal(x)) => Seq(s"$key=$x")
case (key, ComplexQueryVal(values # _*)) => values.map {v =>
s"$key=$v"
}
}.mkString("&")
No need for ArrayBuffer or to repeat the .mkString("&").
Keep in mind that this is good for just learning. If you're actually trying to handle HTTP query string parameters, you need to URLEncode the keys and the values and there's probably better libraries for that.
Try this:
def paramString(queryMap: Map[String, QueryMap]) = {
val qParams = queryMap.keys.map { key =>
queryMap(key) match {
case SimpleQueryVal(x) => "%s=%s".format(key, x)
case complexQuery: ComplexQueryVal => complexQuery.values.map { value =>
"%s=%s".format(key, value)
}.mkString("&")
}
}
qParams.mkString("&")
}
println(paramString(params))
Here, first you get a Set[String] like a=1 or b=2&b=3. Then you simply do another .mkString("&") to concatenate them all.
I'm trying to write a simple parser in scala but when I add a repeated token Scala seems to get stuck in an infinite loop.
I have 2 parse methods below. One uses rep(). The non repetitive version works as expected (not what I want though) using the rep() version results in an infinite loop.
EDIT:
This was a learning example where I tired to enforce the '=' was surrounded by whitespace.
If it is helpful this is my actual test file:
a = 1
b = 2
c = 1 2 3
I was able to parse: (with the parse1 method)
K = V
but then ran into this problem when tried to expand the exercise out to:
K = V1 V2 V3
import scala.util.parsing.combinator._
import scala.io.Source.fromFile
class MyParser extends RegexParsers {
override def skipWhitespace(): Boolean = { false }
def key: Parser[String] = """[a-zA-Z]+""".r ^^ { _.toString }
def eq: Parser[String] = """\s+=\s+""".r ^^ { _.toString.trim }
def string: Parser[String] = """[^ \t\n]*""".r ^^ { _.toString.trim }
def value: Parser[List[String]] = rep(string)
def foo(key: String, value: String): Boolean = {
println(key + " = " + value)
true
}
def parse1: Parser[Boolean] = key ~ eq ~ string ^^ { case k ~ eq ~ string => foo(k, string) }
def parse2: Parser[Boolean] = key ~ eq ~ value ^^ { case k ~ eq ~ value => foo(k, value.toString) }
def parseLine(line: String): Boolean = {
parse(parse2, line) match {
case Success(matched, _) => true
case Failure(msg, _) => false
case Error(msg, _) => false
}
}
}
object TestParser {
def usage() = {
System.out.println("<file>")
}
def main(args: Array[String]) : Unit = {
if (args.length != 1) {
usage()
} else {
val mp = new MyParser()
fromFile(args(0)).getLines().foreach { mp.parseLine }
println("done")
}
}
}
Next time, please provide some concrete examples, it's not obvious what your input is supposed to look like.
Meanwhile, you can try this, maybe you find it helpful:
import scala.util.parsing.combinator._
import scala.io.Source.fromFile
class MyParser extends JavaTokenParsers {
// override def skipWhitespace(): Boolean = { false }
def key: Parser[String] = """[a-zA-Z]+""".r ^^ { _.toString }
def eq: Parser[String] = "="
def string: Parser[String] = """[^ \t\n]+""".r
def value: Parser[List[String]] = rep(string)
def foo(key: String, value: String): Boolean = {
println(key + " = " + value)
true
}
def parse1: Parser[Boolean] = key ~ eq ~ string ^^ { case k ~ eq ~ string => foo(k, string) }
def parse2: Parser[Boolean] = key ~ eq ~ value ^^ { case k ~ eq ~ value => foo(k, value.toString) }
def parseLine(line: String): Boolean = {
parseAll(parse2, line) match {
case Success(matched, _) => true
case Failure(msg, _) => false
case Error(msg, _) => false
}
}
}
val mp = new MyParser()
for (line <- List("hey = hou", "hello = world ppl", "foo = bar baz blup")) {
println(mp.parseLine(line))
}
Explanation:
JavaTokenParsers and RegexParsers treat white space differently.
The JavaTokenParsers handles the white space for you, it's not specific for Java, it works for most non-esoteric languages. As long as you are not trying to parse Whitespace, JavaTokenParsers is a good starting point.
Your string definition included a *, which caused the infinite recursion.
Your eq definition included something that messed with the empty space handling (don't do this unless it's really necessary).
Furthermore, if you want to parse the whole line, you must call parseAll,
otherwise it parses only the beginning of the string in non-greedy manner.
Final remark: for parsing key-value pairs line by line, some String.split and
String.trim would be completely sufficient. Scala Parser Combinators are a little overkill for that.
PS: Hmm... Did you want to allow =-signs in your key-names? Then my version would not work here, because it does not enforce an empty space after the key-name.
This is not a duplicate, it's a different version with RegexParsers that takes care of whitespace explicitly
If you for some reason really care about the white space, then you could stick to the RegexParsers, and do the following (notice the skipWhitespace = false, explicit parser for whitespace ws, the two ws with squiglies around the equality sign, and the repsep with explicitly specified ws):
import scala.util.parsing.combinator._
import scala.io.Source.fromFile
class MyParser extends RegexParsers {
override def skipWhitespace(): Boolean = false
def ws: Parser[String] = "[ \t]+".r
def key: Parser[String] = """[a-zA-Z]+""".r ^^ { _.toString }
def eq: Parser[String] = ws ~> """=""" <~ ws
def string: Parser[String] = """[^ \t\n]+""".r
def value: Parser[List[String]] = repsep(string, ws)
def foo(key: String, value: String): Boolean = {
print(key + " = " + value)
true
}
def parse1: Parser[Boolean] = (key ~ eq ~ string) ^^ { case k ~ e ~ v => foo(k, v) }
def parse2: Parser[Boolean] = (key ~ eq ~ value) ^^ { case k ~ e ~ v => foo(k, v.toString) }
def parseLine(line: String): Boolean = {
parseAll(parse2, line) match {
case Success(matched, _) => true
case Failure(msg, _) => false
case Error(msg, _) => false
}
}
}
val mp = new MyParser()
for (line <- List("hey = hou", "hello = world ppl", "foo = bar baz blup", "foo= bar baz", "foo =bar baz")) {
println(" (Matches: " + mp.parseLine(line) + ")")
}
Now the parser rejects the lines where there is no whitespace around the equal sign:
hey = List(hou) (Matches: true)
hello = List(world, ppl) (Matches: true)
foo = List(bar, baz, blup) (Matches: true)
(Matches: false)
(Matches: false)
The bug with * instead of + in string has been removed, just like in the previous version.
Edit: I've fixed the problem - I was incorrectly calling .map(f => f.typeSignature.asInstanceOf[TypeRef].args.head) on recursiveOpt, which meant that field.name was giving me the wrong field name in my copy method. I've removed the map and everything is working now.
I am writing a macro that will create a map of update methods for a case class, e.g.
case class Inner(innerStr: String)
case class Outer(outerStr: String, inner: Inner, innerOpt: Option[Inner])
should produce an update map for Outer that is something like
val innerMap = Map("innerStr" -> {(json: JsValue) => Try{(inner: Inner) => inner.copy(innerStr = json)}})
val outerMap = Map("outerStr" -> {(json: JsValue) => Try{(outer: Outer) => outer.copy(outerStr = json)}},
"inner.innerStr" -> {(json: JsValue) => Try{(outer: Outer) => innerMap.get("innerStr").get(json).flatMap(update => outer.copy(inner = update(outer.inner)))},
"innerOpt.innerStr" -> {(json: JsValue) => Try{(outer: Outer) => innerMap.get("innerStr").get(json).flatMap(update => outer.copy(inner = outer.inner.map(inner => update(inner))))})
which would then be called like
val oldOuter = Outer("str", Inner("str"), Some(Inner("str")))
val updatedOuter = outerMap.get("inner.innerStr").get(JsString("newStr")).get(oldOuter)
The idea is that given a json kv pair, I can retrieve the appropriate update method from the map using the key and then apply the update using the value, using implicit conversions to convert from the json value to the appropriate type.
My macro is working for the case of a flat case class, e.g. Inner(innerStr: String), and for a nested case class, e.g. Outer(outerStr: String, inner: Inner). However, the case of the nested option case class, Outer(outerStr: String, innerOpt: Option[Inner]), is crashing the compiler. I'm not sure if I'm doing something disastrously wrong, or if there's a bug in the compiler, or third option. This was done using the Scala 2.11.0-M7 REPL
Below is my code - I'm constructing a Map that accepts String input instead of JsValue input so that I don't need to import the play framework into my REPL. The blacklist filters out fields that should not be in the update map (e.g. one of the case classes we're applying this to has fields like "crypted_password" and "salt" that should never be updated via json sent in through a REST route). baseMethods constructs the key -> method tuples for the flat case, recursiveMethods constructs the key-method tuples for the nested case, and recursiveOptMethods constructs the key-value tuples for the nested option case; at the bottom of the macro these are all merged into a flat sequence and a placed in a Map.
I've tested the code in the recursiveOptMethods quasiquotes to ensure that I'm constructing a properly typed sequence of tuples and haven't found an error (also, this code is extremely similar to the recursiveMethods quasiquotes, which are functioning correctly), and I've tested the code that constructs the base, recursive, and recursiveOpt sequences of symbols and they seem to be doing their job.
Any help as to why I'm crashing the compiler would be greatly appreciated.
import scala.language.experimental.macros
def copyTestImpl[T: c.WeakTypeTag](c: scala.reflect.macros.Context)(blacklist: c.Expr[String]*): c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] = {
import c.universe._
val blacklistList: Seq[String] = blacklist.map(e => c.eval(c.Expr[String](c.resetAllAttrs(e.tree))))
def isCaseClass(tpe: Type): Boolean = tpe.typeSymbol.isClass && tpe.typeSymbol.asClass.isCaseClass
def isCaseClassOpt(tpe: Type): Boolean = tpe.typeSymbol.name.decoded == "Option" && isCaseClass(tpe.asInstanceOf[TypeRef].args.head)
def rec(tpe: Type): c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] = {
val typeName = tpe.typeSymbol.name.decoded
val fields = tpe.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramss.head.filterNot(field => blacklistList.contains(typeName + "." + field.name.decoded))
val recursive = fields.filter(f => isCaseClass(f.typeSignature))
val recursiveOpt = fields.filter(f => isCaseClassOpt(f.typeSignature))
val base = fields.filterNot(f => isCaseClass(f.typeSignature) || isCaseClassOpt(f.typeSignature))
val recursiveMethods = recursive.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
val map = rec(field.typeSignature)
q"""{
val innerMap = $map
innerMap.toSeq.map(tuple => ($fieldNameDecoded + "." + tuple._1) -> {
(str: String) => {
val innerUpdate = tuple._2(str)
innerUpdate.map(innerUpdate => (outer: $tpe) => outer.copy($fieldName = innerUpdate(outer.$fieldName)))
}
})}"""
}}
val recursiveOptMethods = recursiveOpt.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
val map = rec(field.typeSignature.asInstanceOf[TypeRef].args.head)
q"""{
val innerMap = $map
innerMap.toSeq.map(tuple => ($fieldNameDecoded + "." + tuple._1) -> {
(str: String) => {
val innerUpdate = tuple._2(str)
innerUpdate.map(innerUpdate => (outer: $tpe) => outer.copy($fieldName = (outer.$fieldName).map(inner => innerUpdate(inner))))
}
})}"""
}}
val baseMethods = base.map {
field => {
val fieldName = field.name
val fieldNameDecoded = fieldName.decoded
val fieldType = field.typeSignature
val fieldTypeName = fieldType.toString
q"""{
$fieldNameDecoded -> {
(str: String) => scala.util.Try {
val x: $fieldType = str
(t: $tpe) => t.copy($fieldName = x)
}.recoverWith {
case e: Exception => scala.util.Failure(new IllegalArgumentException("Failed to parse " + str + " as " + $typeName + "." + $fieldNameDecoded + ": " + $fieldTypeName))
}
}}"""
}}
c.Expr[Map[String, (String) => scala.util.Try[(T) => T]]] {
q"""{ Map((List(..$recursiveMethods).flatten ++ List(..$recursiveOptMethods).flatten ++ List(..$baseMethods)):_*) }"""
}
}
rec(weakTypeOf[T])
}
def copyTest[T](blacklist: String*) = macro copyTestImpl[T]
And the top and bottom of my error from the 2.11.0-M7 REPL when calling copyTest[Outer]() (where Outer has an Option[Inner] field)
scala> copyTest[Outer]()
scala.MatchError: AnyRef
with Product
with Serializable {
val innerStr: String
private[this] val innerStr: String
def <init>(innerStr: String): Inner
def copy(innerStr: String): Inner
def copy$default$1: String #scala.annotation.unchecked.uncheckedVariance
override def productPrefix: String
def productArity: Int
def productElement(x$1: Int): Any
override def productIterator: Iterator[Any]
def canEqual(x$1: Any): Boolean
override def hashCode(): Int
override def toString(): String
override def equals(x$1: Any): Boolean
} (of class scala.reflect.internal.Types$ClassInfoType)
at scala.reflect.internal.Variances$class.inType$1(Variances.scala:181)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.util.Collections$class.map2(Collections.scala:55)
at scala.reflect.internal.SymbolTable.map2(SymbolTable.scala:14)
at scala.reflect.internal.Variances$class.inArgs$1(Variances.scala:176)
at scala.reflect.internal.Variances$class.inType$1(Variances.scala:189)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.Variances$$anonfun$inArgs$1$1.apply(Variances.scala:176)
at scala.reflect.internal.util.Collections$class.map2(Collections.scala:55)
at scala.reflect.internal.SymbolTable.map2(SymbolTable.scala:14)
at scala.tools.nsc.typechecker.Analyzer$typerFactory$$anon$3.run(Analyzer.scala:93)
at scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1603)
at scala.tools.nsc.Global$Run.compileUnits(Global.scala:1588)
at scala.tools.nsc.Global$Run.compileSources(Global.scala:1583)
at scala.tools.nsc.interpreter.IMain.compileSourcesKeepingRun(IMain.scala:387)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.compileAndSaveRun(IMain.scala:816)
at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.compile(IMain.scala:775)
at scala.tools.nsc.interpreter.IMain$Request.compile$lzycompute(IMain.scala:951)
at scala.tools.nsc.interpreter.IMain$Request.compile(IMain.scala:946)
at scala.tools.nsc.interpreter.IMain.compile(IMain.scala:530)
at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:518)
at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:516)
at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:748)
at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:793)
at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:660)
at scala.tools.nsc.interpreter.ILoop.processLine(ILoop.scala:427)
at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:444)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:862)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:848)
at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:848)
at scala.reflect.internal.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:95)
at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:848)
at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:81)
at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:94)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:103)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
That entry seems to have slain the compiler. Shall I replay
your session? I can re-run each line except the last one.
I found the problem - originally I had val recursiveOpt = fields.filter(f => isCaseClassOpt(f.typeSignature)).map(f => f.typeSignature.asInstanceOf[TypeRef].args.head), which meant that when I called field.name on the recursiveOpt fields I was getting the wrong name back.
I have multiple Option's. I want to check if they hold a value. If an Option is None, I want to reply to user about this. Else proceed.
This is what I have done:
val name:Option[String]
val email:Option[String]
val pass:Option[String]
val i = List(name,email,pass).find(x => x match{
case None => true
case _ => false
})
i match{
case Some(x) => Ok("Bad Request")
case None => {
//move forward
}
}
Above I can replace find with contains, but this is a very dirty way. How can I make it elegant and monadic?
Edit: I would also like to know what element was None.
Another way is as a for-comprehension:
val outcome = for {
nm <- name
em <- email
pwd <- pass
result = doSomething(nm, em, pwd) // where def doSomething(name: String, email: String, password: String): ResultType = ???
} yield (result)
This will generate outcome as a Some(result), which you can interrogate in various ways (all the methods available to the collections classes: map, filter, foreach, etc.). Eg:
outcome.map(Ok(result)).orElse(Ok("Bad Request"))
val ok = Seq(name, email, pass).forall(_.isDefined)
If you want to reuse the code, you can do
def allFieldValueProvided(fields: Option[_]*): Boolean = fields.forall(_.isDefined)
If you want to know all the missing values then you can find all missing values and if there is none, then you are good to go.
def findMissingValues(v: (String, Option[_])*) = v.collect {
case (name, None) => name
}
val missingValues = findMissingValues(("name1", option1), ("name2", option2), ...)
if(missingValues.isEmpty) {
Ok(...)
} else {
BadRequest("Missing values for " + missingValues.mkString(", ")))
}
val response = for {
n <- name
e <- email
p <- pass
} yield {
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
Or, with Scalaz:
val response = (name |#| email |#| pass) { (n, e, p) =>
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
if ((name :: email :: pass :: Nil) forall(!_.isEmpty)) {
} else {
// bad request
}
I think the most straightforward way would be this:
(name,email,pass) match {
case ((Some(name), Some(email), Some(pass)) => // proceed
case _ => // Bad request
}
A version with stone knives and bear skins:
import util._
object Test extends App {
val zero: Either[List[Int], Tuple3[String,String,String]] = Right((null,null,null))
def verify(fields: List[Option[String]]) = {
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) =>
val u = i match {
case 0 => t copy (_1 = s)
case 1 => t copy (_2 = s)
case 2 => t copy (_3 = s)
}
Right(u)
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
val res = verify(List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify(List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
}
The same thing, using reflection to generalize the tuple copy.
The downside is that you must tell it what tuple to expect back. In this form, reflection is like one of those Stone Age advances that were so magical they trended on twitter for ten thousand years.
def verify[A <: Product](fields: List[Option[String]]) = {
import scala.reflect.runtime._
import universe._
val MaxTupleArity = 22
def tuple = {
require (fields.length <= MaxTupleArity)
val n = fields.length
val tupleN = typeOf[Tuple2[_,_]].typeSymbol.owner.typeSignature member TypeName(s"Tuple$n")
val init = tupleN.typeSignature member nme.CONSTRUCTOR
val ctor = currentMirror reflectClass tupleN.asClass reflectConstructor init.asMethod
val vs = Seq.fill(n)(null.asInstanceOf[String])
ctor(vs: _*).asInstanceOf[Product]
}
def zero: Either[List[Int], Product] = Right(tuple)
def nextProduct(p: Product, i: Int, s: String) = {
val im = currentMirror reflect p
val ts = im.symbol.typeSignature
val copy = (ts member TermName("copy")).asMethod
val args = copy.paramss.flatten map { x =>
val name = TermName(s"_$i")
if (x.name == name) s
else (im reflectMethod (ts member x.name).asMethod)()
}
(im reflectMethod copy)(args: _*).asInstanceOf[Product]
}
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) => Right(nextProduct(t, i + 1, s))
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}.asInstanceOf[Either[List[Int], A]]
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
type T3 = Tuple3[String,String,String]
val res = verify[T3](List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify[T3](List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
I know this doesn't scale well, but would this suffice?
(name, email, pass) match {
case (None, _, _) => "name"
case (_, None, _) => "email"
case (_, _, None) => "pass"
case _ => "Nothing to see here"
}
I tried to parse an input of two Ints and some elements and the end:
import scala.util.parsing.combinator.JavaTokenParsers
class X extends JavaTokenParsers {
lazy val elems = elem("wrong elem", "#WB-" contains _)
lazy val lists = repsep(rep(elems), ",")
lazy val p1 = int ~ int ~ lists
lazy val p2 = int ~ int ~ (whiteSpace ~> lists)
def go[A](p: Parser[A]) = parseAll(p, "1 2 WB#,---,BBB") match {
case NoSuccess(msg, _) => sys.error(msg)
case _ =>
}
lazy val int: Parser[Int] =
wholeNumber ^^ {
try _.toInt catch {
case e: NumberFormatException => sys.error("invalid number")
}
}
}
An example input is given in method go. The Ints and the elements at the end have to be delimited by spaces. But this works only for the Ints and not for the elements. When I type in
val x = new X
x go x.p1
I get following error:
java.lang.RuntimeException: string matching regex `\z' expected but `W' found
But when I type in
x go x.p1
I get:
java.lang.RuntimeException: string matching regex `\s+' expected but `W' found
At the end I want to have a Parser[Int ~ Int ~ List[List[Char]]]. Why does inserting white spaces in front of elem not work? And how can I get this code to work?
Just replace elems by a RegEx Parser :
import scala.util.parsing.combinator.JavaTokenParsers
class X extends JavaTokenParsers {
lazy val elems = "[#WB-]".r
lazy val lists = repsep(rep(elems), ",")
lazy val p1 = int ~ int ~ lists
def go[A](p: Parser[A]) = parseAll(p, "1 2 WB#,---,BBB") match {
case NoSuccess(msg, _) => sys.error(msg)
case _ =>
}
lazy val int: Parser[Int] =
wholeNumber ^^ {
try _.toInt catch {
case e: NumberFormatException => sys.error("invalid number")
}
}
}
i have removed p2 because is not useful now