LogoMore arsing around with Scala

More arsing around with Scala. This time, trying to reimplement lesscss. This is about two hours worth of hacking:

object LessParser extends RegexParsers {
  def parse(spec: String) : Sequence = {
    parseAll(sequence, spec) match {
      case Success(l, _) => l
      case Failure(m, _) => throw new IllegalArgumentException(m)
      case Error(m, _)   => throw new IllegalArgumentException(m)
    }
  }
  def sequence = rep(namedNested | atom) ^^ {
    Sequence(_)
  }
  def namedNested = nestedName~"{"~rep(atom)~"}" ^^ {
    case name~"{"~contents~"}" => NamedNested(name, contents)
  }
  def nestedName = """[\.\#][a-zA-Z]+""".r
  def atom = variableDef | variableUse | text
  def text = "[^@{}]+".r ^^ { Text(_) }
  def variableDef = "@"~variableName~":"~variableValue~";" ^^ {
    case "@"~name~":"~value~";" => VariableDef(name, value)
  }
  def variableUse = "@"~variableName ^^ {
    case "@"~name => VariableUse(name)
  }
  def variableName = "[a-z-]+".r
  def variableValue = "[^;]+".r
}

abstract class Tree {
  def eval(defs: Map[String, String], out: StringBuffer) : Map[String, String]
}
case class Sequence(parts: List[Tree]) extends Tree {
  def eval(defs: Map[String, String], out: StringBuffer) : Map[String, String] = {
    parts.foldLeft (defs) { (accumDefs : Map[String, String], part : Tree) =>
      part.eval(accumDefs, out)
    }
  }
}
case class NamedNested(name: String, contents: List[Atom]) extends Tree {
  def eval(defs: Map[String, String], out: StringBuffer) : Map[String, String] = {
    out.append(name)
    out.append(" {")
    contents.foldLeft (defs) { (accumDefs : Map[String, String], part : Tree) =>
      part.eval(accumDefs, out)
    }
    out.append("}")
    defs
  }
}
abstract case class Atom() extends Tree
case class VariableDef(name: String, value: String) extends Atom {
  def eval(defs: Map[String, String], out: StringBuffer) : Map[String, String] = {
    defs + (name -> value)
  }
}
case class VariableUse(name: String) extends Atom {
  def eval(defs: Map[String, String], out: StringBuffer) : Map[String, String] = {
    out.append(defs(name))
    defs
  }
}
case class Text(value: String) extends Atom {
  def eval(defs: Map[String, String], out: StringBuffer) : Map[String, String] = {
    out.append(value)
    defs
  }
}

object Less {
  def main(args : Array[String]) : Unit = {
    println(LessParser.parse("@nice-blue: #5B83AD;"))
    println(LessParser.parse("@nice-blue"))
    println(LessParser.parse("@nice-blue: #5B83AD; @nice-blue"))
    println(LessParser.parse("@nice-blue: #5B83AD; #header { background-color: @nice-blue; }"))

    var fullExample = """
    @nice-blue: #5B83AD;
    #header { background-color: @nice-blue; }
    """

    var buf = new StringBuffer
    LessParser.parse(fullExample).eval(Map(), buf)
    println(buf)
  }
}

Output:

Sequence(List(VariableDef(nice-blue,#5B83AD)))
Sequence(List(VariableUse(nice-blue)))
Sequence(List(VariableDef(nice-blue,#5B83AD), VariableUse(nice-blue)))
Sequence(List(VariableDef(nice-blue,#5B83AD), NamedNested(#header,List(Text(background-color: ),
VariableUse(nice-blue), Text(; )))))
#header {background-color: #5B83AD; }

It does variables and the beginnings of mixins. It's actually quite lame e.g. it throws away whitespace. Hohum, time to learn more about parsers in Scala...