78316
|
1 |
/* Title: Pure/General/toml.scala
|
78317
|
2 |
Author: Fabian Huch, TU Muenchen
|
78316
|
3 |
|
|
4 |
Support for TOML: https://toml.io/en/v1.0.0
|
78317
|
5 |
*/
|
|
6 |
|
78316
|
7 |
package isabelle
|
|
8 |
|
|
9 |
|
|
10 |
import TOML.Parsers.Keys
|
|
11 |
import TOML.Parse_Context.Seen
|
|
12 |
|
|
13 |
import java.lang.Long.parseLong
|
|
14 |
import java.lang.{String => Str}
|
|
15 |
import java.time.{LocalDate, LocalDateTime, LocalTime, OffsetDateTime}
|
|
16 |
|
|
17 |
import scala.{Boolean => Bool}
|
|
18 |
import scala.collection.immutable.ListMap
|
|
19 |
import scala.reflect.{ClassTag, classTag}
|
|
20 |
import scala.util.Try
|
|
21 |
import scala.util.matching.Regex
|
|
22 |
import scala.util.parsing.combinator
|
|
23 |
import scala.util.parsing.combinator.lexical.Scanners
|
|
24 |
import scala.util.parsing.input.CharArrayReader.EofCh
|
|
25 |
|
|
26 |
|
|
27 |
object TOML {
|
|
28 |
/* typed representation and access */
|
|
29 |
|
|
30 |
type Key = Str
|
|
31 |
|
|
32 |
sealed trait T
|
|
33 |
case class String(rep: Str) extends T
|
|
34 |
case class Integer(rep: Long) extends T
|
|
35 |
case class Float(rep: Double) extends T
|
|
36 |
case class Boolean(rep: Bool) extends T
|
|
37 |
case class Offset_Date_Time(rep: OffsetDateTime) extends T
|
|
38 |
case class Local_Date_Time(rep: LocalDateTime) extends T
|
|
39 |
case class Local_Date(rep: LocalDate) extends T
|
|
40 |
case class Local_Time(rep: LocalTime) extends T
|
|
41 |
|
|
42 |
class Array private(private val rep: List[T]) extends T {
|
|
43 |
override def hashCode(): Int = rep.hashCode()
|
|
44 |
override def equals(that: Any): Bool = that match {
|
|
45 |
case other: Array => rep == other.rep
|
|
46 |
case _ => false
|
|
47 |
}
|
|
48 |
|
|
49 |
class Values[A](pf: PartialFunction[T, A]) { def values: List[A] = rep.collect(pf).reverse }
|
|
50 |
lazy val string = new Values({ case s: String => s })
|
|
51 |
lazy val integer = new Values({ case i: Integer => i })
|
|
52 |
lazy val float = new Values({ case f: Float => f })
|
|
53 |
lazy val boolean = new Values({ case b: Boolean => b })
|
|
54 |
lazy val offset_date_time = new Values({ case o: Offset_Date_Time => o })
|
|
55 |
lazy val local_date_time = new Values({ case l: Local_Date_Time => l })
|
|
56 |
lazy val local_date = new Values({ case l: Local_Date => l })
|
|
57 |
lazy val local_time = new Values({ case l: Local_Time => l })
|
|
58 |
lazy val array = new Values({ case a: Array => a })
|
|
59 |
lazy val table = new Values({ case t: Table => t })
|
|
60 |
lazy val any = new Values({ case t => t })
|
|
61 |
|
|
62 |
def +(elem: T): Array = new Array(elem :: rep)
|
|
63 |
def ++(other: Array): Array = new Array(other.rep ::: rep)
|
|
64 |
def length: Int = rep.length
|
|
65 |
def is_empty: Bool = rep.isEmpty
|
|
66 |
}
|
|
67 |
|
|
68 |
object Array {
|
|
69 |
def apply(elems: Iterable[T]): Array = new Array(elems.toList.reverse)
|
|
70 |
def apply(elems: T*): Array = Array(elems)
|
|
71 |
}
|
|
72 |
|
|
73 |
class Table private(private val rep: Map[Key, T]) extends T {
|
|
74 |
override def hashCode(): Int = rep.hashCode()
|
|
75 |
override def equals(that: Any): Bool =
|
|
76 |
that match {
|
|
77 |
case other: Table => rep == other.rep
|
|
78 |
case _ => false
|
|
79 |
}
|
|
80 |
|
|
81 |
class Value[A: ClassTag](pf: PartialFunction[T, A]) {
|
|
82 |
def values: List[(Key, A)] =
|
|
83 |
rep.toList.collect { case (k, v) if pf.isDefinedAt(v) => k -> pf(v) }
|
|
84 |
def get(k: Key): Option[A] = rep.get(k).flatMap(v => PartialFunction.condOpt(v)(pf))
|
|
85 |
def apply(k: Key): A =
|
|
86 |
rep.get(k) match {
|
|
87 |
case Some(v) => PartialFunction.condOpt(v)(pf) match {
|
|
88 |
case Some(value) => value
|
|
89 |
case None =>
|
|
90 |
error("Expected" + classTag[A].runtimeClass.getName +
|
|
91 |
", got " + v.getClass.getSimpleName + " for key " + quote(k))
|
|
92 |
}
|
|
93 |
case None => error("Key " + quote(k) + " does not exist")
|
|
94 |
}
|
|
95 |
}
|
|
96 |
|
|
97 |
lazy val string = new Value({ case s: String => s })
|
|
98 |
lazy val integer = new Value({ case i: Integer => i })
|
|
99 |
lazy val float = new Value({ case f: Float => f })
|
|
100 |
lazy val boolean = new Value({ case b: Boolean => b })
|
|
101 |
lazy val offset_date_time = new Value({ case o: Offset_Date_Time => o })
|
|
102 |
lazy val local_date_time = new Value({ case l: Local_Date_Time => l })
|
|
103 |
lazy val local_date = new Value({ case l: Local_Date => l })
|
|
104 |
lazy val local_time = new Value({ case l: Local_Time => l })
|
|
105 |
lazy val array = new Value({ case a: Array => a })
|
|
106 |
lazy val table = new Value({ case t: Table => t })
|
|
107 |
lazy val any = new Value({ case t => t })
|
|
108 |
|
|
109 |
def +(elem: (Key, T)): Table = {
|
|
110 |
val (k, v) = elem
|
|
111 |
val v1 = rep.get(k) match {
|
|
112 |
case None => v
|
|
113 |
case Some(v0) =>
|
|
114 |
(v0, v) match {
|
|
115 |
case (t0: Table, t: Table) => t0 ++ t
|
|
116 |
case (a0: Array, a: Array) => a0 ++ a
|
|
117 |
case _ => error("Key already present: " + quote(k))
|
|
118 |
}
|
|
119 |
}
|
|
120 |
new Table(rep + (k -> v1))
|
|
121 |
}
|
|
122 |
def -(k: Key): Table = new Table(rep - k)
|
|
123 |
def ++(other: Table): Table = other.rep.foldLeft(this)(_ + _)
|
|
124 |
def domain: Set[Key] = rep.keySet
|
|
125 |
def is_empty: Bool = rep.isEmpty
|
|
126 |
}
|
|
127 |
|
|
128 |
object Table {
|
|
129 |
def apply(elems: Iterable[(Key, T)]): Table = elems.foldLeft(new Table(ListMap.empty))(_ + _)
|
|
130 |
def apply(elems: (Key, T)*): Table = Table(elems)
|
|
131 |
}
|
|
132 |
|
|
133 |
|
|
134 |
/* lexer */
|
|
135 |
|
|
136 |
object Kind extends Enumeration {
|
|
137 |
val KEYWORD, VALUE, STRING, MULTILINE_STRING, LINE_SEP, ERROR = Value
|
|
138 |
}
|
|
139 |
|
|
140 |
sealed case class Token(kind: Kind.Value, text: Str) {
|
|
141 |
def is_keyword(name: Str): Bool = kind == Kind.KEYWORD && text == name
|
|
142 |
def is_value: Bool = kind == Kind.VALUE
|
|
143 |
def is_string: Bool = kind == Kind.STRING
|
|
144 |
def is_multiline_string: Bool = kind == Kind.MULTILINE_STRING
|
|
145 |
def is_line_sep: Bool = kind == Kind.LINE_SEP
|
|
146 |
}
|
|
147 |
|
|
148 |
object Lexer extends Scanners with Scan.Parsers {
|
|
149 |
override type Elem = Char
|
|
150 |
type Token = TOML.Token
|
|
151 |
|
|
152 |
def errorToken(msg: Str): Token = Token(Kind.ERROR, msg)
|
|
153 |
|
|
154 |
val white_space: Str = " \t"
|
|
155 |
override val whiteSpace: Regex = ("[" + white_space + "]+").r
|
|
156 |
override def whitespace: Parser[Any] = rep(comment | many1(character(white_space.contains(_))))
|
|
157 |
|
|
158 |
def line_sep: Parser[Str] = rep1("\n" | s"\r\n" | EofCh) ^^ (cs => cs.mkString)
|
|
159 |
def line_sep_token: Parser[Token] = line_sep ^^ (s => Token(Kind.LINE_SEP, s))
|
|
160 |
|
|
161 |
def is_control(e: Elem): Bool =
|
|
162 |
e <= '\u0008' || ('\u000A' <= e && e <= '\u001F') || e == '\u007F'
|
|
163 |
|
|
164 |
override def comment: Parser[Str] = '#' ~>! many(character(c => !is_control(c)))
|
|
165 |
|
|
166 |
def keyword: Parser[Token] = one(character("{}[],=.".contains)) ^^ (s => Token(Kind.KEYWORD, s))
|
|
167 |
|
|
168 |
def is_value(c: Elem): Bool =
|
|
169 |
Symbol.is_ascii_letter(c) || Symbol.is_ascii_digit(c) || "_-:+".contains(c)
|
|
170 |
def value: Parser[Token] =
|
|
171 |
many1(character(is_value)) ~
|
|
172 |
opt(' ' ~ many1(character(is_value)) ^^ { case ws ~ s => ws.toString + s }) ~
|
|
173 |
opt('.' ~ many1(character(is_value)) ^^ { case dot ~ s => dot.toString + s}) ^^
|
|
174 |
{ case s ~ ss ~ sd => Token(Kind.VALUE, s + ss.getOrElse("") + sd.getOrElse("")) }
|
|
175 |
|
|
176 |
def string: Parser[Token] =
|
|
177 |
multiline_basic_string | basic_string | multiline_literal_string | literal_string
|
|
178 |
|
|
179 |
private def trim(s: Str): Str =
|
|
180 |
if (s.startsWith("\n")) s.stripPrefix("\n") else s.stripPrefix("\r\n")
|
|
181 |
|
|
182 |
def basic_string: Parser[Token] =
|
|
183 |
'"' ~> rep(basic_string_elem) <~ '"' ^^ (cs => Token(Kind.STRING, cs.mkString))
|
|
184 |
|
|
185 |
def multiline_basic_string: Parser[Token] =
|
|
186 |
"\"\"\"" ~>
|
|
187 |
rep(multiline_basic_string_elem |
|
|
188 |
("\"\"" | "\"") ~ multiline_basic_string_elem ^^ { case s ~ t => s + t }) ~
|
|
189 |
repeated(character(_ == '"'), 3, 5) ^^ { case cs ~ q =>
|
|
190 |
Token(Kind.MULTILINE_STRING, trim(cs.mkString + q.drop(3))) }
|
|
191 |
|
|
192 |
private def multiline_basic_string_elem: Parser[Str] =
|
|
193 |
('\\' ~ line_sep ~ rep(many1(character(white_space.contains)) | line_sep)) ^^ (_ => "") |
|
|
194 |
basic_string_elem ^^ (_.toString) | line_sep
|
|
195 |
|
|
196 |
def literal_string: Parser[Token] =
|
|
197 |
'\'' ~> rep(literal_string_elem) <~ '\'' ^^ (cs => Token(Kind.STRING, cs.mkString))
|
|
198 |
|
|
199 |
def multiline_literal_string: Parser[Token] =
|
|
200 |
"'''" ~>
|
|
201 |
rep(multiline_literal_string_elem |
|
|
202 |
("''" | "'") ~ multiline_literal_string_elem ^^ { case s ~ t => s + t }) ~
|
|
203 |
repeated(character(_ == '\''), 3, 5) ^^ { case cs ~ q =>
|
|
204 |
Token(Kind.MULTILINE_STRING, trim(cs.mkString + q.drop(3))) }
|
|
205 |
|
|
206 |
private def multiline_literal_string_elem: Parser[Str] =
|
|
207 |
line_sep | literal_string_elem ^^ (_.toString)
|
|
208 |
|
|
209 |
private def basic_string_elem: Parser[Elem] =
|
|
210 |
elem("", c => !is_control(c) && !"\"\\".contains(c)) | '\\' ~> string_escape
|
|
211 |
|
|
212 |
private def string_escape: Parser[Elem] =
|
|
213 |
elem("", "\"\\".contains(_)) |
|
|
214 |
elem("", "btnfr".contains(_)) ^^
|
|
215 |
{ case 'b' => '\b' case 't' => '\t' case 'n' => '\n' case 'f' => '\f' case 'r' => '\r' } |
|
|
216 |
('u' ~> repeated(character("0123456789abcdefABCDEF".contains(_)), 4, 4) |
|
|
217 |
'U' ~> repeated(character("0123456789abcdefABCDEF".contains(_)), 8, 8)) ^^
|
|
218 |
(s => java.lang.Integer.parseInt(s, 16).toChar)
|
|
219 |
|
|
220 |
private def literal_string_elem: Parser[Elem] = elem("", c => !is_control(c) && c != '\'')
|
|
221 |
|
|
222 |
def string_failure: Parser[Token] = ("\"" | "'") ~> failure("Unterminated string")
|
|
223 |
|
|
224 |
def token: Parser[Token] =
|
|
225 |
line_sep_token | keyword | value | string | string_failure | failure("Unrecognized token")
|
|
226 |
}
|
|
227 |
|
|
228 |
|
|
229 |
/* parser */
|
|
230 |
|
|
231 |
trait Parsers extends combinator.Parsers {
|
|
232 |
type Elem = Token
|
78317
|
233 |
|
|
234 |
|
78316
|
235 |
/* parse structure */
|
78317
|
236 |
|
78316
|
237 |
type Keys = List[Key]
|
|
238 |
|
|
239 |
sealed trait V
|
|
240 |
case class Primitive(t: T) extends V
|
|
241 |
case class Array(rep: List[V]) extends V
|
|
242 |
case class Inline_Table(elems: List[(Keys, V)]) extends V
|
|
243 |
|
|
244 |
sealed trait Def
|
|
245 |
case class Table(key: Keys, elems: List[(Keys, V)]) extends Def
|
|
246 |
case class Array_Of_Tables(key: Keys, elems: List[(Keys, V)]) extends Def
|
78317
|
247 |
|
78316
|
248 |
case class File(elems: List[(Keys, V)], defs: List[Def])
|
78317
|
249 |
|
78316
|
250 |
|
|
251 |
/* top-level syntax structure */
|
78317
|
252 |
|
78316
|
253 |
def key: Parser[Keys] = rep1sep(keys, $$$(".")) ^^ (_.flatten)
|
78317
|
254 |
|
78316
|
255 |
def string: Parser[String] =
|
|
256 |
elem("string", e => e.is_string || e.is_multiline_string) ^^ (s => String(s.text))
|
|
257 |
def integer: Parser[Integer] =
|
|
258 |
(decimal_int | binary_int | octal_int | hexadecimal_int) ^^ Integer.apply
|
|
259 |
def float: Parser[Float] = (symbol_float | number_float) ^^ Float.apply
|
|
260 |
def boolean: Parser[Boolean] = token("boolean", _.is_value, s => Boolean(Value.Boolean.parse(s)))
|
|
261 |
|
|
262 |
def offset_date_time: Parser[Offset_Date_Time] =
|
|
263 |
token("offset date-time", _.is_value,
|
|
264 |
s => Offset_Date_Time(OffsetDateTime.parse(s.replace(" ", "T"))))
|
|
265 |
def local_date_time: Parser[Local_Date_Time] =
|
|
266 |
token("local date-time", _.is_value,
|
|
267 |
s => Local_Date_Time(LocalDateTime.parse(s.replace(" ", "T"))))
|
|
268 |
def local_date: Parser[Local_Date] =
|
|
269 |
token("local date", _.is_value, s => Local_Date(LocalDate.parse(s)))
|
|
270 |
def local_time: Parser[Local_Time] =
|
|
271 |
token("local time", _.is_value, s => Local_Time(LocalTime.parse(s)))
|
|
272 |
|
|
273 |
def array: Parser[Array] =
|
|
274 |
$$$("[") ~>! repsep(opt(line_sep) ~> toml_value, opt(line_sep) ~ $$$(",")) <~!
|
|
275 |
opt(line_sep) ~! opt($$$(",")) ~! opt(line_sep) <~! $$$("]") ^^ Array.apply
|
|
276 |
|
|
277 |
def inline_table: Parser[Inline_Table] =
|
|
278 |
$$$("{") ~>! repsep(pair, $$$(",")) <~! $$$("}") ^^ Inline_Table.apply
|
78317
|
279 |
|
78316
|
280 |
def pair: Parser[(Keys, V)] = (key <~! $$$("=")) ~! toml_value ^^ { case ks ~ v => (ks, v) }
|
|
281 |
|
|
282 |
def table: Parser[Table] = $$$("[") ~> (key <~! $$$("]") ~! line_sep) ~! content ^^
|
|
283 |
{ case key ~ content => Table(key, content) }
|
|
284 |
|
|
285 |
def array_of_tables: Parser[Array_Of_Tables] =
|
|
286 |
$$$("[") ~ $$$("[") ~>! (key <~! $$$("]") ~! $$$("]") ~! line_sep) ~! content ^^
|
|
287 |
{ case key ~ content => Array_Of_Tables(key, content) }
|
|
288 |
|
|
289 |
def toml: Parser[File] =
|
|
290 |
(opt(line_sep) ~>! content ~! rep(table | array_of_tables)) ^^
|
|
291 |
{ case content ~ defs => File(content, defs) }
|
|
292 |
|
|
293 |
|
|
294 |
/* auxiliary */
|
|
295 |
|
|
296 |
private def $$$(name: Str): Parser[Token] = elem(name, _.is_keyword(name))
|
|
297 |
private def maybe[A, B](p: Parser[A], f: A => B): Parser[B] =
|
|
298 |
p ^^ (a => Try(f(a))) ^? { case util.Success(v) => v }
|
|
299 |
private def token[A](name: Str, p: Token => Bool, parser: Str => A): Parser[A] =
|
|
300 |
maybe(elem(name, p), s => parser(s.text))
|
|
301 |
private def prefixed[A](
|
|
302 |
prefix: Str, name: Str, p: Str => Bool, parser: Str => A
|
|
303 |
): Parser[A] =
|
|
304 |
token(name, e => e.is_value && e.text.startsWith(prefix) && p(e.text.stripPrefix(prefix)),
|
|
305 |
s => parser(s.stripPrefix(prefix)))
|
|
306 |
|
|
307 |
private def is_key(e: Elem): Bool = e.is_value && !e.text.exists("+: ".contains(_))
|
|
308 |
private def keys: Parser[Keys] =
|
|
309 |
token("string key", _.is_string, List(_)) | token("key", is_key, _.split('.').toList)
|
|
310 |
|
|
311 |
private def sep_surrounded(s: Str): Bool =
|
|
312 |
!s.startsWith("_") && !s.endsWith("_") && s.split('_').forall(_.nonEmpty)
|
|
313 |
private def no_leading_zero(s: Str): Bool = {
|
|
314 |
val t = s.replaceAll("_", "").takeWhile(_.isDigit)
|
|
315 |
t == "0" || !t.startsWith("0")
|
|
316 |
}
|
|
317 |
|
|
318 |
private def is_int(s: Str): Bool =
|
|
319 |
no_leading_zero(s.replaceAll("[-+]", "")) && sep_surrounded(s.replaceAll("[-+]", ""))
|
|
320 |
private def decimal_int: Parser[Long] =
|
|
321 |
token("integer", e => e.is_value && is_int(e.text), _.replace("_", "").toLong)
|
|
322 |
private def binary_int: Parser[Long] =
|
|
323 |
prefixed("0b", "integer", sep_surrounded, s => parseLong(s.replace("_", ""), 2))
|
|
324 |
private def octal_int: Parser[Long] =
|
|
325 |
prefixed("0o", "integer", sep_surrounded, s => parseLong(s.replace("_", ""), 8))
|
|
326 |
private def hexadecimal_int: Parser[Long] =
|
|
327 |
prefixed("0x", "integer", sep_surrounded, s => parseLong(s.replace("_", ""), 16))
|
|
328 |
|
|
329 |
private def is_float(s: Str): Bool =
|
|
330 |
s.exists(".eE".contains) && s.count("eE".contains) <= 1 &&
|
|
331 |
no_leading_zero(s.replaceAll("[-+]", "")) &&
|
|
332 |
sep_surrounded(s.replaceAll("[-+]", "").replaceAll("[.eE][+\\-]?", "_"))
|
|
333 |
private def number_float: Parser[Double] =
|
|
334 |
token("float", e => e.is_value && is_float(e.text), _.replace("_", "").toDouble)
|
|
335 |
private def symbol_float: Parser[Double] =
|
|
336 |
token("float", _.is_value, {
|
|
337 |
case "inf" | "+inf" => Double.PositiveInfinity
|
|
338 |
case "-inf" => Double.NegativeInfinity
|
|
339 |
case "nan" | "+nan" | "-nan" => Double.NaN
|
|
340 |
})
|
|
341 |
|
|
342 |
private def toml_value: Parser[V] = (string | float | integer | boolean | offset_date_time |
|
|
343 |
local_date_time | local_date | local_time) ^^ Primitive.apply | array | inline_table
|
|
344 |
|
|
345 |
private def line_sep: Parser[Any] = rep1(elem("line sep", _.is_line_sep))
|
|
346 |
|
|
347 |
private def content: Parser[List[(Keys, V)]] =
|
|
348 |
rep((key <~! $$$("=")) ~! toml_value <~! line_sep ^^ { case ks ~ v => ks -> v })
|
|
349 |
|
|
350 |
|
|
351 |
/* parse */
|
|
352 |
|
|
353 |
def parse(input: Str): File = {
|
|
354 |
val scanner = new Lexer.Scanner(Scan.char_reader(input + EofCh))
|
|
355 |
val result = phrase(toml)(scanner)
|
|
356 |
result match {
|
|
357 |
case Success(toml, _) => toml
|
|
358 |
case Failure(msg, next) => error("Malformed TOML input at " + next.pos + ": " + msg)
|
|
359 |
case Error(msg, next) => error("Error parsing toml at " + next.pos + ": " + msg)
|
|
360 |
}
|
|
361 |
}
|
|
362 |
}
|
|
363 |
|
|
364 |
object Parsers extends Parsers
|
|
365 |
|
|
366 |
|
|
367 |
/* checking and translating parse structure into toml */
|
|
368 |
|
|
369 |
private def prefixes(ks: Keys): Set[Keys] =
|
|
370 |
if (ks.isEmpty) Set.empty else Range.inclusive(1, ks.length).toSet.map(ks.take)
|
|
371 |
|
|
372 |
class Parse_Context private(var seen_tables: Map[Keys, Seen]) {
|
|
373 |
private def splits(ks: Keys): List[(Keys, Keys)] =
|
|
374 |
Range.inclusive(0, ks.length).toList.map(n => (ks.dropRight(n), ks.takeRight(n)))
|
|
375 |
|
|
376 |
private def recent_array(ks: Keys): (Keys, Seen, Keys) =
|
|
377 |
splits(ks).collectFirst {
|
|
378 |
case (ks1, ks2) if seen_tables.contains(ks1) => (ks1, seen_tables(ks1), ks2)
|
|
379 |
}.get
|
|
380 |
|
|
381 |
private def check_add(start: Int, ks: Keys, elem: Parsers.Def | Parsers.V): Unit = {
|
|
382 |
val (to, seen, rest, split) =
|
|
383 |
elem match {
|
|
384 |
case _: Parsers.Array_Of_Tables =>
|
|
385 |
val (_, seen, rest) = recent_array(ks.dropRight(1))
|
|
386 |
(ks, seen, rest ::: ks.takeRight(1), 0)
|
|
387 |
case _ =>
|
|
388 |
val (to, seen, rest) = recent_array(ks)
|
|
389 |
(to, seen, rest, start - to.length)
|
|
390 |
}
|
|
391 |
val (rest0, rest1) = rest.splitAt(split)
|
|
392 |
val implicitly_seen = prefixes(rest1.dropRight(1)).map(rest0 ::: _)
|
|
393 |
|
|
394 |
if (seen.inlines.exists(rest.startsWith(_))) error("Attempting to update an inline value")
|
|
395 |
|
|
396 |
val (inlines, tables) =
|
|
397 |
elem match {
|
|
398 |
case _: Parsers.Primitive =>
|
|
399 |
(seen.inlines, seen.tables ++ implicitly_seen)
|
|
400 |
case _: Parsers.Array =>
|
|
401 |
if (seen_tables.contains(ks)) error("Attempting to append with an inline array")
|
|
402 |
(seen.inlines + rest, seen.tables ++ implicitly_seen)
|
|
403 |
case _: Parsers.Inline_Table =>
|
|
404 |
if (seen.tables.exists(_.startsWith(rest)))
|
|
405 |
error("Attempting to add with an inline table")
|
|
406 |
(seen.inlines + rest, seen.tables ++ implicitly_seen)
|
|
407 |
case _: Parsers.Table =>
|
|
408 |
if (seen.tables.contains(rest)) error("Attempting to define a table twice")
|
|
409 |
(seen.inlines, seen.tables + rest)
|
|
410 |
case _: Parsers.Array_Of_Tables => (Set.empty, Set.empty)
|
|
411 |
}
|
|
412 |
|
|
413 |
seen_tables += to -> Seen(inlines, tables)
|
|
414 |
}
|
|
415 |
def check_add_def(ks: Keys, defn: Parsers.Def): Unit = check_add(0, ks, defn)
|
|
416 |
def check_add_value(ks0: Keys, ks1: Keys, v: Parsers.V): Unit =
|
|
417 |
check_add(ks0.length - 1, ks0 ::: ks1, v)
|
|
418 |
}
|
|
419 |
|
|
420 |
object Parse_Context {
|
|
421 |
case class Seen(inlines: Set[Keys], tables: Set[Keys])
|
|
422 |
|
|
423 |
def empty: Parse_Context = new Parse_Context(Map(Nil -> Seen(Set.empty, Set.empty)))
|
|
424 |
}
|
|
425 |
|
|
426 |
def parse(s: Str, context: Parse_Context = Parse_Context.empty): Table = {
|
|
427 |
val file = Parsers.parse(s)
|
|
428 |
|
|
429 |
def convert(ks0: Keys, ks1: Keys, v: Parsers.V): T = {
|
|
430 |
def to_table(ks: Keys, t: T): Table =
|
|
431 |
ks.dropRight(1).foldRight(Table(ks.last -> t))((k, v) => Table(k -> v))
|
|
432 |
def to_toml(v: Parsers.V): (T, Set[Keys]) = v match {
|
|
433 |
case Parsers.Primitive(t) => (t, Set.empty)
|
|
434 |
case Parsers.Array(rep) => (Array(rep.map(to_toml(_)._1)), Set.empty)
|
|
435 |
case Parsers.Inline_Table(elems) =>
|
|
436 |
elems.foreach(e => if (elems.count(_._1 == e._1) > 1)
|
|
437 |
error("Duplicate key in inline table"))
|
|
438 |
|
|
439 |
val (tables, pfxs, key_spaces) =
|
|
440 |
elems.map { (ks, v) =>
|
|
441 |
val (t, keys) = to_toml(v)
|
|
442 |
(to_table(ks, t), prefixes(ks), keys.map(ks ::: _) + ks)
|
|
443 |
}.unzip3
|
|
444 |
|
|
445 |
pfxs.foreach(pfx => if (key_spaces.count(pfx.intersect(_).nonEmpty) > 1)
|
|
446 |
error("Inline table not self-contained"))
|
|
447 |
|
|
448 |
(tables.foldLeft(Table())(_ ++ _), pfxs.toSet.flatten ++ key_spaces.toSet.flatten)
|
|
449 |
}
|
|
450 |
context.check_add_value(ks0, ks1, v)
|
|
451 |
to_toml(v)._1
|
|
452 |
}
|
|
453 |
|
|
454 |
def update(map: Table, ks: Keys, value: T): Table = {
|
|
455 |
val updated =
|
|
456 |
if (ks.length == 1) {
|
|
457 |
map.any.get(ks.head) match {
|
|
458 |
case Some(a: Array) =>
|
|
459 |
value match {
|
|
460 |
case a2: Array => a ++ a2
|
|
461 |
case _ => error("Table conflicts with previous array of tables")
|
|
462 |
}
|
|
463 |
case Some(t: Table) => value match {
|
|
464 |
case t2: Table =>
|
|
465 |
if (t.domain.intersect(t2.domain).nonEmpty)
|
|
466 |
error("Attempting to redefine existing value in super-table")
|
|
467 |
else t ++ t2
|
|
468 |
case _ => error("Attempting to redefine a table")
|
|
469 |
}
|
|
470 |
case Some(_) => error("Attempting to redefine a value")
|
|
471 |
case None => value
|
|
472 |
}
|
|
473 |
}
|
|
474 |
else {
|
|
475 |
map.any.get(ks.head) match {
|
|
476 |
case Some(t: Table) => update(t, ks.tail, value)
|
|
477 |
case Some(a: Array) =>
|
|
478 |
Array(a.table.values.dropRight(1) :+ update(a.table.values.last, ks.tail, value))
|
|
479 |
case Some(_) => error("Attempting to redefine a value")
|
|
480 |
case None => update(Table(), ks.tail, value)
|
|
481 |
}
|
|
482 |
}
|
|
483 |
(map - ks.head) + (ks.head -> updated)
|
|
484 |
}
|
|
485 |
|
|
486 |
def fold(elems: List[(Keys, T)]): Table =
|
|
487 |
elems.foldLeft(Table()) { case (t0, (ks1, t1)) => update(t0, ks1, t1) }
|
|
488 |
|
|
489 |
val t = fold(file.elems.map((ks, v) => (ks, convert(Nil, ks, v))))
|
|
490 |
file.defs.foldLeft(t) {
|
|
491 |
case (t0, defn@Parsers.Table(ks0, elems)) =>
|
|
492 |
context.check_add_def(ks0, defn)
|
|
493 |
update(t0, ks0, fold(elems.map((ks, v) => (ks, convert(ks0, ks, v)))))
|
|
494 |
case (t0, defn@Parsers.Array_Of_Tables(ks0, elems)) =>
|
|
495 |
context.check_add_def(ks0, defn)
|
|
496 |
update(t0, ks0, Array(fold(elems.map((ks, v) => (ks, convert(ks0, ks, v))))))
|
|
497 |
}
|
|
498 |
}
|
|
499 |
|
|
500 |
|
|
501 |
/* Format TOML */
|
|
502 |
|
|
503 |
object Format {
|
|
504 |
def apply(toml: Table): Str = {
|
|
505 |
val result = new StringBuilder
|
|
506 |
|
|
507 |
/* keys */
|
|
508 |
|
|
509 |
def key(k: Key): Unit = {
|
|
510 |
val Bare_Key = """[A-Za-z0-9_-]+""".r
|
|
511 |
k match {
|
|
512 |
case Bare_Key() => result ++= k
|
|
513 |
case _ => result ++= basic_string(k)
|
|
514 |
}
|
|
515 |
}
|
|
516 |
|
|
517 |
def keys(ks: List[Key]): Unit =
|
|
518 |
Library.separate(None, ks.reverse.map(Some(_))).foreach {
|
|
519 |
case None => result += '.'
|
|
520 |
case Some(k) => key(k)
|
|
521 |
}
|
|
522 |
|
|
523 |
/* string */
|
|
524 |
|
|
525 |
def basic_string(s: Str): Str =
|
|
526 |
"\"" + s.iterator.map {
|
|
527 |
case '\b' => "\\b" case '\t' => "\\t" case '\n' => "\\n" case '\f' => "\\f"
|
|
528 |
case '\r' => "\\r" case '"' => "\\\"" case '\\' => "\\\\" case c =>
|
|
529 |
if (c <= '\u001f' || c == '\u007f') "\\u%04x".format(c.toInt) else c
|
|
530 |
}.mkString + "\""
|
|
531 |
|
|
532 |
def multiline_basic_string(s: Str): Str =
|
|
533 |
"\"\"\"\n" + s.iterator.map {
|
|
534 |
case '\b' => "\\b" case '\t' => "\t" case '\n' => "\n" case '\f' => "\\f"
|
|
535 |
case '\r' => "\r" case '"' => "\\\"" case '\\' => "\\\\" case c =>
|
|
536 |
if (c <= '\u001f' || c == '\u007f') "\\u%04x".format(c.toInt) else c
|
|
537 |
}.mkString.replace("\"\"\"", "\"\"\\\"") + "\"\"\""
|
|
538 |
|
|
539 |
|
|
540 |
def `inline`(t: T, indent: Int = 0): Unit = {
|
|
541 |
def indentation(i: Int): Unit = for (_ <- Range(0, i)) result ++= " "
|
|
542 |
|
|
543 |
indentation(indent)
|
|
544 |
t match {
|
|
545 |
case s: String =>
|
|
546 |
if (s.rep.contains("\n") && s.rep.length > 20) result ++= multiline_basic_string(s.rep)
|
|
547 |
else result ++= basic_string(s.rep)
|
|
548 |
case i: Integer => result ++= i.rep.toString
|
|
549 |
case f: Float => result ++= f.rep.toString
|
|
550 |
case b: Boolean => result ++= b.rep.toString
|
|
551 |
case o: Offset_Date_Time => result ++= o.rep.toString
|
|
552 |
case l: Local_Date_Time => result ++= l.rep.toString
|
|
553 |
case l: Local_Date => result ++= l.rep.toString
|
|
554 |
case l: Local_Time => result ++= l.rep.toString
|
|
555 |
case a: Array =>
|
|
556 |
if (a.is_empty) result ++= "[]"
|
|
557 |
else {
|
|
558 |
result ++= "[\n"
|
|
559 |
a.any.values.foreach { elem =>
|
|
560 |
`inline`(elem, indent + 1)
|
|
561 |
result ++= ",\n"
|
|
562 |
}
|
|
563 |
indentation(indent)
|
|
564 |
result += ']'
|
|
565 |
}
|
|
566 |
case table: Table =>
|
|
567 |
if (table.is_empty) result ++= "{}"
|
|
568 |
else {
|
|
569 |
result += '{'
|
|
570 |
table.any.values.foreach { case (k, v) =>
|
|
571 |
key(k)
|
|
572 |
result ++= " = "
|
|
573 |
`inline`(v)
|
|
574 |
}
|
|
575 |
result += '}'
|
|
576 |
}
|
|
577 |
}
|
|
578 |
}
|
|
579 |
|
|
580 |
/* array */
|
|
581 |
|
|
582 |
def inline_values(path: List[Key], t: T): Unit =
|
|
583 |
t match {
|
|
584 |
case t: Table => t.any.values.foreach { case (k, v1) => inline_values(k :: path, v1) }
|
|
585 |
case _ =>
|
|
586 |
keys(path)
|
|
587 |
result ++= " = "
|
|
588 |
`inline`(t)
|
|
589 |
result += '\n'
|
|
590 |
}
|
|
591 |
|
|
592 |
def is_inline(elem: T): Bool =
|
|
593 |
elem match {
|
|
594 |
case _: String | _: Integer | _: Float | _: Boolean | _: Offset_Date_Time |
|
|
595 |
_: Local_Date_Time | _: Local_Date | _: Local_Time => true
|
|
596 |
case a: Array => a.any.values.forall(is_inline)
|
|
597 |
case _ => false
|
|
598 |
}
|
|
599 |
def is_table(elem: T): Bool = elem match { case _: Table => true case _ => false }
|
|
600 |
|
|
601 |
def array(path: List[Key], a: Array): Unit =
|
|
602 |
if (a.any.values.forall(is_inline) || !a.any.values.forall(is_table))
|
|
603 |
inline_values(path.take(1), a)
|
|
604 |
else a.table.values.foreach { t =>
|
|
605 |
result ++= "\n[["
|
|
606 |
keys(path)
|
|
607 |
result ++= "]]\n"
|
|
608 |
table(path, t, is_table_entry = true)
|
|
609 |
}
|
|
610 |
|
|
611 |
/* table */
|
|
612 |
|
|
613 |
def table(path: List[Key], t: Table, is_table_entry: Bool = false): Unit = {
|
|
614 |
val (values, nodes) = t.any.values.partition(kv => is_inline(kv._2))
|
|
615 |
|
|
616 |
if (!is_table_entry && path.nonEmpty) {
|
|
617 |
result ++= "\n["
|
|
618 |
keys(path)
|
|
619 |
result ++= "]\n"
|
|
620 |
}
|
|
621 |
|
|
622 |
values.foreach { case (k, v) => inline_values(List(k), v) }
|
|
623 |
nodes.foreach {
|
|
624 |
case (k, t: Table) => table(k :: path, t)
|
|
625 |
case (k, arr: Array) => array(k :: path, arr)
|
|
626 |
case _ =>
|
|
627 |
}
|
|
628 |
}
|
|
629 |
|
|
630 |
table(Nil, toml)
|
|
631 |
result.toString
|
|
632 |
}
|
|
633 |
}
|
|
634 |
}
|