I was wondering if there is any way of preserving indentation while doing string interpolation in scala. Essentially, I was wondering if I could interpose my own StringContext. Macros would address this problem, but I'd like to wait until they are official.
This is what I want:
val x = "line1 \nline2"
val str = s"> ${x}"
str should evaluate to
> line1
line2
Answering my question, and converting Daniel Sobral's very helpful answer to code. Hopefully it will be of use to someone else with the same issue. I have not used implicit classes since I am still pre-2.10.
Usage:
import Indenter._ and use string interpolation like so e" $foo "
Example
import Indenter._
object Ex extends App {
override def main(args: Array[String]) {
val name = "Foo"
val fields = "x: Int\ny:String\nz:Double"
// fields has several lines. All of them will be indented by the same amount.
print (e"""
class $name {
${fields}
}
""")
}
}
should print
class Foo
x: Int
y: String
z: Double
Here's the custom indenting context.
class IndentStringContext(sc: StringContext) {
def e(args: Any*):String = {
val sb = new StringBuilder()
for ((s, a) <- sc.parts zip args) {
sb append s
val ind = getindent(s)
if (ind.size > 0) {
sb append a.toString().replaceAll("\n", "\n" + ind)
} else {
sb append a.toString()
}
}
if (sc.parts.size > args.size)
sb append sc.parts.last
sb.toString()
}
// get white indent after the last new line, if any
def getindent(str: String): String = {
val lastnl = str.lastIndexOf("\n")
if (lastnl == -1) ""
else {
val ind = str.substring(lastnl + 1)
if (ind.trim.isEmpty) ind // ind is all whitespace. Use this
else ""
}
}
}
object Indenter {
// top level implicit defs allowed only in 2.10 and above
implicit def toISC(sc: StringContext) = new IndentStringContext(sc)
}
You can write your own interpolators, and you can shadow the standard interpolators with your own. Now, I have no idea what's the semantic behind your example, so I'm not even going to try.
Check out my presentation on Scala 2.10 on either Slideshare or SpeakerDeck, as they contain examples on all the manners in which you can write/override interpolators. Starts on slide 40 (for now -- the presentation might be updated until 2.10 is finally out).
For Anybody seeking a post 2.10 answer:
object Interpolators {
implicit class Regex(sc: StringContext) {
def r = new util.matching.Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*)
}
implicit class IndentHelper(val sc: StringContext) extends AnyVal {
import sc._
def process = StringContext.treatEscapes _
def ind(args: Any*): String = {
checkLengths(args)
parts.zipAll(args, "", "").foldLeft("") {
case (a, (part, arg)) =>
val processed = process(part)
val prefix = processed.split("\n").last match {
case r"""([\s|]+)$d.*""" => d
case _ => ""
}
val argLn = arg.toString
.split("\n")
val len = argLn.length
// Todo: Fix newline bugs
val indented = argLn.zipWithIndex.map {
case (s, i) =>
val res = if (i < 1) { s } else { prefix + s }
if (i == len - 1) { res } else { res + "\n" }
}.mkString
a + processed + indented
}
}
}
}
Here's a short solution. Full code and tests on Scastie. There are two versions there, a plain indented interpolator, but also a slightly more complex indentedWithStripMargin interpolator which allows it to be a bit more readable:
assert(indentedWithStripMargin"""abc
|123456${"foo\nbar"}-${"Line1\nLine2"}""" == s"""|abc
|123456foo
| bar-Line1
| Line2""".stripMargin)
Here is the core function:
def indentedHelper(parts: List[String], args: List[String]): String = {
// In string interpolation, there is always one more string than argument
assert(parts.size == 1+args.size)
(parts, args) match {
// The simple case is where there is one part (and therefore zero args). In that case,
// we just return the string as-is:
case (part0 :: Nil, Nil) => part0
// If there is more than one part, we can simply take the first two parts and the first arg,
// merge them together into one part, and then use recursion. In other words, we rewrite
// indented"A ${10/10} B ${2} C ${3} D ${4} E"
// as
// indented"A 1 B ${2} C ${3} D ${4} E"
// and then we can rely on recursion to rewrite that further as:
// indented"A 1 B 2 C ${3} D ${4} E"
// then:
// indented"A 1 B 2 C 3 D ${4} E"
// then:
// indented"A 1 B 2 C 3 D 4 E"
case (part0 :: part1 :: tailparts, arg0 :: tailargs) => {
// If 'arg0' has newlines in it, we will need to insert spaces. To decide how many spaces,
// we count many characters after after the last newline in 'part0'. If there is no
// newline, then we just take the length of 'part0':
val i = part0.reverse.indexOf('\n')
val n = if (i == -1)
part0.size // if no newlines in part0, we just take its length
else
i // the number of characters after the last newline
// After every newline in arg0, we must insert 'n' spaces:
val arg0WithPadding = arg0.replaceAll("\n", "\n" + " "*n)
val mergeTwoPartsAndOneArg = part0 + arg0WithPadding + part1
// recurse:
indentedHelper(mergeTwoPartsAndOneArg :: tailparts, tailargs)
}
// The two cases above are exhaustive, but the compiler thinks otherwise, hence we need
// to add this dummy.
case _ => ???
}
}
Related
I am reading a TSV file and using using something like this:
case class Entry(entryType: Int, value: Int)
def filterEntries(): Iterator[Entry] = {
for {
line <- scala.io.Source.fromFile("filename").getLines()
} yield new Entry(line.split("\t").map(x => x.toInt))
}
Now I am both interested in filtering out entries whose entryType are set to 0 and ignoring lines with column count greater or lesser than 2 (that does not match the constructor). I was wondering if there's an idiomatic way to achieve this may be using pattern matching and unapply method in a companion object. The only thing I can think of is using .filter on the resulting iterator.
I will also accept solution not involving for loop but that returns Iterator[Entry]. They solutions must be tolerant to malformed inputs.
This is more state-of-arty:
package object liner {
implicit class R(val sc: StringContext) {
object r {
def unapplySeq(s: String): Option[Seq[String]] = sc.parts.mkString.r unapplySeq s
}
}
}
package liner {
case class Entry(entryType: Int, value: Int)
object I {
def unapply(s: String): Option[Int] = util.Try(s.toInt).toOption
}
object Test extends App {
def lines = List("1 2", "3", "", " 4 5 ", "junk", "0, 100000", "6 7 8")
def entries = lines flatMap {
case r"""\s*${I(i)}(\d+)\s+${I(j)}(\d+)\s*""" if i != 0 => Some(Entry(i, j))
case __________________________________________________ => None
}
Console println entries
}
}
Hopefully, the regex interpolator will make it into the standard distro soon, but this shows how easy it is to rig up. Also hopefully, a scanf-style interpolator will allow easy extraction with case f"$i%d".
I just started using the "elongated wildcard" in patterns to align the arrows.
There is a pupal or maybe larval regex macro:
https://github.com/som-snytt/regextractor
You can create variables in the head of the for-comprehension and then use a guard:
edit: ensure length of array
for {
line <- scala.io.Source.fromFile("filename").getLines()
arr = line.split("\t").map(x => x.toInt)
if arr.size == 2 && arr(0) != 0
} yield new Entry(arr(0), arr(1))
I have solved it using the following code:
import scala.util.{Try, Success}
val lines = List(
"1\t2",
"1\t",
"2",
"hello",
"1\t3"
)
case class Entry(val entryType: Int, val value: Int)
object Entry {
def unapply(line: String) = {
line.split("\t").map(x => Try(x.toInt)) match {
case Array(Success(entryType: Int), Success(value: Int)) => Some(Entry(entryType, value))
case _ =>
println("Malformed line: " + line)
None
}
}
}
for {
line <- lines
entryOption = Entry.unapply(line)
if entryOption.isDefined
} yield entryOption.get
The left hand side of a <- or = in a for-loop may be a fully-fledged pattern. So you may write this:
def filterEntries(): Iterator[Int] = for {
line <- scala.io.Source.fromFile("filename").getLines()
arr = line.split("\t").map(x => x.toInt)
if arr.size == 2
// now you may use pattern matching to extract the array
Array(entryType, value) = arr
if entryType == 0
} yield Entry(entryType, value)
Note that this solution will throw a NumberFormatException if a field is not convertible to an Int. If you do not want that, you'll have to encapsulate x.toInt with a Try and pattern match again.
I'm reading lines from a file
for (line <- Source.fromFile("test.txt").getLines) {
....
}
I basically want to get a list of paragraphs in the end. If a line is empty, that starts as a new paragraph, and I might want to parse some keyword - value pairs in the future.
The text file contains a list of entries like this (or something similar, like an Ini file)
User=Hans
Project=Blow up the moon
The slugs are going to eat the mustard. // multiline possible!
They are sneaky bastards, those slugs.
User=....
And I basically want to have a List[Project] where Project looks something like
class Project (val User: String, val Name:String, val Desc: String) {}
And the Description is that big chunk of text that doesn't start with a <keyword>=, but can stretch over any number of lines.
I know how to do this in an iterative style. Just do a list of checks for the keywords, and populate an instance of a class, and add it to a list to return later.
But I think it should be possible to do this in proper functional style, possibly with match case, yield and recursion, resulting in a list of objects that have the fields User, Project and so on. The class used is known, as are all the keywords, and the file format is not set in stone either. I'm mostly trying to learn better functional style.
You're obviously parsing something, so it might be the time to use... a parser!
Since your language seems to treat line breaks as significant, you will need to refer to this question to tell the parser so.
Apart from that, a rather simple implementation would be
import scala.util.parsing.combinator.RegexParsers
case class Project(user: String, name: String, description: String)
object ProjectParser extends RegexParsers {
override val whiteSpace = """[ \t]+""".r
def eol : Parser[String] = """\r?\n""".r
def user: Parser[String] = "User=" ~> """[^\n]*""".r <~ eol
def name: Parser[String] = "Project=" ~> """[^\n]*""".r <~ eol
def description: Parser[String] = repsep("""[^\n]+""".r, eol) ^^ { case l => l.mkString("\n") }
def project: Parser[Project] = user ~ name ~ description ^^ { case a ~ b ~ c => Project(a, b, c) }
def projects: Parser[List[Project]] = repsep(project,eol ~ eol)
}
And how to use it:
val sample = """User=foo1
Project=bar1
desc1
desc2
desc3
User=foo
Project=bar
desc4 desc5 desc6
desc7 desc8 desc9"""
import scala.util.parsing.input._
val reader = new CharSequenceReader(sample)
val res = ProjectParser.parseAll(ProjectParser.projects, reader)
if(res.successful) {
print("Found projects: " + res.get)
} else {
print(res)
}
Another possible implementation (since this parser is rather simple), using recursion:
import scala.io.Source
case class Project(user: String, name: String, desc: String)
#scala.annotation.tailrec
def parse(source: Iterator[String], list: List[Project] = Nil): List[Project] = {
val emptyProject = Project("", "", "")
#scala.annotation.tailrec
def parseProject(project: Option[Project] = None): Option[Project] = {
if(source.hasNext) {
val line = source.next
if(!line.isEmpty) {
val splitted = line.span(_ != '=')
parseProject(splitted match {
case (h, t) if h == "User" => project.orElse(Some(emptyProject)).map(_.copy(user = t.drop(1)))
case (h, t) if h == "Project" => project.orElse(Some(emptyProject)).map(_.copy(name = t.drop(1)))
case _ => project.orElse(Some(emptyProject)).map(project => project.copy(desc = (if(project.desc.isEmpty) "" else project.desc ++ "\n") ++ line))
})
} else project
} else project
}
if(source.hasNext) {
parse(source, parseProject().map(_ :: list).getOrElse(list))
} else list.reverse
}
And the test:
object Test {
def source = Source.fromString("""User=Hans
Project=Blow up the moon
The slugs are going to eat the mustard. // multiline possible!
They are sneaky bastards, those slugs.
User=Plop
Project=SO
Some desc""")
def test = println(parse(source.getLines))
}
Which gives:
List(Project(Hans,Blow up the moon,The slugs are going to eat the mustard. // multiline possible!
They are sneaky bastards, those slugs.), Project(Plop,SO,Some desc))
To answer your question without also tackling keyword parsing, fold over the lines and aggregate lines unless it's an empty one, in which case you start a new empty paragraph.
lines.foldLeft(List("")) { (l, x) =>
if (x.isEmpty) "" :: l else (l.head + "\n" + x) :: l.tail
} reverse
You'll notice this has some wrinkles in how it handles zero lines, and multiple and trailing empty lines. Adapt to your needs. Also if you are anal about string concatenations you can collect them in a nested list and flatten in the end (using .map(_.mkString)), this is just to showcase the basic technique of folding a sequence not to a scalar but to a new sequence.
This builds a list in reverse order because list prepend (::) is more efficient than appending to l in each step.
You're obviously building something, so you might want to try... a builder!
Like Jürgen, my first thought was to fold, where you're accumulating a result.
A mutable.Builder does the accumulation mutably, with a collection.generic.CanBuildFrom to indicate the builder to use to make a target collection from a source collection. You keep the mutable thing around just long enough to get a result. So that's my plug for localized mutability. Lest one assume that the path from List[String] to List[Project] is immutable.
To the other fine answers (the ones with non-negative appreciation ratings), I would add that functional style means functional decomposition, and usually small functions.
If you're not using regex parsers, don't neglect regexes in your pattern matches.
And try to spare the dots. In fact, I believe that tomorrow is a Spare the Dots Day, and people with sensitivity to dots are advised to remain indoors.
case class Project(user: String, name: String, description: String)
trait Sample {
val sample = """
|User=Hans
|Project=Blow up the moon
|The slugs are going to eat the mustard. // multiline possible!
|They are sneaky bastards, those slugs.
|
|User=Bob
|I haven't thought up a project name yet.
|
|User=Greta
|Project=Burn the witch
|It's necessary to escape from the witch before
|we blow up the moon. I hope Hans sees it my way.
|Once we burn the bitch, I mean witch, we can
|wreak whatever havoc pleases us.
|""".stripMargin
}
object Test extends App with Sample {
val kv = "(.*?)=(.*)".r
def nonnully(s: String) = if (s == null) "" else s + " "
val empty = Project(null, null, null)
val (res, dummy) = ((List.empty[Project], empty) /: sample.lines) { (acc, line) =>
val (sofar, cur) = acc
line match {
case kv("User", u) => (sofar, cur copy (user = u))
case kv("Project", n) => (sofar, cur copy (name = n))
case kv(k, _) => sys error s"Bad keyword $k"
case x if x.nonEmpty => (sofar, cur copy (description = s"${nonnully(cur.description)}$x"))
case _ if cur != empty => (cur :: sofar, empty)
case _ => (sofar, empty)
}
}
val ps = if (dummy == empty) res.reverse else (dummy :: res).reverse
Console println ps
}
The match can be mashed this way, too:
val (res, dummy) = ((List.empty[Project], empty) /: sample.lines) {
case ((sofar, cur), kv("User", u)) => (sofar, cur copy (user = u))
case ((sofar, cur), kv("Project", n)) => (sofar, cur copy (name = n))
case ((sofar, cur), kv(k, _)) => sys error s"Bad keyword $k"
case ((sofar, cur), x) if x.nonEmpty => (sofar, cur copy (description = s"${nonnully(cur.description)}$x"))
case ((sofar, cur), _) if cur != empty => (cur :: sofar, empty)
case ((sofar, cur), _) => (sofar, empty)
}
Before the fold, it seemed simpler to do paragraphs first. Is that imperative thinking?
object Test0 extends App with Sample {
def grafs(ss: Iterator[String]): List[List[String]] = {
val (g, rest) = ss dropWhile (_.isEmpty) span (_.nonEmpty)
val others = if (rest.nonEmpty) grafs(rest) else Nil
g.toList :: others
}
def toProject(ss: List[String]): Project = {
var p = Project("", "", "")
for (line <- ss; parts = line split '=') parts match {
case Array("User", u) => p = p.copy(user = u)
case Array("Project", n) => p = p.copy(name = n)
case Array(k, _) => sys error s"Bad keyword $k"
case Array(text) => p = p.copy(description = s"${p.description} $text")
}
p
}
val ps = grafs(sample.lines) map toProject
Console println ps
}
class Project (val User: String, val Name:String, val Desc: String) {}
object Project {
def apply(str: String): Project = {
val user = somehowFetchUserName(str)
val name = somehowFetchProjectName(str)
val desc = somehowFetchDescription(str)
new Project(user, name, desc)
}
}
val contents: Array[String] = Source.fromFile("test.txt").mkString.split("\\n\\n")
val list = contents map(Project(_))
will end up with the list of projects.
Let's say I want to parse a string with various opening and closing brackets (I used parentheses in the title because I believe it is more common -- the question is the same nevertheless) so that I get all the higher levels separated in a list.
Given:
[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]
I want:
List("[hello:=[notting],[hill]]", "[3.4(4.56676|5.67787)]", "[the[hill[is[high]]not]]")
The way I am doing this is by counting the opening and closing brackets and adding to the list whenever I get my counter to 0. However, I have an ugly imperative code. You may assume that the original string is well formed.
My question is: what would be a nice functional approach to this problem?
Notes: I have thought of using the for...yield construct but given the use of the counters I cannot get a simple conditional (I must have conditionals just for updating the counters as well) and I do not know how I could use this construct in this case.
Quick solution using Scala parser combinator library:
import util.parsing.combinator.RegexParsers
object Parser extends RegexParsers {
lazy val t = "[^\\[\\]\\(\\)]+".r
def paren: Parser[String] =
("(" ~ rep1(t | paren) ~ ")" |
"[" ~ rep1(t | paren) ~ "]") ^^ {
case o ~ l ~ c => (o :: l ::: c :: Nil) mkString ""
}
def all = rep(paren)
def apply(s: String) = parseAll(all, s)
}
Checking it in REPL:
scala> Parser("[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]")
res0: Parser.ParseResult[List[String]] = [1.72] parsed: List([hello:=[notting],[hill]], [3.4(4.56676|5.67787)], [the[hill[is[high]]not]])
What about:
def split(input: String): List[String] = {
def loop(pos: Int, ends: List[Int], xs: List[String]): List[String] =
if (pos >= 0)
if ((input charAt pos) == ']') loop(pos-1, pos+1 :: ends, xs)
else if ((input charAt pos) == '[')
if (ends.size == 1) loop(pos-1, Nil, input.substring(pos, ends.head) :: xs)
else loop(pos-1, ends.tail, xs)
else loop(pos-1, ends, xs)
else xs
loop(input.length-1, Nil, Nil)
}
scala> val s1 = "[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]"
s1: String = [hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]
scala> val s2 = "[f[sad][add]dir][er][p]"
s2: String = [f[sad][add]dir][er][p]
scala> split(s1) foreach println
[hello:=[notting],[hill]]
[3.4(4.56676|5.67787)]
[the[hill[is[high]]not]]
scala> split(s2) foreach println
[f[sad][add]dir]
[er]
[p]
Given your requirements counting the parenthesis seems perfectly fine. How would you do that in a functional way? You can make the state explicitly passed around.
So first we define our state which accumulates results in blocks or concatenates the next block and keeps track of the depth:
case class Parsed(blocks: Vector[String], block: String, depth: Int)
Then we write a pure function that processed that returns the next state. Hopefully, we can just carefully look at this one function and ensure it's correct.
def nextChar(parsed: Parsed, c: Char): Parsed = {
import parsed._
c match {
case '[' | '(' => parsed.copy(block = block + c,
depth = depth + 1)
case ']' | ')' if depth == 1
=> parsed.copy(blocks = blocks :+ (block + c),
block = "",
depth = depth - 1)
case ']' | ')' => parsed.copy(block = block + c,
depth = depth - 1)
case _ => parsed.copy(block = block + c)
}
}
Then we just used a foldLeft to process the data with an initial state:
val data = "[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]"
val parsed = data.foldLeft(Parsed(Vector(), "", 0))(nextChar)
parsed.blocks foreach println
Which returns:
[hello:=[notting],[hill]]
[3.4(4.56676|5.67787)]
[the[hill[is[high]]not]]
You have an ugly imperative solution, so why not make a good-looking one? :)
This is an imperative translation of huynhjl's solution, but just posting to show that sometimes imperative is concise and perhaps easier to follow.
def parse(s: String) = {
var res = Vector[String]()
var depth = 0
var block = ""
for (c <- s) {
block += c
c match {
case '[' => depth += 1
case ']' => depth -= 1
if (depth == 0) {
res :+= block
block = ""
}
case _ =>
}
}
res
}
Try this:
val s = "[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]"
s.split("]\\[").toList
returns:
List[String](
[hello:=[notting],[hill],
3.4(4.56676|5.67787),
the[hill[is[high]]not]]
)
I am getting an error from a piece of code. I will only show one line of code, at least the line I believe is causing it from the error report. It is:
b = temp(temp.length-1).toInt; //temp is an ArrayBuffer[String]
the error is:
For input string: "z"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Integer.parseInt(Integer.java:449)
at java.lang.Integer.parseInt(Integer.java:499)
at scala.collection.immutable.StringLike$class.toInt(StringLike.scala:231)
at scala.collection.immutable.StringOps.toInt(StringOps.scala:31)
at Driver$.stringParse$1(Driver.scala:59)
at Driver$.main(Driver.scala:86)
at Driver.main(Driver.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:78)
at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:24)
at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:88)
at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:78)
at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101)
at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:33)
at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:40)
at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:56)
at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:80)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:89)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
From what I can tell, it is causing an issue with this. Since it is immutable, I know it cannot be changed. But I am not sure. I am basing this off of
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
Once I do something like my lone of code above, does it change the whole object? Temp is an ArrayBuffer[String]. So I am trying to access a string representation of a number, and convert it. But in doing so, does this change what it is and keep me from doing anything?
If you believe putting all my code will be helpful, let me know to edit it, but it is a lot and I don't want to annoy anybody. I appreciate anybody who can help me understand this!
*EDIT: MY CODE (Only here to help me figure out my error, but not necessary to look at. I just can't see where its giving me this error).
The point of my code is to parse either one of those strings at the top. It puts together and into one string and then reads the other two symbols to go with it. It parses str just fine, but it finds a problem when it reads "z" in str2, and "y" in str3. As one can see, the problem is with the second string after the and when recursing. Its also important to note that the string has to be in that form. So it can only be parsed like "(and x (and y z))", but not in any other way that makes it more convenient.
val str = "(and x y)";
val str2 = "(and x (and y z))"; //case with expression on th right side
val str3 = "(and (and x y) z)"; //case with expression ont he left side
var i = 0; //just counter used to loop through the finished parsed array to make a list
//var position = 0; //this is used for when passing it in the parser to start off at zero
var hold = new ArrayBuffer[String]();//finished array should be here
def stringParse ( exp: String, expreshHolder: ArrayBuffer[String] ): ArrayBuffer[String] = { //takes two arguments, string, arraybuffer
var b = 0; //position of where in the expression String I am currently in
var temp = expreshHolder; //holder of expressions without parens
var arrayCounter = 0;
if(temp.length == 0)
b = 0;
else {
b = temp(temp.length-1).toInt;
temp.remove(temp.length-1);
arrayCounter = temp.length;
} //this sets the position of wherever the string was read last plus removes that check from the end of the ArrayBuffer
//just counts to make sure an empty spot in the array is there to put in the strings
if(exp(b) == '(') {
b = b + 1;
while(exp(b) == ' '){b = b + 1;} //point of this is to just skip any spaces between paren and start of expression type
if(exp(b) == 'a') {
//first create the 'and', 'or', 'not' expression types to figure out
temp += exp(b).toString;
b = b+1;
temp(arrayCounter) = temp(arrayCounter) + exp(b).toString; //concatenates the second letter
b = b+1;
temp(arrayCounter) = temp(arrayCounter) + exp(b).toString; //concatenates the last letter for the expression type
//arrayCounter+=1;
//this part now takes the symbols and puts them in an array
b+=1;
while(exp(b) == ' ') {b+=1;} //just skips any spaces until it reaches the FIRST symbol
if(exp(b) == '(') {
temp += b.toString;
temp = stringParse(exp, temp);
b = temp(temp.length-1).toInt;
temp.remove(temp.length-1);
arrayCounter = temp.length-1
} else {
temp += exp(b).toString;
arrayCounter+=1; b+=1; }
while(exp(b) == ' ') {b+=1;} //just skips any spaces until it reaches the SECOND symbol
if(exp(b) == '(') {
temp += b.toString;
temp = stringParse(exp, temp);
b = temp(temp.length-1).toInt;
temp.remove(temp.length-1);
arrayCounter = temp.length-1
} else {
temp += exp(b).toString;
arrayCounter+=1;
b+=1;
}
temp;
} else { var fail = new ArrayBuffer[String]; fail +="failed"; fail;}
}
hold = stringParse(str2, ho );
for(test <- hold) println(test);
What does temp contain? Your code assumes that it contains Strings that can be converted to Ints, but it seems that you have a String "z" in there instead. That would produce the error:
scala> "z".toInt
java.lang.NumberFormatException: For input string: "z"
...
Here's a recreation of what temp might look like:
val temp = ArrayBuffer("1", "2", "z")
temp(temp.length-1).toInt //java.lang.NumberFormatException: For input string: "z"
So you need to figure out why some String "z" is getting into temp.
EDIT:
So you're adding "expressions" to temp (temp += exp(b).toString) and also adding indices (temp += b.toString). Then you're assuming that temp only holds indices (b = temp(temp.length-1).toInt). You need to decide what temp is for, and then use it exclusively for that purpose.
No, toInt doesn't change the object, it takes the object as an argument and returns an integer, leaving the object as is.
I can't understand you question because I can`t understand you code.
Let's try to simplify you code.
First of all: you have some expressions with expression type and list of operands:
scala> :paste
// Entering paste mode (ctrl-D to finish)
abstract sealed class Operand
case class IdentOperand(name: String) extends Operand { override def toString(): String = name }
case class IntOperand(i: Int) extends Operand { override def toString(): String = i.toString() }
case class ExprOperand(expr: Expression) extends Operand { override def toString(): String = expr.toString() }
case class Expression(exprType: String, operands: Seq[Operand]) {
override def toString(): String = operands.mkString("(" + exprType + " ", " ", ")")
}
// Exiting paste mode, now interpreting.
defined class Operand
defined class IdentOperand
defined class IntOperand
defined class ExprOperand
defined class Expression
scala> Expression("and", Seq(IdentOperand("x"), IdentOperand("y")))
res0: Expression = (and x y)
scala> Expression("and", Seq(IdentOperand("x"), ExprOperand(Expression("and", Seq(IdentOperand("y"), IdentOperand("z"))))))
res1: Expression = (and x (and y z))
scala> Expression("and", Seq(ExprOperand(Expression("and", Seq(IdentOperand("x"), IdentOperand("y")))), IdentOperand("z")))
res2: Expression = (and (and x y) z)
Now we have to parse strings to expressions of this type:
scala> import scala.util.parsing.combinator._
import scala.util.parsing.combinator._
scala> object ExspessionParser extends JavaTokenParsers {
| override def skipWhitespace = false;
|
| def parseExpr(e: String) = parseAll(expr, e)
|
| def expr: Parser[Expression] = "(" ~> exprType ~ operands <~ ")" ^^ { case exprType ~ operands => Expression(exprType, operands) }
| def operands: Parser[Seq[Operand]] = rep(" "~>operand)
| def exprType: Parser[String] = "and" | "not" | "or"
| def operand: Parser[Operand] = variable | exprOperand
| def exprOperand: Parser[ExprOperand] = expr ^^ (ExprOperand( _ ))
| def variable: Parser[IdentOperand] = ident ^^ (IdentOperand( _ ))
| }
defined module ExspessionParser
scala> ExspessionParser.parseExpr("(and x y)")
res3: ExspessionParser.ParseResult[Expression] = [1.10] parsed: (and x y)
scala> ExspessionParser.parseExpr("(and x (and y z))")
res4: ExspessionParser.ParseResult[Expression] = [1.18] parsed: (and x (and y z))
scala> ExspessionParser.parseExpr("(and (and x y) z)")
res5: ExspessionParser.ParseResult[Expression] = [1.18] parsed: (and (and x y) z)
And now (as far as I understand your code) we have to replace string operands (x, y, z) with integer values. Let's add these 2 methods to Expression class:
def replaceOperands(ints: Seq[Int]): Expression = replaceOperandsInner(ints)._2
private def replaceOperandsInner(ints: Seq[Int]): (Seq[Int], Expression) = {
var remainInts = ints
val replacedOperands = operands.collect{
case n: IdentOperand =>
val replacement = remainInts.head
remainInts = remainInts.tail
IntOperand(replacement)
case ExprOperand(e) =>
val (remain, replaced) = e.replaceOperandsInner(remainInts)
remainInts = remain
ExprOperand(replaced)
}
(remainInts, Expression(exprType, replacedOperands))
}
And now we can do this:
scala> ExspessionParser.parseExpr("(and (and x y) z)").get.replaceOperands(Seq(1, 2, 3))
res7: Expression = (and (and 1 2) 3)
And if you have integer values in string form, then you can just convert them first:
scala> Seq("1", "2", "3") map { _.toInt }
res8: Seq[Int] = List(1, 2, 3)
Is it possible to have angle brackets in method names , e.g. :
class Foo(ind1:Int,ind2:Int){...}
var v = new Foo(1,2)
v(1) = 3 //updates ind1
v<1> = 4 //updates ind2
The real situation is obviously more complicated than this!!I am trying to provide a convenient user interface.
This response is not meant to be taken too seriously - just a proof that this can almost be achieved using some hacks.
class Vector(values: Int*) {
val data = values.toArray
def < (i:Int) = new {
def `>_=`(x: Int) {
data(i) = x
}
def > {
println("value at "+ i +" is "+ data(i))
}
}
override def toString = data.mkString("<", ", ", ">")
}
val v = new Vector(1, 2, 3)
println(v) // prints <1, 2, 3>
v<1> = 10
println(v) // prints <1, 10, 3>
v<1> // prints: value at 1 is 10
Using this class we can have a vector that uses <> instead of () for "read" and write access.
The compiler (2.9.0.1) crashes if > returns a value. It might be a bug or a result of misusing >.
Edit: I was wrong; kassens's answer shows how to do it as you want.
It is not possible to implement a method that would be called when you write v<1> = 4 (except, maybe, if you write a compiler plugin?). However, something like this would be possible:
class Foo {
def at(i: Int) = new Assigner(i)
class Assigner(i: Int) {
def :=(v: Int) = println("assigning " + v + " at index " + i)
}
}
Then:
val f = new Foo
f at 4 := 6
With a little trickery you can actually get quite close to what you want.
object Foo {
val a:Array[Int] = new Array(100)
def <(i:Int) = new Updater(a, i)
}
class Updater(a:Array[Int], i:Int) {
def update(x:Int) {
a(i) = x
}
def >() = this
}
Foo<1>() = 123
I am not sure why Scala requires the () though. And yes, this is a bit of a hack...