Skip to content

edadma/toml

Repository files navigation

toml

Maven Central Last Commit GitHub Scala Version ScalaJS Version Scala Native Version TOML specification

Cross-platform TOML parser for Scala 3, built on scala-parser-combinators (StdLexical / StdTokenParsers). Published artifacts target JVM, JavaScript (Scala.js), and Native (Scala Native).

TOML specification conformance

TOML v1.0.0 highlights:

  • Basic strings: only the escapes listed in v1.0.0; \e and \xHH are rejected (v1.1.0).
  • Arrays: v1.0.0 allows mixed types (e.g. [1, 2.0] and nested arrays of different inner types).
  • Single-line basic strings: tab is allowed (v1.0.0), unlike v0.5.0.

Implemented builder checks: duplicate [table] headers; [name] on a key that is already an array of tables; [[name]] when name is already a normal table; static arr = [] then [[arr]]; dotted-key table paths that cannot be reopened with a [...] header; super-table headers like [x] after [x.y.z] when valid; new array-of-tables row clears nested [child...] header state so tables like [a.b.c] can reopen under each [[a.b]] (toml-test valid/table/array-table-array.toml).

Official toml-test corpus (JVM)

Clone toml-lang/toml-test to third_party/toml-test (so third_party/toml-test/tests/files-toml-1.0.0 exists). Then:

sbt "tomlJVM/testOnly io.github.edadma.toml.TomlOfficial1_0_0Spec"

The suite checks every valid case in files-toml-1.0.0 against the tagged JSON expected by toml-test, and every invalid case in that list is required to fail TomlParser.parse (strict UTF-8 when reading files in the test harness).

Three invalid one-line multiline-string fixtures (multiline-quotes-01, literal-multiline-quotes-01 / -02) are rejected via an exact-line pre-check in TomlParser.parse, because the lexer can otherwise accept ambiguous closing-quote runs; everything else is handled by the lexer, decoder, and builder.

Reference spec copies live in this repo as v0.5.0.md, v1.0.0.md, and v1.1.0.md.

Not TOML 1.1.0: documents that rely on v1.1-only features (notably \e, \xHH, and other 1.1 deltas) may fail to parse.

Module coordinates

libraryDependencies += "io.github.edadma" %% "toml" % "0.1.0" // JVM

libraryDependencies += "io.github.edadma" %%% "toml" % "0.1.0" // cross: JS / Native via %%%

(Replace the version with the current release from Maven Central.)

Usage

Parse a UTF-8 string; failures are a left value with a short message (lexer, parser, or builder / TOML 1.0.0 rules).

import io.github.edadma.toml.{TomlParser, TomlValue}
import TomlValue.*

val input = """title = "Demo"
              |count = 42
              |enabled = true
              |""".stripMargin

TomlParser.parse(input) match
  case Left(msg)  => println(s"parse error: $msg")
  case Right(doc) =>
    val title = doc.getString("title")
    val count = doc.getInt("count") // Option[Long] — TOML integers are 64-bit

TomlDocument.root is a VectorMap[String, TomlValue]: it behaves like a map but iterates keys in file order (first-seen order), which helps config emitters round-trip section order. Nested tables are TomlValue.Obj with fields: VectorMap[String, TomlValue] (same ordering).

Dotted lookups on the document (each segment is one key; dots are separators only):

doc.getString("kernel.name")
doc.getInt("memory.page_size")
doc.getTable("server")      // Option[VectorMap[String, TomlValue]]
doc.get("nested.path")      // Option[TomlValue]

TomlValue extractors throw TomlTypeMismatch if the shape is wrong: toStr, toLong / toInt (both return Long for TOML integers), toDouble, toBool, toTable, toArr, plus toOffsetDateTime, toLocalDateTime, toLocalDate, toLocalTime.

val cfg = """
  |[server]
  |host = "127.0.0.1"
  |port = 8080
  |""".stripMargin

TomlParser.parse(cfg).foreach { doc =>
  val host = doc.getString("server.host")
  val port = doc.getInt("server.port")
  doc.getTable("server").foreach { t =>
    val h = t.get("host").map(_.toStr)
  }
}

Arrays are TomlValue.Arr (elems: List[TomlValue]). TOML 1.0.0 allows mixed element types; this parser accepts them.

val nums = TomlParser.parse("nums = [1, 2, 3]\n").toOption
  .flatMap(_.root.get("nums")) match
  case Some(Arr(xs)) => xs.collect { case Num(n) => n }
  case _             => Nil

Array of tables [[items]] becomes an Arr of Obj (one object per segment).

val aot = """
  |[[items]]
  |id = 1
  |[[items]]
  |id = 2
  |""".stripMargin

TomlParser.parse(aot).foreach { doc =>
  doc.root.get("items").foreach {
    case Arr(rows) =>
      rows.foreach {
        case Obj(m) => println(m.get("id"))
        case _      => ()
      }
    case _ =>
  }
}

Times and dates use java.time on the JVM: OffsetDateTime, LocalDateTime, LocalDate, LocalTime (see TomlValue cases). On Scala.js and Scala Native the same types come from scala-java-time.

Project structure

toml/
├── shared/                 # Parser, lexer, AST, builder (all platforms)
├── jvm/                    # JVM-only stub (`platform`)
├── js/                     # JS stub (`platform`)
├── native/                 # Native stub (`platform`)
├── project/
│   ├── build.properties
│   └── plugins.sbt
├── build.sbt
├── v0.5.0.md               # TOML v0.5.0 spec (reference copy)
├── v1.0.0.md               # TOML v1.0.0 spec (reference copy)
└── v1.1.0.md               # TOML v1.1.0 spec (reference copy)

Prerequisites

  • JDK 11 or higher
  • sbt 1.12.8 or higher
  • Node.js (for Scala.js tests)
  • LLVM/Clang (for Scala Native)

Building and testing

git clone [email protected]:edadma/toml.git
cd toml
sbt compile
sbt test

sbt tomlJVM/test
sbt tomlJS/test
sbt tomlNative/test

Breaking changes in 0.1.0

  • TomlValue.Integer was renamed to TomlValue.Num.
  • TomlDocument.root and TomlValue.Obj.fields are VectorMap (ordered) instead of an unordered Map.

Tests

  • TomlParserSpec — quick regression checks, insertion order, and document/value accessors.
  • TomlV050Spec — broader hand-written cases (mostly shaped like v0.5.0 examples) covering keys, integers, floats, booleans, offset/local date-times, basic and multiline literal strings, arrays, tables, inline tables, array-of-tables, and comments, checked against v1.0.0 rules where they apply (including bare keys that are only ASCII digits).
  • TomlV100Spec — duplicate [table] headers, dotted-key vs [table] redefine rules, [[...]] vs [...] conflicts, and related TOML 1.0.0 builder errors.
  • TomlOfficial1_0_0Spec (JVM) — full toml-test files-toml-1.0.0 valid + invalid lists when third_party/toml-test is present (or TOML_TEST_ROOT is set).

License

This project is licensed under the ISC License; see LICENSE.

About

Cross-platform TOML v1.0.0 parser for Scala 3

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages