/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx object Parse { def apply[A](string:String)(implicit parser:Parsable[A]):A = parser(string) def split[A](string:String)(implicit parser:Parsable[A]):Vector[A] = parser.split(string.trim) def split[A](string:String,sep:String)(implicit parser:Parsable[A]):Vector[A] = parser.split(string.trim, sep) def get[A](string:String)(implicit parser:Parsable[A]):Option[A] = parser.get(string) implicit class ParseStringContext(val sc: StringContext) extends AnyVal{ def p[T](args:Any*)(implicit parser:Parsable[T]):T = parser(sc.s(args:_*)) //first wrap the sc with the normal s interpreter then do the implicit drx string parsing } /**Split on the highest precedence split character. Parse.split without a split string uses this. note: if string contains the follow chars a trailing or leading is required to specify the split character */ def autoSplit(str:String):Array[String] = { val sepList = List(";", ",", "&&", "||", "&", "|", " to ", " ") val sepPat = sepList.find{str contains _}.getOrElse(sepList.last) //find the highest precedence split val escapeChars = Set('&','|') //custom Regex quote since 2.10 doesn't have this fuction and scalaJS is non standard regex from java val sepPatEscaped = sepPat.foldLeft(""){case (s,c) => val e = if(escapeChars contains c) "\\" else "" s + e + c.toString } str.trim.split(sepPatEscaped).map{_.trim}.filter{_ != ""} } } trait Parsable[+A]{// extends MacroParsable[A]{ def apply(string:String):A def split(string:String):Vector[A] = Parse.autoSplit(string).toVector map apply //toVector since Array from split is invarient but Vector is Covarient like Parsable[+T] def split(string:String,sep:String):Vector[A] = (string split sep).toVector map apply //toVector since Array from split is invarient but Vector is Covarient like Parsable[+T] def get(string:String):Option[A] = Try{apply(string)}.toOption //TODO return Either or Try with warning info } trait ParsableLowPriorityImplicits{ private val boolKeys:Map[String,Boolean] = Map( "true" -> true, "false" -> false, "on" -> true, "off" -> false, "yes" -> true, "no" -> false, "y" -> true, "n" -> false, "1" -> true, "0" -> false, "t" -> true, "f" -> false, "good" -> true, "bad" -> false, "+" -> true, "-" -> false ) private val charKeys:Map[String,Char] = Map( "max" -> Char.MaxValue, "min" -> Char.MinValue //Char is unsigned... so +- does not make sense ) private val intKeys:Map[String,Int] = Map( "max" -> Int.MaxValue, "min" -> Int.MinValue, "+" -> 1, "-" -> -1 ) private val longKeys:Map[String,Long] = Map( "max" -> Long.MaxValue, "+" -> 1L, "min" -> Long.MinValue, "-" -> -1L ) private val floatKeys:Map[String,Float] = Map( "nan" -> Float.NaN, "inf" -> Float.PositiveInfinity, "+inf" -> Float.PositiveInfinity, "-inf" -> Float.NegativeInfinity, "max" -> Float.MaxValue, "min" -> Float.MinValue, "+" -> 1f, "-" -> -1f ) private val doubleKeys:Map[String,Double] = Map( "nan" -> Double.NaN, "inf" -> Double.PositiveInfinity, "+inf" -> Double.PositiveInfinity, "-inf" -> Double.NegativeInfinity, "max" -> Double.MaxValue, "min" -> Double.MinValue, "e" -> E, "pi" -> Pi, "π" -> Pi, "tau" -> Tau, "τ" -> Tau, "+" -> 1d, "-" -> -1d ) private val SIPrefix:Map[Char,Double] = Map( 'E' -> 1E15, 'T' -> 1E12, 'G' -> 1E9, 'M' -> 1E6, //mega 'k' -> 1E3, //kilo 'm' -> 1E-3, //milli 'u' -> 1E-6, //micro 'μ' -> 1E-6, //micro 'n' -> 1E-9, 'p' -> 1E-12 ) private val intDigits = "-+0123456789".toSet private val decDigits = ".-+0123456789Ee".toSet private val allDigits = decDigits ++ SIPrefix.keys private def getLong(value:String,base:Int, prefixDigits:String,validDigits:String):Option[Long] = { def isPrefix = prefixDigits.toSet val norm = value.trim.toUpperCase val v = if((norm.size > 1) && (norm(0) == '0') && isPrefix(norm(1))) norm drop 1 else norm val triggers = prefixDigits.toSet val digits = if(triggers(v.head)) Some(v drop 1) else if(triggers(v.last)) Some(v dropRight 1) else None val res = digits map {d => val parseString = d filter validDigits.toSet java.lang.Long.parseLong(parseString,base) } // println(s"value:$value base:$base digits:$digits getLong:$res") res } private def getBin(value:String) = getLong(value, 2, "B", "-+01") private def getOct(value:String) = getLong(value, 8, "O", "-+01234567") private def getHex(value:String) = getLong(value,16, "XH#", "-+0123456789ABCDEF") private def applySiPrefix(value:String):Option[Double] = for(prefix <- value.lastOption; scale <- SIPrefix.get(prefix) ) yield { if(value.size == 1) scale else ParsableDouble(value.init)*scale } /** General number converter with included simple math calculator with simple ^ / * + - precedence */ private def mkNumber[T](value:String, fromString:String => T, fromDouble:Double => T, fromLong:Long => T):T = { lazy val lcValue = value.toLowerCase //math ops of - and + require a space so they are not interpreted as prefix if(value endsWith "%") fromDouble(ParsableDouble((value dropRight 1).trim)/100.0) else if(value endsWith "dB") fromDouble(ParsableDouble((value dropRight 2).trim).dB) else if(value contains "- ") fromDouble(value.split("""\- """) map {s => ParsableDouble(s)} reduce (_ - _)) else if(value contains "+ ") fromDouble(value.split("""\+ """) map {s => ParsableDouble(s)} reduce (_ + _)) else if(value contains '*') fromDouble(value.split("""\*+""") map {s => ParsableDouble(s)} reduce (_ * _)) else if(value contains '/') fromDouble(value.split("""\/+""") map {s => ParsableDouble(s)} reduce (_ / _)) else if(value contains '^') fromDouble(value.split("""\^+""") map {s => ParsableDouble(s)} reduce (_ ** _)) else if(doubleKeys contains lcValue) fromDouble(doubleKeys(lcValue)) else{ getHex(value).map{fromLong} orElse getOct(value).map{fromLong} orElse getBin(value).map{fromLong} orElse applySiPrefix(value).map{fromDouble} getOrElse { //this mutable implementation was much cleaner to read/faster but harder to debug than an immutable solution var isInt = true var foundDecimal = false var clean = "" for(c <- value){ if( intDigits doesNotContain c) isInt = false val skip = c == '.' && foundDecimal if( decDigits.contains(c) && !skip) clean += c if( c == '.') foundDecimal = true } if(isInt) fromString(clean) //more preformant for integers else fromDouble(clean.toDouble) } } } implicit object ParsableSymbol extends Parsable[Symbol]{ def apply(v:String):Symbol = Symbol(v) //Symbol wrapper } implicit object ParsableRegex extends Parsable[Regex]{ def apply(v:String):Regex = v.r //pass it with the scala regex generator } //-- numeric parse types // using def mkNumber[T](value:String, fromString:String => T, fromDouble:Double => T, fromLong:Long => T):T implicit object ParsableChar extends Parsable[Char]{ //fromString fromDouble, fromLong def apply(v:String):Char= charKeys.getOrElse(v.toLowerCase, mkNumber(v, _.toInt.toChar, _.toChar, _.toChar) ) } implicit object ParsableInt extends Parsable[Int]{ def apply(v:String):Int = intKeys.getOrElse(v.toLowerCase, mkNumber(v, _.toInt, _.toInt, _.toInt) ) } implicit object ParsableLong extends Parsable[Long]{ def apply(v:String):Long = longKeys.getOrElse(v.toLowerCase, mkNumber(v, _.toLong, _.toLong, _.toLong) ) } implicit object ParsableFloat extends Parsable[Float]{ def apply(v:String):Float = floatKeys.getOrElse(v.toLowerCase, mkNumber(v, _.toFloat, _.toFloat, _.toFloat) ) } implicit object ParsableDouble extends Parsable[Double]{ def apply(v:String):Double = mkNumber(v,_.toDouble, _.toDouble, _.toDouble) //internally checks the keys TODO why } implicit object ParsableBoolean extends Parsable[Boolean]{ def apply(v:String):Boolean = boolKeys.getOrElse(v.toLowerCase, v.toBoolean) } //--drx helpers types are all included in their own object files } object Parsable extends ParsableLowPriorityImplicits{ implicit object ParsableString extends Parsable[String]{ def apply(v:String):String = v //pass it as is } }