1 # YAML for Bend: read YAML text into values and write values back as YAML. 2 # 3 # import ./yaml.bend as Y 4 # import ./value.bend as V 5 # 6 # Y.load("a: [1, 2]") # Done{V.Mapping{[V.Entry{V.Str{"a"}, V.Seq{...}}]}} 7 # 8 # load reads under the YAML 1.2 Core schema; load.v11 reads the YAML 1.1 9 # types (yes/no booleans, 0 octal, base 60, timestamps, "<<" merges). 10 # Errors say what went wrong and where: T.error.show(e) is "mapping values 11 # are not allowed in this context at line 1 column 5". 12 13 import Base 14 15 import ./token.bend as T 16 17 import ./parse.bend as P 18 19 import ./value.bend as V 20 21 import ./compose.bend as C 22 23 import ./emit.bend as E 24 25 def doc.vals(ds: List<&2, C.Doc>) -> List<&2, V.Yaml>: 26 match ds: 27 case Nil{}: 28 [] 29 case Con{d, rest}: 30 match d: 31 case C.Doc{v, at}: 32 v <> doc.vals(rest) 33 34 def all.of(r: Result<&2, &2, T.Error, List<&2, C.Doc>>) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>: 35 match r: 36 case Fail{e}: 37 Fail{e} 38 case Done{ds}: 39 Done{doc.vals(ds)} 40 41 def one.of(r: Result<&2, &2, T.Error, List<&2, C.Doc>>) -> Result<&2, &2, T.Error, V.Yaml>: 42 match r: 43 case Fail{e}: 44 Fail{e} 45 case Done{ds}: 46 match ds: 47 case Nil{}: 48 Done{V.Null{}} 49 case Con{d, rest}: 50 match d: 51 case C.Doc{v, at}: 52 match rest: 53 case Nil{}: 54 Done{v} 55 case Con{other, more}: 56 match other: 57 case C.Doc{w, second}: 58 Fail{T.Error{"found a second document where load expects one; use load.all", second, T.Content{}}} 59 60 # Every document in +text+ under schema +s+, with aliases making at most 61 # +limit+ nodes in a document. 62 def load.all.with(s: C.Schema, limit: Nat, text: String) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>: 63 all.of(C.compose.limited(P.events(text), s, limit)) 64 65 # The one document in +text+ under schema +s+: null when there is none, an 66 # error when there are more. 67 def load.with(s: C.Schema, limit: Nat, text: String) -> Result<&2, &2, T.Error, V.Yaml>: 68 one.of(C.compose.limited(P.events(text), s, limit)) 69 70 # The one document in +text+ under the YAML 1.2 Core schema: null when 71 # there is none, an error when there are more. A repeated key is an error, 72 # and so is a document whose aliases make more than 100000 nodes. 73 def load(text: String) -> Result<&2, &2, T.Error, V.Yaml>: 74 load.with(C.Core{}, C.alias.limit(), text) 75 76 # Every document in +text+, under the Core schema. 77 def load.all(text: String) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>: 78 load.all.with(C.Core{}, C.alias.limit(), text) 79 80 # load, reading the YAML 1.1 types. 81 def load.v11(text: String) -> Result<&2, &2, T.Error, V.Yaml>: 82 load.with(C.Yaml11{}, C.alias.limit(), text) 83 84 # load.all, reading the YAML 1.1 types. 85 def load.all.v11(text: String) -> Result<&2, &2, T.Error, List<&2, V.Yaml>>: 86 load.all.with(C.Yaml11{}, C.alias.limit(), text) 87 88 # The events of +text+, as the parser emits them, and the error that ended 89 # them early, if one did. 90 def events(text: String) -> P.Parsed: 91 P.events(text) 92 93 # +v+ as YAML text in block style, which load reads back as +v+. 94 def dump(v: V.Yaml) -> String: 95 E.dump(v) 96 97 # Values as a stream of documents, each after "---". 98 def dump.all(vs: List<&2, V.Yaml>) -> String: 99 E.dump.all(vs)