joesan
joesan

Reputation: 15355

DSL Like Syntax in Scala

I'm trying to come up with a CSV Parser that can be called like this:

parser parse "/path/to/csv/file" using parserConfiguration

Where the parser will be a class that contains the target case class into which the CSV file will be parsed into:

    class CSVParser[A] {

      def parse(path: String) = Source.fromFile(fromFilePath).getLines().mkString("\n")

      def using(cfg: ParserConfig) = ??? How do I chain this optionally???
    }

val parser = CSVParser[SomeCaseClass]

I managed to get up to the point where I can call:

parser parse "/the/path/to/the/csv/file/"

But I do not want to run the parse method yet as I want to apply the configuration using the using like DSL as mentioned above! So there are two rules here. If the caller does not supply a parserConfig, I should be able to run with the default, but if the user supplies a parserConfig, I want to apply the config and then run the parse method. I tried it with a combination of implicits, but could not get them to work properly!

Any suggestions?

EDIT: So the solution looks like this as per comments from "Cyrille Corpet":

class CSVReader[A] {
  def parse(path: String) = ReaderWithFile[A](path)

  case class ReaderWithFile[A](path: String) {
    def using(cfg: CSVParserConfig): Seq[A] = {
      val lines = Source.fromFile(path).getLines().mkString("\n")
      println(lines)
      println(cfg)
      null
    }
  }
  object ReaderWithFile {
    implicit def parser2parsed[A](parser: ReaderWithFile[A]): Seq[A] = parser.using(defaultParserCfg)
  }
}
object CSVReader extends App {

  def parser[A] = new CSVReader[A]

  val sss: Seq[A] = parser parse "/csv-parser/test.csv" // assign this to a val so that the implicit conversion gets applied!! Very important to note!
}

I guess I need to get the implicit in scope at the location where I call the parser parse, but at the same time I do not want to mess up the structure that I have above!

Upvotes: 2

Views: 200

Answers (2)

Jasper-M
Jasper-M

Reputation: 15086

If you replace using with an operator with a higher precedence than parse you can get it to work without needing extra type annotations. Take for instance <<:

object parsedsl {
  class ParserConfig
  object ParserConfig { 
    val default = new ParserConfig
  }

  case class ParseUnit(path: String, config: ParserConfig)
  object ParseUnit {
    implicit def path2PU(path: String) = ParseUnit(path, ParserConfig.default)
  }

  implicit class ConfigSyntax(path: String) {
    def <<(config: ParserConfig) = ParseUnit(path, config)
  }

  class CSVParser {
    def parse(pu: ParseUnit) = "parsing"
  }
}

import parsedsl._

val parser = new CSVParser

parser parse "path" << ParserConfig.default

parser parse "path"

Upvotes: 2

Cyrille Corpet
Cyrille Corpet

Reputation: 5315

Your parse method should just give a partial result, without doing anything at all. To deal with default implem, you can use implicit conversion to output type:

class CSVParser[A] {
  def parse(path: String) = ParserWithFile[A](path)
}

case class ParserWithFile[A](path: String) {
  def using(cfg: ParserConfig): A = ??? 
}

object ParserWithFile {
  implicit def parser2parsed[A](parser: ParserWithFile[A]): A = parser.using(ParserConfig.default)
}

val parser = CSVParser[SomeCaseClass] 

Upvotes: 1

Related Questions