1 # The YAML 1.1 types (yaml.org/type), for YAML written for 1.1 parsers. 2 # 3 # An untagged plain scalar is null (~, null, Null, NULL or nothing), a 4 # boolean (yes, no, true, false, on, off, each lowercase, capitalized or 5 # uppercase), an integer (decimal, 0b binary, 0 octal, 0x hex, or base 60 6 # as in 1:30:00, with "_" anywhere among the digits), a float (decimal, 7 # base 60 with a fraction, .inf, -.inf, .nan), a timestamp, or a string. 8 # A timestamp is Tagged{"tag:yaml.org,2002:timestamp", Str{text}}, the text 9 # as written. "<<" is the merge key, which the composer handles. 10 # 11 # y, Y, n and N are strings, as in most 1.1 parsers, though the 1.1 type 12 # list has them as booleans. 13 14 import Base 15 16 import ./value.bend as V 17 18 import ./re.bend as R 19 20 import ./num.bend as N 21 22 import ./core.bend as C 23 24 def when(-A: Type, hit: Bool, yes: Unit -> A, no: Unit -> A) -> A: 25 match hit: 26 case True{}: 27 yes(Unit{}) 28 case False{}: 29 no(Unit{}) 30 31 def timestamp() -> String: 32 C.yaml.tag("timestamp") 33 34 def is_true(+s: String) -> Bool: 35 List.contains(~String, ~String.eq, ["yes", "Yes", "YES", "true", "True", "TRUE", "on", "On", "ON"], s) 36 37 def is_false(+s: String) -> Bool: 38 List.contains(~String, ~String.eq, ["no", "No", "NO", "false", "False", "FALSE", "off", "Off", "OFF"], s) 39 40 # Patterns 41 # -------- 42 43 def d() -> R.Pat: 44 R.digit() 45 46 def digits_() -> R.Pat: 47 R.Star{R.of("0123456789_")} 48 49 def bin.pat() -> List<&2, R.Pat>: 50 [R.sign(), R.c('0'), R.c('b'), R.Plus{R.of("01_")}] 51 52 def oct.pat() -> List<&2, R.Pat>: 53 [R.sign(), R.c('0'), R.Plus{R.of("01234567_")}] 54 55 def dec.pat() -> List<&2, R.Pat>: 56 [R.sign(), R.Alt{[[R.c('0')], [R.One{R.Range{'1', '9'}}, digits_()]]}] 57 58 def hex.pat() -> List<&2, R.Pat>: 59 [R.sign(), R.c('0'), R.c('x'), R.Plus{R.Alt{[[R.One{R.Hex{}}], [R.c('_')]]}}] 60 61 def sexa.part() -> R.Pat: 62 R.seq([R.c(':'), R.Opt{R.One{R.Range{'0', '5'}}}, d()]) 63 64 def sexa.int.pat() -> List<&2, R.Pat>: 65 [R.sign(), R.One{R.Range{'1', '9'}}, digits_(), R.Plus{sexa.part()}] 66 67 def float.pat() -> List<&2, R.Pat>: 68 [R.sign(), R.Opt{R.seq([d(), digits_()])}, R.c('.'), digits_(), R.Opt{R.seq([R.of("eE"), R.of("-+"), R.digits()])}] 69 70 def sexa.float.pat() -> List<&2, R.Pat>: 71 [R.sign(), d(), digits_(), R.Plus{sexa.part()}, R.c('.'), digits_()] 72 73 def ymd.pat() -> List<&2, R.Pat>: 74 [d(), d(), d(), d(), R.c('-'), d(), d(), R.c('-'), d(), d()] 75 76 def blank() -> R.Pat: 77 R.of(" \t") 78 79 def time.pat() -> List<&2, R.Pat>: 80 [d(), d(), d(), d(), R.c('-'), d(), R.Opt{d()}, R.c('-'), d(), R.Opt{d()}, 81 R.Alt{[[R.of("Tt")], [R.Plus{blank()}]]}, d(), R.Opt{d()}, R.c(':'), d(), d(), R.c(':'), d(), d(), 82 R.Opt{R.seq([R.c('.'), R.Star{d()}])}, 83 R.Opt{R.seq([R.Star{blank()}, R.Alt{[[R.c('Z')], [R.of("-+"), d(), R.Opt{d()}, R.Opt{R.seq([R.c(':'), d(), d()])}]]}])}] 84 85 def is_timestamp(+s: String) -> Bool: 86 Bool.or(R.matches(ymd.pat(), s), R.matches(time.pat(), s)) 87 88 # Values 89 # ------ 90 91 def no.seps(s: String) -> String: 92 match s: 93 case SNil{}: 94 "" 95 case SCon{c, t}: 96 match c: 97 case '_': 98 no.seps(t) 99 case _: 100 SCon{c, no.seps(t)} 101 102 def has_digit(s: String) -> Bool: 103 match s: 104 case SNil{}: 105 False{} 106 case SCon{+c, t}: 107 Bool.or(Char.is_digit(c), has_digit(t)) 108 109 # Base 60: each part is the next digit, "1:30" is 90. 110 def sexa.go(parts: List<&2, String>, acc: List<&2, Nat>) -> List<&2, Nat>: 111 match parts: 112 case Nil{}: 113 acc 114 case Con{p, rest}: 115 sexa.go(rest, N.big.mul.add(acc, 60n, N.big.small(p, 0n))) 116 117 def sexa.big(parts: List<&2, String>) -> List<&2, Nat>: 118 match parts: 119 case Nil{}: 120 [] 121 case Con{p, rest}: 122 sexa.go(rest, N.big.read(p, 10n, [])) 123 124 def int.of.digits(+ds: String) -> String: 125 when(String, String.starts_with(ds, "0b"), u => N.big.show(N.big.read(String.drop(ds, 2n), 2n, [])), 126 u => when(String, String.starts_with(ds, "0x"), u => N.big.show(N.big.read(String.drop(ds, 2n), 16n, [])), 127 u => when(String, String.contains(ds, ":"), u => N.big.show(sexa.big(String.split(ds, ':'))), 128 u => when(String, String.starts_with(ds, "0"), u => N.big.show(N.big.read(ds, 8n, [])), u => N.big.show(N.big.read(ds, 10n, [])))))) 129 130 # An integer's decimal text; +s+ already matched one of the integer forms. 131 def int.text(+s: String) -> String: 132 +t = no.seps(s) 133 N.signed(C.is_neg(t), int.of.digits(C.unsigned(t))) 134 135 def is_int(+s: String) -> Bool: 136 Bool.or(Bool.or(R.matches(dec.pat(), s), R.matches(oct.pat(), s)), 137 Bool.or(Bool.or(R.matches(hex.pat(), s), R.matches(bin.pat(), s)), R.matches(sexa.int.pat(), s))) 138 139 def before.dot(s: String) -> String: 140 match s: 141 case SNil{}: 142 "" 143 case SCon{c, t}: 144 match c: 145 case '.': 146 "" 147 case _: 148 SCon{c, before.dot(t)} 149 150 def after.dot(s: String) -> String: 151 match s: 152 case SNil{}: 153 "" 154 case SCon{c, t}: 155 match c: 156 case '.': 157 t 158 case _: 159 after.dot(t) 160 161 # A base-60 float: the whole part in base 60, then the fraction. 162 def sexa.float(+s: String) -> String: 163 +t = no.seps(s) 164 +body = C.unsigned(t) 165 N.dec.exact(Bool.pick(String, C.is_neg(t), "-", "") ++ N.big.show(sexa.big(String.split(before.dot(body), ':'))) ++ "." ++ after.dot(body)) 166 167 def float.of(+s: String) -> Maybe<&2, String>: 168 +body = C.unsigned(s) 169 when(Maybe<&2, String>, C.is_inf(body), u => Some{Bool.pick(String, C.is_neg(s), "-.inf", ".inf")}, 170 u => when(Maybe<&2, String>, C.is_nan(s), u => Some{".nan"}, 171 u => when(Maybe<&2, String>, Bool.and(R.matches(float.pat(), s), has_digit(s)), u => Some{N.dec.exact(no.seps(s))}, 172 u => when(Maybe<&2, String>, R.matches(sexa.float.pat(), s), u => Some{sexa.float(s)}, u => None{})))) 173 174 def int.of(+s: String) -> Maybe<&2, String>: 175 when(Maybe<&2, String>, is_int(s), u => Some{int.text(s)}, u => None{}) 176 177 def or.int(m: Maybe<&2, String>, rest: Unit -> V.Yaml) -> V.Yaml: 178 match m: 179 case Some{t}: 180 V.Int{t} 181 case None{}: 182 rest(Unit{}) 183 184 def or.float(m: Maybe<&2, String>, rest: Unit -> V.Yaml) -> V.Yaml: 185 match m: 186 case Some{t}: 187 V.Float{t} 188 case None{}: 189 rest(Unit{}) 190 191 # An untagged plain scalar. 192 def plain(+s: String) -> V.Yaml: 193 when(V.Yaml, C.is_null(s), u => V.Null{}, 194 u => when(V.Yaml, is_true(s), u => V.Boolean{True{}}, 195 u => when(V.Yaml, is_false(s), u => V.Boolean{False{}}, 196 u => or.int(int.of(s), u => or.float(float.of(s), 197 u => when(V.Yaml, is_timestamp(s), u => V.Tagged{timestamp(), V.Str{s}}, u => V.Str{s})))))) 198 199 def int.yaml(m: Maybe<&2, String>) -> Maybe<&2, V.Yaml>: 200 match m: 201 case Some{t}: 202 Some{V.Int{t}} 203 case None{}: 204 None{} 205 206 # !!float: a float, or an integer written as one ("3" is 3.0). 207 def float.yaml(m: Maybe<&2, String>, i: Maybe<&2, String>) -> Maybe<&2, V.Yaml>: 208 match m: 209 case Some{t}: 210 Some{V.Float{t}} 211 case None{}: 212 match i: 213 case Some{t}: 214 Some{V.Float{N.dec.exact(t)}} 215 case None{}: 216 None{} 217 218 def bool.yaml(+s: String) -> Maybe<&2, V.Yaml>: 219 Bool.pick(Maybe<&2, V.Yaml>, is_true(s), Some{V.Boolean{True{}}}, Bool.pick(Maybe<&2, V.Yaml>, is_false(s), Some{V.Boolean{False{}}}, None{})) 220 221 # A scalar with a tag: the standard types read the text in their 1.1 forms, 222 # !!merge is the merge key, "!" is a string; another tag is kept. 223 def tagged(+tag: String, +s: String) -> Result<&2, &2, String, V.Yaml>: 224 when(Result<&2, &2, String, V.Yaml>, Bool.or(Bool.or(String.eq(tag, "!"), String.eq(tag, C.yaml.tag("str"))), String.eq(tag, C.yaml.tag("merge"))), u => Done{V.Str{s}}, 225 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("null")), u => C.must(Bool.pick(Maybe<&2, V.Yaml>, C.is_null(s), Some{V.Null{}}, None{}), "!!null", s), 226 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("bool")), u => C.must(bool.yaml(s), "!!bool", s), 227 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("int")), u => C.must(int.yaml(int.of(s)), "!!int", s), 228 u => when(Result<&2, &2, String, V.Yaml>, String.eq(tag, C.yaml.tag("float")), u => C.must(float.yaml(float.of(s), int.of(s)), "!!float", s), 229 u => Done{V.Tagged{tag, V.Str{s}}})))))