Skip to content

Instantly share code, notes, and snippets.

@tstone
Created August 31, 2013 06:35
Show Gist options
  • Save tstone/6396579 to your computer and use it in GitHub Desktop.
Save tstone/6396579 to your computer and use it in GitHub Desktop.
A little evening project to test a proof of concept idea about turning SMACSS into it's own language.
import scala.util.parsing.combinator.JavaTokenParsers
object Parser extends JavaTokenParsers {
object Renderer {
def render(ms: List[Module]): String = {
// Assemble a map
val names = ms.map(_.name)
val modules = Map(names.zip(ms).toArray: _*)
// Reduce to CSS
modules.foldLeft("") { case (acc, (_, m)) =>
m.lineage = resolveLineage(m, modules)
acc + "\n" + render(m)
}
}
private def resolveLineage(module: Module, ms: Map[String, Module]): String = {
if (module.parentName.isEmpty) module.name
else resolveLineage(ms.get(module.parentName.get).get, ms) + "-" + module.name
}
private def render(m: Module): String = {
"/* === module [ " + m.name + " ] ============================ */\n" +
m.rules.map(render(_, "." + m.lineage)).mkString("\n")
}
private def render(rule: Rule, prefix: String): String =
prefix + " " + rule.selector + " {\n" +
rule.properties.map(render(_)).mkString("\n") +
"\n}\n"
private def render(prop: Property): String = " " + prop.name + ": " + prop.value + ";"
}
class Module(val name: String, val parentName: Option[String], val rules: List[Rule]) {
var lineage: String = name
}
class Rule(val selector: String, val properties: List[Property])
class Property(val name: String, var value: String)
def toString(a: Any): String = a match {
case None => ""
case Some(a) => toString(a)
case s: String => s
case list: List[Any] => list.foldLeft("") { _ + toString(_) }
case ~(a, b) => toString(a) + toString(b)
}
def toList[T](a: Any): List[T] = a match {
case list: List[Any] => list.asInstanceOf[List[T]]
}
def toModule(a: Any): Module = a match {
case ~(~(name: String, parent: Option[Any]), block: List[Any]) =>
new Module(name, parent.asInstanceOf[Option[String]], block.asInstanceOf[List[Rule]])
}
def toRule(a: Any): Rule = a match {
case ~(selector: String, properties: List[Any]) => new Rule(selector, properties.asInstanceOf[List[Property]])
}
def toProperty(a: Any): Property = a match {
case ~(name: String, value: String) => new Property(name, value)
}
def file = rep(module) ^^ toList[Module]
def module = "@module" ~> identifier ~ opt("::" ~> identifier) ~ moduleBlock ^^ toModule
def moduleBlock = "{" ~> rep(rule) <~ "}" ^^ toList[Rule]
def rule = identifier ~ ruleBlock ^^ toRule
def ruleBlock = "{" ~> rep(property) <~ "}" ^^ toList[Property]
def property = identifier ~ propertyValue <~ opt(termination) ^^ toProperty
def propertyValue = ":" ~> regex("[^;]+"r) ^^ toString
def valueChar = regex("[^;]+"r)
def identifier = letter ~ rep(letter | wholeNumber | symbol) ^^ toString
def letter = regex("[A-Za-z]"r)
def symbol = "_" | "-"
def termination = "\n" | ";"
def main(args: Array[String]) {
val code = """
@module third :: second {
div { font-size: 15px; }
ul {
list-style-type: none;
display: inline-block;
}
}
@module first{div{color:#333;}}
@module fourth :: third { aside { display: inline; } }
@module second :: first {
li {color: steelblue;}
}
"""
val m = parseAll(file, code).get
println(Renderer.render(m))
}
}
@tstone
Copy link
Author

tstone commented Aug 31, 2013

Output from the above is:

/* === module [ third ] ============================ */
.first-second-third div {
  font-size: 15px;
}

.first-second-third ul {
  list-style-type: none;
  display: inline-block;
}

/* === module [ first ] ============================ */
.first div {
  color: #333;
}

/* === module [ fourth ] ============================ */
.first-second-third-fourth aside {
  display: inline;
}

/* === module [ second ] ============================ */
.first-second li {
  color: steelblue;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment