Skip to content
This repository has been archived by the owner on Oct 24, 2024. It is now read-only.

Positions for TOML nodes? #19

Open
olafurpg opened this issue Aug 13, 2020 · 2 comments
Open

Positions for TOML nodes? #19

olafurpg opened this issue Aug 13, 2020 · 2 comments

Comments

@olafurpg
Copy link

Thank you for creating this project! I'm using toml-scala in new configuration library that I'm working on (adaptation of https://github.com/scalameta/metaconfig) and I'm curious if it would be possible to somehow expose position information in the toml.Value._ case classes.

My goal is to be able to report positioned type error messages when decoding TOML into Scala case classes, for example

# config.toml
booleanField = "stringValue"

The reported error would look like this

config.toml:0 error: Type mismatch;
  found    : String
  expected : Boolean
booleanField = "stringValue"
               ^

One way to achieve this with an immutable ADT with minimal boilerplate is to add a private var into the superclass

abstract class Value {
  private var myIndex: Option[Int] = None
  def position: Option[Int] = myIndex
  def withPosition(newIndex: Int): Value = {
    val copy = copyThis()
    copy.myIndex = Some(newIndex)
    copy
  }
  private[this] def copyThis(): Value = this match {
    case Value.Str(value) => Value.Str(value)
    // ...
  }
}

As long as copyThis().myIndex = is the only place where you mutate the var then the class remains effectively immutable. The position field can be added in a backwards compatible way, it doesn't affect Value equality semantics.

The Rules parser can then construct nodes with the Index fastparse parser and construct nodes with something like this (I didn't check if it compiles)

  val basicStr: Parser[Value.Str] =
    P(Index ~ (DoubleQuote.toString ~/ (strChars | escape).rep.! ~ DoubleQuote.toString))
      .map((index, str) => Value.Str(Unescape.unescapeJavaString(str)).withPosition(index))
@olafurpg
Copy link
Author

Sidenote: it would be even better to get range positions if possible. It's probably not much more work compared to adding offset positions.

@olafurpg
Copy link
Author

Another alternative solution would be to add a new visitor API to construct TOML nodes, which the parser would use instead of constructing toml.Value._ nodes directly.

class TomlVisitor[T] {
  def visitString(value: String, startOffset: Int, endOffset: Int): T
  // ...
}

class ValueTomlVisitor[toml.Value] extends TomlVistor[toml.Value] {
  def visitString(value: String, startOffset: Int, endOffset: Int) = toml.Value.Str(value)
  // ...
}

class Rules[T](visitor: TomlVisitor[T]) {
  val root: T = ...
}

Similar to https://github.com/typelevel/jawn

Related blog post https://www.lihaoyi.com/post/ZeroOverheadTreeProcessingwiththeVisitorPattern.html

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant