| 1 | // Copyright (c) 2022, 2024 blackshirt. All rights reserved. |
| 2 | // Use of this source code is governed by a MIT License |
| 3 | // that can be found in the LICENSE file. |
| 4 | module asn1 |
| 5 | |
| 6 | // vfmt off |
| 7 | // bit masking values for ASN.1 tag header |
| 8 | const tag_class_mask = 0xc0 // 192, bits 8-7 |
| 9 | const constructed_mask = 0x20 // 32, bits 6 |
| 10 | const tag_number_mask = 0x1f // 31, bits 1-5 |
| 11 | // vfmt on |
| 12 | |
| 13 | // Maximum number of bytes to represent tag number, includes the tag byte. |
| 14 | const max_tag_length = 3 |
| 15 | const max_tag_number = 16383 |
| 16 | |
| 17 | // Maximum value for UNIVERSAL class tag number, see `TagType`, |
| 18 | // Tag number above this number should be considered to other class, |
| 19 | // PRIVATE, CONTEXT_SPECIFIC or APPLICATION class. |
| 20 | const max_universal_tagnumber = 255 |
| 21 | |
| 22 | // ASN.1 Tag identifier handling. |
| 23 | // |
| 24 | // Tag represents identifier of the ASN.1 element (object). |
| 25 | // ASN.1 Tag number can be represented in two form, the short form and the long form. |
| 26 | // The short form for tag number below <= 30 and stored enough in single byte. |
| 27 | // The long form for tag number > 30, and stored in two or more bytes. |
| 28 | // |
| 29 | // ASN.1 imposes no limit on the tag number, but the NIST Stable Implementation Agreements (1991) |
| 30 | // and its European and Asian counterparts limit the size of tags to 16383. |
| 31 | // see https://www.oss.com/asn1/resources/asn1-faq.html#tag-limitation |
| 32 | // We impose limit on the tag number to be in range 0..16383. |
| 33 | // Its big enough to accomodate and represent different of yours own tag number. |
| 34 | // Its represents 2 bytes length where maximum bytes arrays to represent tag number |
| 35 | // in multibyte (long) form is `[u8(0x1f), 0xff, 0x7f]` or 16383 in base 128. |
| 36 | pub struct Tag { |
| 37 | mut: |
| 38 | class TagClass = .universal |
| 39 | constructed bool |
| 40 | number int |
| 41 | } |
| 42 | |
| 43 | // `Tag.new` creates new ASN.1 tag identifier. Its accepts params of TagClass `cls`, |
| 44 | // the tag form in the constructed or primitive form in `constructed` flag, and the integer tag `number`. |
| 45 | pub fn Tag.new(cls TagClass, constructed bool, number int) !Tag { |
| 46 | if number < 0 || number > max_tag_number { |
| 47 | return error('Unallowed tag number, ${number} exceed limit') |
| 48 | } |
| 49 | // validates required criteria |
| 50 | if cls == .universal { |
| 51 | // UNIVERSAL tag number should below max_universal_tagnumber |
| 52 | if number > max_universal_tagnumber { |
| 53 | return error('Not a valid tag number for universal class=${number}') |
| 54 | } |
| 55 | // SEQUENCE (OF) or SET (OF) should be in constructed form |
| 56 | if number == int(TagType.sequence) || number == int(TagType.set) { |
| 57 | if !constructed { |
| 58 | return error('For SEQUENCE(OF) or SET(OF) type, should be in constructed form') |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | // otherwise, ok |
| 64 | tag := Tag{ |
| 65 | class: cls |
| 66 | constructed: constructed |
| 67 | number: number |
| 68 | } |
| 69 | return tag |
| 70 | } |
| 71 | |
| 72 | // `tag_class` return the ASN.1 class of this tag |
| 73 | pub fn (t Tag) tag_class() TagClass { |
| 74 | return t.class |
| 75 | } |
| 76 | |
| 77 | // `is_constructed` tells us whether this tag is in constructed form |
| 78 | // or the primitive one, ie, not constructed. |
| 79 | pub fn (t Tag) is_constructed() bool { |
| 80 | return t.constructed |
| 81 | } |
| 82 | |
| 83 | // `tag_number` return the tag nunber of this tag |
| 84 | pub fn (t Tag) tag_number() int { |
| 85 | return t.number |
| 86 | } |
| 87 | |
| 88 | // `encode` serializes the tag into bytes array with default rule, ie, .der. |
| 89 | // Its serializes into dst bytes buffer or return error on fails. |
| 90 | pub fn (t Tag) encode(mut dst []u8) ! { |
| 91 | t.encode_with_rule(mut dst, .der)! |
| 92 | } |
| 93 | |
| 94 | // `Tag.from_bytes` creates a new Tag from bytes. Its return newly created tag and |
| 95 | // the rest of remaining bytes on success, or error on failures. |
| 96 | pub fn Tag.from_bytes(bytes []u8) !(Tag, []u8) { |
| 97 | tag, next_pos := Tag.decode(bytes)! |
| 98 | if next_pos < bytes.len { |
| 99 | rest := unsafe { bytes[next_pos..] } |
| 100 | return tag, rest |
| 101 | } |
| 102 | if next_pos == bytes.len { |
| 103 | return tag, []u8{} |
| 104 | } |
| 105 | return error('Tag: too short data') |
| 106 | } |
| 107 | |
| 108 | // `equal` checks whether this tag is equal with the other tag |
| 109 | pub fn (t Tag) equal(o Tag) bool { |
| 110 | return t.class == o.class && t.constructed == o.constructed && t.number == o.number |
| 111 | } |
| 112 | |
| 113 | // encode_with_rule serializes tag into bytes array |
| 114 | fn (t Tag) encode_with_rule(mut dst []u8, rule EncodingRule) ! { |
| 115 | // we currently only support .der or (stricter) .ber |
| 116 | if rule != .der && rule != .ber { |
| 117 | return error('Tag: unsupported rule') |
| 118 | } |
| 119 | // makes sure tag number is valid |
| 120 | if t.number > max_tag_number { |
| 121 | return error('Tag: tag number exceed limit') |
| 122 | } |
| 123 | |
| 124 | // get the class type and constructed bit and build the bytes tag. |
| 125 | // if the tag number > 0x1f, represented in long form required two or more bytes, |
| 126 | // otherwise, represented in short form, fit in single byte. |
| 127 | mut b := (u8(t.class) << 6) & tag_class_mask |
| 128 | if t.constructed { |
| 129 | b |= constructed_mask |
| 130 | } |
| 131 | // The tag is in long form |
| 132 | if t.number >= 0x1f { |
| 133 | b |= tag_number_mask // 0x1f |
| 134 | dst << b |
| 135 | t.to_bytes_in_base128(mut dst)! |
| 136 | } else { |
| 137 | // short form |
| 138 | b |= u8(t.number) |
| 139 | dst << b |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // Tag.decode tries to deserializes bytes into Tag. |
| 144 | // Its return error on fails. |
| 145 | fn Tag.decode(bytes []u8) !(Tag, int) { |
| 146 | tag, next := Tag.decode_from_offset(bytes, 0)! |
| 147 | return tag, next |
| 148 | } |
| 149 | |
| 150 | // Tag.decode tries to deserializes bytes into Tag. its return error on fails. |
| 151 | fn Tag.decode_from_offset(bytes []u8, pos int) !(Tag, int) { |
| 152 | // default rule |
| 153 | tag, next := Tag.decode_with_rule(bytes, pos, .der)! |
| 154 | return tag, next |
| 155 | } |
| 156 | |
| 157 | // Tag.decode_with_rule deserializes bytes back into Tag structure start from `loc` offset. |
| 158 | // By default, its decodes in .der encoding rule, if you want more control, pass your `Params`. |
| 159 | // Its return Tag and next offset to operate on, and return error if it fails to decode. |
| 160 | fn Tag.decode_with_rule(bytes []u8, loc int, rule EncodingRule) !(Tag, int) { |
| 161 | // preliminary check |
| 162 | if bytes.len < 1 { |
| 163 | return error('Tag: bytes underflow') |
| 164 | } |
| 165 | if rule != .der && rule != .ber { |
| 166 | return error('Tag: unsupported rule') |
| 167 | } |
| 168 | // when accessing byte at ofset `loc` within bytes, ie, `b := bytes[loc]`, |
| 169 | // its maybe can lead to panic when the loc is not be checked. |
| 170 | if loc >= bytes.len { |
| 171 | return error('Tag: invalid pos') |
| 172 | } |
| 173 | mut pos := loc |
| 174 | // first byte of tag bytes |
| 175 | b := bytes[pos] |
| 176 | pos += 1 |
| 177 | |
| 178 | // First we get the first byte from the bytes, check and gets the class and constructed bits |
| 179 | // and the tag number marker. If this marker == 0x1f, it tells whether the tag number is represented |
| 180 | // in multibyte (long form), or short form otherwise. |
| 181 | class := int((b & tag_class_mask) >> 6) |
| 182 | constructed := b & constructed_mask == constructed_mask |
| 183 | mut number := int(b & tag_number_mask) |
| 184 | |
| 185 | // check if this `number` is in long (multibyte) form, and interpretes more bytes as a tag number. |
| 186 | if number == 0x1f { |
| 187 | // we only allowed `max_tag_length` bytes following to represent tag number. |
| 188 | number, pos = Tag.read_tagnum(bytes, pos)! |
| 189 | |
| 190 | // pos is the next position to read next bytes, so check tag bytes length |
| 191 | if pos >= max_tag_length + loc + 1 { |
| 192 | return error('Tag: tag bytes is too long') |
| 193 | } |
| 194 | if number < 0x1f && rule == .der { |
| 195 | // requirement for DER encoding. |
| 196 | // TODO: the other encoding may remove this restriction |
| 197 | return error('Tag: non-minimal tag') |
| 198 | } |
| 199 | } |
| 200 | // build the tag |
| 201 | tag := Tag.new(TagClass.from_int(class)!, constructed, number)! |
| 202 | |
| 203 | return tag, pos |
| 204 | } |
| 205 | |
| 206 | fn (t Tag) str() string { |
| 207 | cls := t.class.str() |
| 208 | form := if t.constructed { 'true' } else { 'false' } |
| 209 | number := t.number.str() |
| 210 | return '${cls}-${form}-${number}' |
| 211 | } |
| 212 | |
| 213 | // bytes_len tells amount of bytes needed to store tag in base 128 |
| 214 | fn (t Tag) bytes_len() int { |
| 215 | if t.number == 0 { |
| 216 | return 1 |
| 217 | } |
| 218 | mut n := t.number |
| 219 | mut ret := 0 |
| 220 | |
| 221 | for n > 0 { |
| 222 | ret += 1 |
| 223 | n >>= 7 |
| 224 | } |
| 225 | |
| 226 | return ret |
| 227 | } |
| 228 | |
| 229 | // `tag_size` informs us how many bytes needed to store this tag includes one byte marker if in the long form. |
| 230 | pub fn (t Tag) tag_size() int { |
| 231 | // when number is greater than 31 (0x1f), its need more bytes |
| 232 | // to represent this number, includes one byte marker for long form tag |
| 233 | len := if t.number < 0x1f { 1 } else { t.bytes_len() + 1 } |
| 234 | return len |
| 235 | } |
| 236 | |
| 237 | // to_bytes_in_base128 serializes tag number into bytes in base 128 |
| 238 | fn (t Tag) to_bytes_in_base128(mut dst []u8) ! { |
| 239 | n := t.bytes_len() |
| 240 | for i := n - 1; i >= 0; i-- { |
| 241 | mut o := u8(t.number >> u32(i * 7)) |
| 242 | o &= 0x7f |
| 243 | if i != 0 { |
| 244 | o |= 0x80 |
| 245 | } |
| 246 | dst << o |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | // Tag.read_tagnum read the tag number from bytes from offset pos in base 128. |
| 251 | // Its return deserialized Tag number and next offset to process on. |
| 252 | fn Tag.read_tagnum(bytes []u8, pos int) !(int, int) { |
| 253 | tnum, next := Tag.read_tagnum_with_rule(bytes, pos, .der)! |
| 254 | return tnum, next |
| 255 | } |
| 256 | |
| 257 | // read_tagnum_with_rule is the main routine to read the tag number part in the bytes source, |
| 258 | // start from offset loc in base 128. Its return the tag number and next offset to process on, or error on fails. |
| 259 | fn Tag.read_tagnum_with_rule(bytes []u8, loc int, rule EncodingRule) !(int, int) { |
| 260 | if loc > bytes.len { |
| 261 | return error('Tag number: invalid pos') |
| 262 | } |
| 263 | mut pos := loc |
| 264 | mut ret := 0 |
| 265 | for s := 0; pos < bytes.len; s++ { |
| 266 | ret <<= 7 |
| 267 | b := bytes[pos] |
| 268 | |
| 269 | if s == 0 && b == 0x80 { |
| 270 | if rule == .der { |
| 271 | // requirement for DER encoding |
| 272 | return error('Tag number: integer is not minimally encoded') |
| 273 | } |
| 274 | } |
| 275 | ret |= b & 0x7f |
| 276 | pos += 1 |
| 277 | |
| 278 | if b & 0x80 == 0 { |
| 279 | if ret > max_tag_number { |
| 280 | return error('Tag number: base 128 integer too large') |
| 281 | } |
| 282 | if ret < 0 { |
| 283 | return error('Negative tag number') |
| 284 | } |
| 285 | |
| 286 | return ret, pos |
| 287 | } |
| 288 | } |
| 289 | return error('Tag: truncated base 128 integer') |
| 290 | } |
| 291 | |
| 292 | fn (t Tag) valid_supported_universal_tagnum() bool { |
| 293 | return t.class == .universal && t.number < max_universal_tagnumber |
| 294 | } |
| 295 | |
| 296 | // `universal_tag_type` turns this Tag into available UNIVERSAL class of TagType, |
| 297 | // or return error if it is unknown number. |
| 298 | pub fn (t Tag) universal_tag_type() !TagType { |
| 299 | // currently, only support Standard universal tag number |
| 300 | if t.number > max_universal_tagnumber { |
| 301 | return error('Tag number: unknown TagType number=${t.number}') |
| 302 | } |
| 303 | match t.class { |
| 304 | .universal { |
| 305 | match t.number { |
| 306 | // vfmt off |
| 307 | 0 { return .reserved } |
| 308 | 1 { return .boolean } |
| 309 | 2 { return .integer } |
| 310 | 3 { return .bitstring } |
| 311 | 4 { return .octetstring } |
| 312 | 5 { return .null } |
| 313 | 6 { return .oid } |
| 314 | 7 { return .objdesc } |
| 315 | 8 { return .external } |
| 316 | 9 { return .real } |
| 317 | 10 { return .enumerated } |
| 318 | 11 { return .embedded } |
| 319 | 12 { return .utf8string } |
| 320 | 13 { return .relativeoid } |
| 321 | 14 { return .time } |
| 322 | 16 { return .sequence } |
| 323 | 17 { return .set } |
| 324 | 18 { return .numericstring } |
| 325 | 19 { return .printablestring } |
| 326 | 20 { return .t61string } |
| 327 | 21 { return .videotexstring } |
| 328 | 22 { return .ia5string } |
| 329 | 23 { return .utctime } |
| 330 | 24 { return .generalizedtime } |
| 331 | 25 { return .graphicstring } |
| 332 | 26 { return .visiblestring } |
| 333 | 27 { return .generalstring } |
| 334 | 28 { return .universalstring } |
| 335 | 29 { return .characterstring } |
| 336 | 30 { return .bmpstring } |
| 337 | 31 { return .date } |
| 338 | 32 { return .time_of_day } |
| 339 | 33 { return .date_time } |
| 340 | 34 { return .duration } |
| 341 | 35 { return .i18_oid } |
| 342 | 36 { return .relative_i18_oid } |
| 343 | else { |
| 344 | return error('reserved or unknonw number') |
| 345 | } |
| 346 | // vfmt on |
| 347 | } |
| 348 | } |
| 349 | else { |
| 350 | return error('Not universal class type') |
| 351 | } |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | // TagClass is an enum of ASN.1 tag class. |
| 356 | // |
| 357 | // To make sure ASN.1 encodings are not ambiguous, every ASN.1 type is associated with a tag. |
| 358 | // A tag consists of three parts: the tag class, tag form and the tag number. |
| 359 | // The following classes are defined in the ASN.1 standard. |
| 360 | pub enum TagClass { |
| 361 | universal = 0x00 // 0b00 |
| 362 | application = 0x01 // 0b01 |
| 363 | context_specific = 0x02 // 0b10 |
| 364 | private = 0x03 // 0b11 |
| 365 | } |
| 366 | |
| 367 | // `TagClass.from_int` creates TagClass from integer v. |
| 368 | // Its return a new TagClass on success or error on fails. |
| 369 | fn TagClass.from_int(v int) !TagClass { |
| 370 | match v { |
| 371 | // vfmt off |
| 372 | 0x00 { return .universal } |
| 373 | 0x01 { return .application } |
| 374 | 0x02 { return .context_specific } |
| 375 | 0x03 { return .private } |
| 376 | else { |
| 377 | return error('Bad class number') |
| 378 | } |
| 379 | // vfmt on |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | // `TagClass.from_string` creates a TagClass from string s. |
| 384 | // Its return a new TagClass on success or error on fails. |
| 385 | fn TagClass.from_string(s string) !TagClass { |
| 386 | match s { |
| 387 | // vfmt off |
| 388 | 'universal' { return .universal } |
| 389 | 'private' { return .private } |
| 390 | 'application' { return .application } |
| 391 | 'context_specific' { return .context_specific } |
| 392 | else { |
| 393 | return error('bad class string') |
| 394 | } |
| 395 | // vfmt on |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | fn (c TagClass) str() string { |
| 400 | match c { |
| 401 | .universal { return 'universal' } |
| 402 | .application { return 'application' } |
| 403 | .context_specific { return 'context_specific' } |
| 404 | .private { return 'private' } |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | // TagType is standard UNIVERSAL tag number. Some of them was deprecated, |
| 409 | // so its not going to be supported on this module. |
| 410 | pub enum TagType { |
| 411 | // vfmt off |
| 412 | reserved = 0 // reserved for BER |
| 413 | boolean = 1 // BOOLEAN type |
| 414 | integer = 2 // INTEGER type |
| 415 | bitstring = 3 // BIT STRING |
| 416 | octetstring = 4 // OCTET STRING |
| 417 | null = 5 // NULL |
| 418 | oid = 6 // OBJECT IDENTIFIER |
| 419 | objdesc = 7 // OBJECT DESCRIPTOR |
| 420 | external = 8 // INSTANCE OF, EXTERNAL |
| 421 | real = 9 // REAL |
| 422 | enumerated = 10 // ENUMERATED |
| 423 | embedded = 11 // EMBEDDED PDV |
| 424 | utf8string = 12 // UTF8STRING |
| 425 | relativeoid = 13 // RELATIVE-OID |
| 426 | // deprecated |
| 427 | time = 14 |
| 428 | // 0x0f is reserved |
| 429 | sequence = 16 // SEQUENCE, SEQUENCE OF, Constructed |
| 430 | set = 17 // SET, SET OF, Constructed |
| 431 | numericstring = 18 // NUMERICSTRING |
| 432 | printablestring = 19 // PRINTABLESTRING |
| 433 | t61string = 20 // TELETEXSTRING, T61STRING |
| 434 | videotexstring = 21 // VIDEOTEXSTRING |
| 435 | ia5string = 22 // IA5STRING |
| 436 | utctime = 23 // UTCTIME |
| 437 | generalizedtime = 24 // GENERALIZEDTIME |
| 438 | graphicstring = 25 // GRAPHICSTRING |
| 439 | visiblestring = 26 // VISIBLESTRING, ISO646STRING |
| 440 | generalstring = 27 // GENERALSTRING |
| 441 | universalstring = 28 // UNIVERSALSTRING |
| 442 | characterstring = 29 // CHARACTER STRING |
| 443 | bmpstring = 30 // BMPSTRING |
| 444 | // unsupported |
| 445 | date = 0x1f |
| 446 | time_of_day = 0x20 |
| 447 | date_time = 0x21 |
| 448 | duration = 0x22 |
| 449 | // Internationalized OID |
| 450 | i18_oid = 0x23 |
| 451 | // Internationalized Relative OID |
| 452 | // Reserved 0x25 and above |
| 453 | relative_i18_oid = 0x24 |
| 454 | // vfmt on |
| 455 | } |
| 456 | |
| 457 | fn (t TagType) str() string { |
| 458 | match t { |
| 459 | .boolean { return 'BOOLEAN' } |
| 460 | .integer { return 'INTEGER' } |
| 461 | .bitstring { return 'BITSTRING' } |
| 462 | .octetstring { return 'OCTETSTRING' } |
| 463 | .null { return 'NULL' } |
| 464 | .oid { return 'OID' } |
| 465 | .objdesc { return 'OBJECT_DESCRIPTOR' } |
| 466 | .external { return 'EXTERNAL' } |
| 467 | .real { return 'REAL' } |
| 468 | .enumerated { return 'ENUMERATED' } |
| 469 | .embedded { return 'EMBEDDED' } |
| 470 | .utf8string { return 'UTF8STRING' } |
| 471 | .relativeoid { return 'RELATIVEOID' } |
| 472 | .time { return 'TIME' } |
| 473 | .sequence { return 'SEQUENCE_OR_SEQUENCEOF' } |
| 474 | .set { return 'SET_OR_SET_OF' } |
| 475 | .numericstring { return 'NUMERICSTRING' } |
| 476 | .printablestring { return 'PRINTABLESTRING' } |
| 477 | .t61string { return 'T61STRING' } |
| 478 | .videotexstring { return 'VIDEOTEXSTRING' } |
| 479 | .ia5string { return 'IA5STRING' } |
| 480 | .utctime { return 'UTCTIME' } |
| 481 | .generalizedtime { return 'GENERALIZEDTIME' } |
| 482 | .graphicstring { return 'GRAPHICSTRING' } |
| 483 | .visiblestring { return 'VISIBLESTRING' } |
| 484 | .generalstring { return 'GENERALSTRING' } |
| 485 | .universalstring { return 'UNIVERSALSTRING' } |
| 486 | .characterstring { return 'CHARACTERSTRING' } |
| 487 | .bmpstring { return 'BMPSTRING' } |
| 488 | else { return 'UNSUPPORTED_TAG_TYPE' } |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | // universal_tag_from_int gets default Tag for universal class type from integer value v. |
| 493 | fn universal_tag_from_int(v int) !Tag { |
| 494 | if v < 0 || v > max_universal_tagnumber { |
| 495 | return error('Get unexpected tag number for universal type') |
| 496 | } |
| 497 | match v { |
| 498 | // vfmt off |
| 499 | 1 { return default_boolean_tag } |
| 500 | 2 { return default_integer_tag } |
| 501 | 3 { return default_bitstring_tag } |
| 502 | 4 { return default_octetstring_tag } |
| 503 | 5 { return default_null_tag } |
| 504 | 6 { return default_oid_tag } |
| 505 | 10 { return default_enumerated_tag } |
| 506 | 12 { return default_utf8string_tag } |
| 507 | 16 { return default_sequence_tag } |
| 508 | 17 { return default_set_tag } |
| 509 | 18 { return default_numericstring_tag } |
| 510 | 19 { return default_printablestring_tag } |
| 511 | 22 { return default_ia5string_tag } |
| 512 | 23 { return default_utctime_tag } |
| 513 | 24 { return default_generalizedtime_tag } |
| 514 | 26 { return default_visiblestring_tag } |
| 515 | else { |
| 516 | // should in primitive form |
| 517 | return Tag{.universal, false, v} |
| 518 | } |
| 519 | // vfmt on |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | // EncodingRule is standard of rule thats drives how some ASN.1 |
| 524 | // element was encoded or deserialized. |
| 525 | pub enum EncodingRule { |
| 526 | // Distinguished Encoding Rules (DER) |
| 527 | der = 0 |
| 528 | // Basic Encoding Rules (BER) |
| 529 | ber = 1 |
| 530 | // Octet Encoding Rules (OER) |
| 531 | oer = 2 |
| 532 | // Packed Encoding Rules (PER) |
| 533 | per = 3 |
| 534 | // XML Encoding Rules (XER) |
| 535 | xer = 4 |
| 536 | } |
| 537 | |
| 538 | // EXPLICIT and IMPLICIT MODE |
| 539 | // |
| 540 | // TaggedMode is way of rule how some element is being wrapped (unwrapped). |
| 541 | // Explicit rule add new (outer) tag to the existing element, |
| 542 | // where implicit rule replaces the tag of existing element. |
| 543 | pub enum TaggedMode { |
| 544 | explicit |
| 545 | implicit |
| 546 | } |
| 547 | |
| 548 | // `TaggedMode.from_string` creates TaggedMode from string s. |
| 549 | fn TaggedMode.from_string(s string) !TaggedMode { |
| 550 | match s { |
| 551 | 'explicit' { return .explicit } |
| 552 | 'implicit' { return .implicit } |
| 553 | // empty string marked as .explicit |
| 554 | '' { return .explicit } |
| 555 | else { return error('Bad string for tagged mode') } |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | fn (m TaggedMode) str() string { |
| 560 | match m { |
| 561 | .explicit { return 'explicit' } |
| 562 | .implicit { return 'implicit' } |
| 563 | } |
| 564 | } |
| 565 | |