Scala External DSL and Combinator Parsing Experimentation
Yesterday I was looking around on the inter-webs for a Scala project that was “iBatis” like, but more Scala oriented. I found some cool stuff (ScalaQL, and scala-sql-dsl for example) but nothing quite what I was looking for.
I kind of chewed on what exactly it was that I was looking for and this is my 10k/foot feature list:
- Ability to define the underlying data store in a DSL that can be understood by Scala
- Ability to map Scala classes to JDBC results by binding to artifacts of the data store
This is more or less what iBatis accomplishes with xml (now annotations) and a bunch of reflection. I’d like to see it done without XML and minimal reflection.
That sent me down the rabbit hole on how exactly one goes about defining a DSL in scala. I re-read chapter 31 in Programming in Scala. I also read chapter 11 in Programming Scala. Then I took a stab at writing a parser combinator for a very, very small subset of the “CREATE TABLE” DDL syntax for SQL.
import scala.util.parsing.combinator._
class TableDdlParser extends JavaTokenParsers {
def tables: Parser[Map[String, Any]] = rep(table) ^^ { Map() ++ _ }
def table: Parser[(String,Any)] =
("TABLE" ~ tableName ~ columns
^^ { case "TABLE" ~ tableName ~ tableContents => (tableName,tableContents) })
def tableName: Parser[String] = ident ^^ { case ident => ident }
def columns: Parser[Map[String, Any]] = "("~> repsep(column, ",") <~")" ^^ { Map() ++ _ }
def column: Parser[(String,Any)] =
columnName ~ dataType ^^ { case columnName ~ dataType => (columnName,dataType) }
def columnName: Parser[String] = ident ^^ { case ident => ident }
def dataType: Parser[Any] = "VARCHAR" | "INTEGER"
}
object TableDdlParserRunner extends TableDdlParser {
def main(args: Array[String]) {
val input =
"""TABLE person (first_name VARCHAR, last_name VARCHAR, age INTEGER)
TABLE place (city VARCHAR, state VARCHAR)"""
println(parseAll(tables,input))
}
}
Here is the output
[2.51] parsed:
Map(person -> Map(first_name -> VARCHAR,
last_name -> VARCHAR,
age -> INTEGER),
place -> Map(city -> VARCHAR,
state -> VARCHAR))
This experiment only supports two data types (VARCHAR and INTEGER), without any of the other metadata that is required for those types. I just wanted a feel for how hard it would be to write and didn’t want to get bogged down in the details. It turned out to be quite a bit easier than I thought it would be.
Next up is to figure out the best way to map this DDL information to a scala class (If I don’t get distracted by something else first).