shrub/pub/doc/hoon/library/3bd.md
2015-06-19 17:16:48 -04:00

46 KiB

section 3bD, JSON and XML

++moon

Mime type to ++cord

++  moon                                                ::  mime type to text
  |=  myn=mite
  %+  rap
    3
  |-  ^-  tape
  ?~  myn  ~
  ?:  =(~ t.myn)  (trip i.myn)
  (weld (trip i.myn) `tape`['/' $(myn t.myn)])
::

Renders a mime type path with infix / to a cord.

myn is a ++mite, a list of @ta.

~zod/try=> `@t`(moon /image/png)
'image/png'
~zod/try=> `@t`(moon /text/x-hoon)
'text/x-hoon'
~zod/try=> `@t`(moon /application/x-pnacl)
'application/x-pnacl'

++perk

Parse cube with fork

++  perk                                                ::  parse cube with fork
  |*  a=(pole ,@tas)
  ?~  a  fail
  ;~  pose 
    (cold -.a (jest -.a))
    $(a +.a)
  ==
::

Parser generator. Produces a parser that succeeds upon encountering one of the ++terms in a faceless list a.

A- perk is an arm used to parse one of a finite set of options, formally a choice between terms: if you want to match "true" or "false", and nothing else, (perk ~[%true %false]) produces the relevant parser, whose result type is ?(%true %false). For more complicated transformations, a combintation of ++sear and map ++get is recommended, e.g. (sear ~(get by (mo ~[[%true &] [%false |]]))) sym) will have a similar effect but produce ?(& |) ,a loobean. However, constructions such as (sear (flit ~(has in (sa %true %false %other ~))) sym) are needlessly unwieldy.

a is a ++pole, which is a ++list without %faces

~zod/try=> (scan "ham" (perk %sam %ham %lam ~))
%ham
~zod/try=> (scan "ram" (perk %sam %ham %lam ~))
! {1 1}
! exit

++poja

JSON parser core

++  poja                                                ::  JSON parser core
  =<  |=(a=cord (rush a apex))
  |%

JSON parser core: parses a ++cord a to the hoon structure for JSON, a ++json.

a is ++cord.

~zod/try=> (poja '[1,2,3]')
[~ [%a p=~[[%n p=~.1] [%n p=~.2] [%n p=~.3]]]]
~zod/try=> (poja 'null')
[~ ~]
~zod/try=> (poja 'invalid{json')
~

++apex

Parse object

   ++  apex  ;~(pose abox obox)                          ::  JSON object

Top level parsing rule. Parses either a single JSON object, or an array of JSON objects to a ++json. See also: ++abox, ++obox.

A- this is now called ++tops, and ++apex is the old ++valu, which apparently didn't make its way into the docs. Apologies for the confusion, many things have been restructured in ++shell inconsistently with current test:urbit.git

~zod/try=> (rash '[1,2]' apex:poja)
[%a p=~[[%n p=~.1] [%n p=~.2]]]
~zod/try=> (rash '{"sam": "kot"}' apex:poja)
[%o p={[p=~.sam q=[%s p=~.kot]]}]
~zod/try=> (rash 'null' apex:poja)
! {1 1}
! exit

++valu

Parse value

  ++  valu                                              ::  JSON value
    %+  knee  *json  |.  ~+
    ;~  pfix  spac
      ;~  pose
        (cold ~ (jest 'null'))
        (jify %b bool)
        (jify %s stri)
        (cook |=(s=tape [%n p=(rap 3 s)]) numb)
        abox
        obox
      ==
    ==

Parsing rule. Parses JSON values to ++json.

~zod/try=> (rash '[1,2]' valu:poja)
[%a p=~[[%n p=~.1] [%n p=~.2]]]
~zod/try=> (rash '{"sam": "kot"}' valu:poja)
[%o p={[p='sam' q=[%s p=~.kot]]}]
~zod/try=> (rash 'null' valu:poja)
~
~zod/try=> (rash '20' valu:poja)
[%n p=~.20]
~zod/try=> (rash '"str"' valu:poja)
[%s p=~.str]
~zod/try=> (rash 'true' valu:poja)
[%b p=%.y]

++abox

Parse array

  ++  abox  (stag %a (ifix [sel (ws ser)] (more (ws com) valu)))

Parsing rule. Parses a JSON array with values enclosed within [] and delimited by a ,.

~zod/try=> (rash '[1, 2,4]' abox:poja)
[[%n p=~.1] ~[[%n p=~.2] [%n p=~.4]]]

JSON Objects

++pair

Parse key value pair

  ++  pair  ;~(plug ;~(sfix (ws stri) (ws col)) valu)

Parsing rule. Parses a ++json from a JSON key-value pair of a string and value delimited by :.

~zod/try=> (rash '"ham": 2' pair:poja)
['ham' [%n p=~.2]]

++obje

Parse array of objects

  ++  obje  (ifix [(ws kel) (ws ker)] (more (ws com) pair))

Parsing rule. Parses a ++json from an array of JSON object key-value pairs that are enclosed within {} and separated by ,.

~zod/try=> (rash '{"ham": 2, "lam":true}' obje:poja)
[['ham' [%n p=~.2]] ~[['lam' [%b p=%.y]]]]

++obox

Parse boxed object

  ++  obox  (stag %o (cook mo obje))

Parsing rule. Parses an array of JSON objects to a ++json map with a tag of %o. See also: ++json.

~zod/try=> (rash '{"ham": 2, "lam":true}' obox:poja)
[%o {[p='lam' q=[%b p=%.y]] [p='ham' q=[%n p=~.2]]}]

JSON Booleans

++bool

Parse boolean

  ++  bool  ;~(pose (cold & (jest 'true')) (cold | (jest 'false')))

Parsing rule. Parses a string of either true or false to a ++json boolean.

~zod/try=> (rash 'true' bool:poja)
%.y
~zod/try=> (rash 'false' bool:poja)
%.n
~zod/try=> (rash 'null' bool:poja)
! {1 1}
! exit

JSON strings

++stri

Parse string

  ++  stri
    (cook crip (ifix [doq doq] (star jcha)))

Parsing rule. Parse a string to a ++cord. A JSON string is a list of characters enclosed in double quotes along with escaping \s, to a ++cord. See also ++jcha.

~zod/try=> (rash '"ham"' stri:poja)
'ham'
~zod/try=> (rash '"h\\nam"' stri:poja)
'h
  am'
~zod/try=> (rash '"This be \\"quoted\\""' stri:poja)
'This be "quoted"'

++jcha

Parse char from string

 ++  jcha  ;~(pose ;~(less doq bas prn) esca)           :: character in string

Parsing rule. Parses either a literal or escaped character from a JSON string to a ++cord.

~zod/try=> (rash 'a' jcha:poja)
'a'.
~zod/try=> (rash '!' jcha:poja)
'!'
~zod/try=> (rash '\\"' jcha:poja)
'"'
~zod/try=> (rash '\\u00a4' jcha:poja)
'¤'
~zod/try=> (rash '\\n' jcha:poja)
'
 '

++esca

Parse escaped char

  ++  esca                                               :: Escaped character
    ;~  pfix  bas
      ;~  pose
        doq  fas  soq  bas
        (sear ~(get by `(map ,@t ,@)`(mo b/8 t/9 n/10 f/12 r/13 ~)) low)
        ;~(pfix (just 'u') (cook tuft qix:ab))           :: 4-digit hex to UTF-8
      ==

Parsing rule. Parses a backslash-escaped special character, low ASCII, or UTF16 codepoint, to a ++cord.

~zod/try=> (rash 'b' esca:poja)
! {1 1}
! exit
~zod/try=> (rash '\n' esca:poja)
~ <syntax error at [1 9]>
~zod/try=> (rash '\\n' esca:poja)
'
 '
~zod/try=> `@`(rash '\\r' esca:poja)
13
~zod/try=> (rash '\\u00c4' esca:poja)
'Ä'
~zod/try=> (rash '\\u00df' esca:poja)
'ß'

JSON numbers

A- JSON numbers are stored as cords internally in lieu of full float support, so ++numb and subarms are really more validators than parsers per se.

++numb

Parse number

  ++  numb
    ;~  (comp twel)
      (mayb (piec hep))
      ;~  pose
        (piec (just '0'))
        ;~(plug (shim '1' '9') digs)
      ==
      (mayb frac)
      (mayb expo)
    ==

Parsing rule. Parses decimal numbers with an optional -, fractional part, or exponent part, to a ++cord.

~zod/try=> (rash '0' numb:poja)
~[~~0]
~zod/try=> (rash '1' numb:poja)
~[~~1]
~zod/try=> `tape`(rash '1' numb:poja)
"1"
~zod/try=> `tape`(rash '12.6' numb:poja)
"12.6"
~zod/try=> `tape`(rash '-2e20' numb:poja)
"-2e20"
~zod/try=> `tape`(rash '00e20' numb:poja)
! {1 2}
! exit

++digs

Parse 1-9

  ++  digs  (star (shim '0' '9'))

Parsing rule. Parses digits 0 through 9 to a ++tape.

~zod/try=> (rash '' digs:poja)
""
~zod/try=> (rash '25' digs:poja)
"25"
~zod/try=> (rash '016' digs:poja)
"016"
~zod/try=> (rash '7' digs:poja)
"7"

++expo

Parse exponent part

  ++  expo                                               :: Exponent part
    ;~  (comp twel)
      (piec (mask "eE"))
      (mayb (piec (mask "+-")))
      digs
    ==

Parsing rule. Parses an exponent to a ++cord. An exponent is an e, followed by an optional + or -, followed by digits.

~zod/try=> `tape`(rash 'e7' expo:poja)
"e7"
~zod/try=> `tape`(rash 'E17' expo:poja)
"E17"
~zod/try=> `tape`(rash 'E-4' expo:poja)
"E-4"

++frac

Fractional part

  ++  frac   ;~(plug dot digs)                          :: Fractional part

Parsing rule. Parses a dot followed by digits to a ++cord.

~zod/try=> (rash '.25' frac:poja)
[~~~. "25"]
~zod/try=> (rash '.016' frac:poja)
[~~~. "016"]
~zod/try=> (rash '.7' frac:poja)
[~~~. "7"]

whitespace

++spac

Parse whitespace

  ++  spac  (star (mask [`@`9 `@`10 `@`13 ' ' ~]))

Parsing rule. Parses a whitespace to a ++tape.

~zod/try=> (scan "" spac:poja)
""
~zod/try=> (scan "   " spac:poja)
"   "
~zod/try=> `*`(scan `tape`~[' ' ' ' ' ' `@`9 ' ' ' ' `@`13] spac:poja)
[32 32 32 9 32 32 13 0]
~zod/try=> (scan "   m " spac:poja)
! {1 4}
! exit

++ws

Allow prefix whitespace

  ++  ws  |*(sef=_rule ;~(pfix spac sef))

Parser modifier. Produces a rule that allows for a whitespace before applying sef.

sef is a ++rule.

~zod/try=> (rash '   4' digs:poja)
! {1 1}
! exit
~zod/try=> (rash '   4' (ws digs):poja)
"4"
~zod/try=> (rash '''
                 
                 4
                 ''' (ws digs):poja)
"4"

Plumbing

++mayb

Maybe parse

  ++  mayb  |*(bus=_rule ;~(pose bus (easy "")))

Parser modifier. Need to document, an example showing failure.

~zod/try=> (abox:poja 1^1 "not-an-array")
[p=[p=1 q=1] q=~]
~zod/try=> ((mayb abox):poja 1^1 "not-an-array")
[p=[p=1 q=1] q=[~ [p="" q=[p=[p=1 q=1] q="not-an-array"]]]]

++twel

Weld two tapes

  ++  twel  |=([a=tape b=tape] (weld a b))

Concatenates two tapes, a and b, producing a ++tape.

a is a ++tape.

b is a ++tape.

~zod/try=> (twel "sam" "hok"):poja
~[~~s ~~a ~~m ~~h ~~o ~~k]
~zod/try=> (twel "kre" ""):poja
~[~~k ~~r ~~e]

++piec

Parse char to list

  ++  piec
    |*  bus=_rule
    (cook |=(a=@ [a ~]) bus)
::

Parser modifer. Parses an atom with bus and then wraps it in a ++list.

bus is a ++rule.

~zod/try=> (scan "4" (piec:poja dem:ag))
[4 ~]

++pojo

Print JSON

++  pojo                                                ::  print json
  |=  val=json
  ^-  tape
  ?~  val  "null"
  ?-    -.val
      %a
    ;:  weld
      "["
      =|  rez=tape
      |-  ^+  rez
      ?~  p.val  rez
      $(p.val t.p.val, rez :(weld rez ^$(val i.p.val) ?~(t.p.val ~ ",")))
      "]"
    ==
 ::
      %b  ?:(p.val "true" "false")
      %n  (trip p.val)
      %s
    ;:  welp
      "\""
      %+  reel
        (turn (trip p.val) jesc)
      |=([p=tape q=tape] (welp +<))
      "\""
    ==
      %o
    ;:  welp
      "\{"
      =+  viz=(~(tap by p.val) ~)
      =|  rez=tape
      |-  ^+  rez
      ?~  viz  rez
      %=    $
          viz  t.viz
          rez
        :(welp rez "\"" (trip p.i.viz) "\":" ^$(val q.i.viz) ?~(t.viz ~ ","))
      ==
      "}"
    ==
  ==
::

Renders a ++json val as a ++tape.

val is a json.

~zod/try=> (pojo [%n '12.6'])
"12.6"
~zod/try=> (crip (pojo %n '12.6'))
'12.6'
~zod/try=> (crip (pojo %s 'samtel'))
'"samtel"'
~zod/try=> (crip (pojo %a ~[(jone 12) (jape "ha")]))
'[12,"ha"]'
~zod/try=> (crip (pojo %a ~[(jone 12) ~ (jape "ha")]))
'[12,null,"ha"]'
~zod/try=> (crip (pojo %o (mo sale/(jone 12) same/b/| ~)))
'{"same":false,"sale":12}'

++poxo

Print XML

++  poxo                                                ::  node to tape
  =<  |=(a=manx `tape`(apex a ~))
  |_  unq=?                                             ::  unq

Renders a ++manx a as a ++tape.

a is a ++manx.

~zod/try=> (poxo ;div;)
"<div></div>"
~zod/try=> (poxo ;div:(p a))
"<div><p></p><a></a></div>"
~zod/try=> (poxo ;div:(p:"tree > text" a))
"<div><p>tree &gt; text</p><a></a></div>"

++apex

Inner XML printer

  ++  apex                                              ::  top level
    |=  [mex=manx rez=tape]
    ^-  tape
    ?:  ?=([%$ [[%$ *] ~]] g.mex)
      (escp v.i.a.g.mex rez)
    =+  man=`mane`n.g.mex
    =.  unq  |(unq =(%script man) =(%style man))
    =+  tam=(name man)
    =.  rez  :(weld "</" tam ">" rez)
    =+  att=`mart`a.g.mex
    :-  '<'
    %+  welp  tam
    =.  rez  ['>' (many c.mex rez)]
    ?~(att rez [' ' (attr att rez)])
  ::  

Renders a ++manx as a ++tape, appending a suffix rez\.

rez is a ++tape.

~zod/try=> (apex:poxo ;div; "")
"<div></div>"
~zod/try=> (apex:poxo ;div:(p a) "").
"<div><p></p><a></a></div>"
~zod/try=> (apex:poxo ;div:(p a) "--sfix")
"<div><p></p><a></a></div>--sfix"
~zod/try=> (apex:poxo ;div:(p:"tree > text" a) "")
"<div><p>tree &gt; text</p><a></a></div>"
~zod/try=> (~(apex poxo &) ;div:(p:"tree > text" a) "")
"<div><p>tree > text</p><a></a></div>"

++attr

Print attributes

  ++  attr                                              ::  attributes to tape
    |=  [tat=mart rez=tape]
    ^-  tape
    ?~  tat  rez
    =.  rez  $(tat t.tat)
    ;:  weld 
      (name n.i.tat)
      "=\"" 
      (escp(unq |) v.i.tat '"' ?~(t.tat rez [' ' rez]))
    ==

Render XML attributes as a ++tape.

tat is a ++mart.

rez is a ++tape.

~zod/try=> (attr:poxo ~ "")
""
~zod/try=> (crip (attr:poxo ~[sam/"hem" [%tok %ns]^"reptor"] ""))
'sam="hem" tok:ns="reptor"'
~zod/try=> (crip (attr:poxo ~[sam/"hem" [%tok %ns]^"reptor"] "|appen"))
'sam="hem" tok:ns="reptor"|appen'

++escp

Escape XML

  ++  escp                                              ::  escape for xml
    |=  [tex=tape rez=tape]
    ?:  unq
      (weld tex rez)
    =+  xet=`tape`(flop tex)
    |-  ^-  tape
    ?~  xet  rez
    %=    $
      xet  t.xet
      rez  ?-  i.xet
             34  ['&' 'q' 'u' 'o' 't' ';' rez]
             38  ['&' 'a' 'm' 'p' ';' rez]
             39  ['&' '#' '3' '9' ';' rez]
             60  ['&' 'l' 't' ';' rez]
             62  ['&' 'g' 't' ';' rez]
             *   [i.xet rez]
           ==
    ==
  ::

Escapes the XML special characters ", &, ', <, >.

texis a ++tape.

rez is a ++tape.

~zod/try=> (escp:poxo "astra" ~)
~[~~a ~~s ~~t ~~r ~~a]
~zod/try=> `tape`(escp:poxo "astra" ~)
"astra"
~zod/try=> `tape`(escp:poxo "x > y" ~)
"x &gt; y"
~zod/try=> `tape`(~(escp poxo &) "x > y" ~)
"x > y"

++name

Print name

  ++  name                                              ::  name to tape
    |=  man=mane  ^-  tape
    ?@  man  (trip man)
    (weld (trip -.man) `tape`[':' (trip +.man)])
  ::

Renders a ++mane as a ++tape.

man is a ++mane.

~zod/try=> (name:poxo %$)
""
~zod/try=> (name:poxo %ham)
"ham"
~zod/try=> (name:poxo %ham^%tor)
"ham:tor"

++many

Print node list

  ++  many                                              ::  nodelist to tape
    |=  [lix=(list manx) rez=tape]
    |-  ^-  tape
    ?~  lix  rez
    (apex i.lix $(lix t.lix))
  ::

Renders multiple XML nodes as a ++tape

lix is a ++list of ++manx.

rez is a ++tape.

~zod/try=> (many:poxo ~ "")
""
~zod/try=> (many:poxo ;"hare" "")
"hare"
~zod/try=> (many:poxo ;"hare;{lep}ton" "")
"hare<lep></lep>ton"
~zod/try=> ;"hare;{lep}ton"
[[[%~. [%~. "hare"] ~] ~] [[%lep ~] ~] [[%~. [%~. "ton"] ~] ~] ~]

++poxa

Parse XML

++  poxa                                                ::  xml parser
  =<  |=(a=cord (rush a apex))
  |%

Parses an XML node from a ++cord, producing a unit ++manx.

a is a ++cord.

~zod/try=> (poxa '<div />')
[~ [g=[n=%div a=~] c=~]]
~zod/try=> (poxa '<html><head/> <body/></html>')
[~ [g=[n=%html a=~] c=~[[g=[n=%head a=~] c=~] [g=[n=%body a=~] c=~]]]]
~zod/try=> (poxa '<script src="/gep/hart.js"/>')
[~ [g=[n=%script a=~[[n=%src v="/gep/hart.js"]]] c=~]]
~zod/try=> (poxa '<<<<')
~

++apex

Top level parser

  ++  apex
    =+  spa=;~(pose comt whit)
    %+  knee  *manx  |.  ~+
    %+  ifix  [(star spa) (star spa)]
    ;~  pose
      %+  sear  |=([a=marx b=marl c=mane] ?.(=(c n.a) ~ (some [a b])))
        ;~(plug head (more (star comt) ;~(pose apex chrd)) tail)
      empt
    == 
  :: 

Parses a node of XML, type ++manx.

~zod/try=> (rash '<div />' apex:xmlp)
[g=[n=%div a=~] c=~]
~zod/try=> (rash '<html><head/> <body/></html>' apex:xmlp)
[g=[n=%html a=~] c=~[[g=[n=%head a=~] c=~] [g=[n=%body a=~] c=~]]]
~zod/try=> (rash '<script src="/gep/hart.js"/>' apex:xmlp)
[g=[n=%script a=~[[n=%src v="/gep/hart.js"]]] c=~]
~zod/try=> (rash '<<<<' apex:xmlp)
! {1 2}
! exit

++attr

Parse XML attributes

  ++  attr                                              ::  attributes
    %+  knee  *mart  |.  ~+ 
    %-  star
    ;~  plug
        ;~(sfix name tis)
        ;~  pose 
            (ifix [doq doq] (star ;~(less doq escp)))
            (ifix [soq soq] (star ;~(less soq escp)))
        ==
      ==  
  ::

Parses the list of attributes inside the opening XML tag, which is zero or more space-prefixed name to string values. Result type ++mart

~zod/try=> (rash '' attr:xmlp)
~
~zod/try=> (rash 'sam=""' attr:xmlp)
! {1 1}
! exit
~zod/try=> (rash ' sam=""' attr:xmlp)
~[[n=%sam v=""]]
~zod/try=> (rash ' sam="hek"' attr:xmlp)
~[[n=%sam v="hek"]]
~zod/try=> (rash ' sam="hek" res="actor"' attr:xmlp)
~[[n=%sam v="hek"] [n=%res v="actor"]]
~zod/try=> (rash ' sam=\'hek\' res="actor"' attr:xmlp)
~[[n=%sam v="hek"] [n=%res v="actor"]]
~zod/try=> (rash ' sam=\'hek" res="actor"' attr:xmlp)
! {1 23}
! exit

++chrd

Parse character data

  ++  chrd                                              ::  character data
    %+  cook  |=(a=tape ^-(mars :/(a)))
    (plus ;~(less soq doq ;~(pose (just `@`10) escp)))
  ::

Parsing rule. Parses XML character data. Result type ++mars

~zod/try=> (rash 'asa' chrd:xmlp)
[g=[n=%$ a=~[[n=%$ v="asa"]]] c=~]
~zod/try=> (rash 'asa &gt; are' chrd:xmlp)
[g=[n=%$ a=~[[n=%$ v="asa > are"]]] c=~]
~zod/try=> (rash 'asa > are' chrd:xmlp)
! {1 6}
! exit

++comt

Parses comments

  ++  comt                                              ::  comments 
    =-  (ifix [(jest '<!--') (jest '-->')] (star -))
    ;~  pose 
      ;~(less hep prn) 
      whit
      ;~(less (jest '-->') hep)
    ==
  ::

Parsing rule. Parses XML comment blocks.

~zod/try=> (rash '<!--  bye -->' comt:xmlp)
"  bye "
~zod/try=> (rash '<!--  bye  ><<<>< - - -->' comt:xmlp)
"  bye  ><<<>< - - "
~zod/try=> (rash '<!--  invalid -->-->' comt:xmlp)
! {1 18}
! exit

++escp

Parse (possibly) escaped char

  ++  escp
    ;~  pose
      ;~(less gal gar pam prn)
      (cold '>' (jest '&gt;'))
      (cold '<' (jest '&lt;'))
      (cold '&' (jest '&amp;'))
      (cold '"' (jest '&quot;'))
      (cold '\'' (jest '&apos;'))
    ==

Parsing rule. Parses a nonspecial or escaped character. Result type ++char

~zod/try=> (rash 'a' escp:xmlp)
'a'
~zod/try=> (rash 'ab' escp:xmlp)
! {1 2}
! exit
~zod/try=> (rash '.' escp:xmlp)
'.'
~zod/try=> (rash '!' escp:xmlp)
'!'
~zod/try=> (rash '>' escp:xmlp)
! {1 2}
! exit
~zod/try=> (rash '&gt;' escp:xmlp)
'>'
~zod/try=> (rash '&quot;' escp:xmlp)
'"'

++empt

Parse self-closing tag

  ++  empt                                              ::  self-closing tag
    %+  ifix  [gal (jest '/>')]  
    ;~(plug ;~(plug name attr) (cold ~ (star whit)))  
  ::

Parsing rule. Parses self-closing XML tags that end in />.

~zod/try=> (rash '<div/>' empt:xmlp)
[[%div ~] ~]
~zod/try=> (rash '<pre color="#eeffee" />' empt:xmlp)
[[%pre ~[[n=%color v="#eeffee"]]] ~]
~zod/try=> (rash '<pre color="#eeffee"></pre>' empt:xmlp)
! {1 21}
! exit

++head

Parse opening tag

  ++  head                                              ::  opening tag
    (ifix [gal gar] ;~(plug name attr))
  ::

Parsing rule. Parses the opening tag of an XML node. Result type ++marx

~zod/try=> (rash '<a>' head:xmlp)
[n=%a a=~]
~zod/try=> (rash '<div mal="tok">' head:xmlp)
[n=%div a=~[[n=%mal v="tok"]]]
~zod/try=> (rash '<div mal="tok" />' head:xmlp)
! {1 16}
! exit

++name

Parse tag name

  ++  name                                              ::  tag name 
    %+  knee  *mane  |.  ~+
    =+  ^=  chx
        %+  cook  crip 
        ;~  plug 
            ;~(pose cab alf) 
            (star ;~(pose cab dot alp))
        ==
    ;~(pose ;~(plug ;~(sfix chx col) chx) chx)
  ::

Parsing rule. Parses the name of an XML tag. Result type ++mane

~zod/try=> (scan "ham" name:xmlp)
%ham
~zod/try=> (scan "ham:tor" name:xmlp)
[%ham %tor]
~zod/try=> (scan "ham-tor" name:xmlp)
%ham-tor
~zod/try=> (scan "ham tor" name:xmlp)
! {1 4}
! exit

++tail

Parse closing tag

  ++  tail  (ifix [(jest '</') gar] name)               ::  closing tag

Parsing rule. Parses an XML closing tag.

~zod/try=> (scan "</div>" tail:xmlp)
%div
~zod/try=> (scan "</a>" tail:xmlp)
%a
~zod/try=> (scan "</>" tail:xmlp)
! {1 3}
! exit

++whit

Parse whitespace, etc.

  ++  whit  (mask ~[' ' `@`0x9 `@`0xa])                 ::  whitespace
::

Parsing rule. Parses newlines, tabs, and spaces.

~zod/try=> `@`(scan " " whit:xmlp)
32
~zod/try=> `@`(scan "  " whit:xmlp)
! {1 2}
! exit
~zod/try=> `@`(scan "\0a" whit:xmlp)
10
~zod/try=> `@`(scan "\09" whit:xmlp)
9
~zod/try=> `@`(scan "\08" whit:xmlp)
! {1 1}
! exit

++jo

JSON reparsing core

++  jo                                                  ::  json reparser
  =>  |%  ++  grub  (unit ,*) 
          ++  fist  $+(json grub)
  |%

Contains converters of ++json to ++unit well-typed structures.

A fist is a gate that produces a grub.

A grub is a unit of some JSON value.

++ar

Parse array to list

  ++  ar                                                ::  array as list
    |*  wit=fist
    |=  jon=json
    ?.  ?=([%a *] jon)  ~
    %-  zl
    |-  
    ?~  p.jon  ~
    [i=(wit i.p.jon) t=$(p.jon t.p.jon)]
  ::

Reparser modifier. Reparses an array to the ++unit of a homogenous ++list using wit to reparse every element.

wit is a ++fist, a JSON reparser.

~zod/try=> :type; ((ar ni):jo a/~[n/'1' n/'2'])
[~ u=~[1 2]]
{[%~ u=it(@)] %~}

++at

Reparse array as tuple

  ++  at                                                ::  array as tuple
    |*  wil=(pole fist)
    |=  jon=json
    ?.  ?=([%a *] jon)  ~
    =+  raw=((at-raw wil) p.jon)
    ?.((za raw) ~ (some (zp raw)))
  ::

Reparser generator. Reparses an array as a fixed-length tuple of ++units, using a list of ++fists.

wil is a ++pole a faceless list of ++fists.

~zod/try=> ((at ni so ni ~):jo a/~[n/'3' s/'to' n/'4'])
[~ u=[q=3 ~.to q=4]]
~zod/try=> :type; ((at ni so ni ~):jo a/~[n/'3' s/'to' n/'4'])
[~ u=[q=3 ~.to q=4]]
{{[%~ u=[q=@ @ta q=@]] %~} %~}
~zod/try=> ((at ni so ni ~):jo a/~[n/'3' s/'to' n/''])
~

++at-raw

Reparse array to tuple

    ++  at-raw                                            ::  array as tuple
    |*  wil=(pole fist)
    |=  jol=(list json)
    ?~  wil  ~
    :-  ?~(jol ~ (-.wil i.jol))
    ((at-raw +.wil) ?~(jol ~ t.jol))
  ::

Reparser generator. Reparses a list of ++json to a tuple of ++unit using wil.

wil is a ++pole, a faceless list of ++fists.

~zod/try=> ((at-raw ni ni bo ~):jo ~[s/'hi' n/'1' b/&])
[~ [~ 1] [~ u=%.y] ~]

++bo

Reparse boolean

  ++  bo                                                ::  boolean
    |=(jon=json ?.(?=([%b *] jon) ~ [~ u=p.jon]))
  ::

Reparser modifier. Reparses a boolean to the ++unit of a loobean.

~zod/try=> (bo:jo [%b &])
[~ u=%.y]
~zod/try=> (bo:jo [%b |])
[~ u=%.n]
~zod/try=> (bo:jo [%s 'hi'])
~

++bu

Reparse boolean not

  ++  bu                                                ::  boolean not
    |=(jon=json ?.(?=([%b *] jon) ~ [~ u=!p.jon]))
  ::

Reparser modifier. Reparses the inverse of a boolean to the ++unit of a loobean.

~zod/try=> (bu:jo [%b &])
[~ u=%.n]
~zod/try=> (bu:jo [%b |])
[~ u=%.y]
~zod/try=> (bu:jo [%s 'hi'])
~

++cu

Reparse and transform

  ++  cu                                                ::  transform
    |*  [poq=$+(* *) wit=fist]
    |=  jon=json
    (bind (wit jon) poq)
  ::

Reparser modifier. Reparses jon and slams the result through wit, producing a ++unit.

wit is a ++fist.

poq is a gate that accepts and returns a noun.

~zod/try=> ((cu dec ni):jo [%n '20'])
[~ 19]
~zod/try=> ((cu dec ni):jo [%b &])
~

++da

Reparse UTC date

  ++  da                                                ::  UTC date
    |=  jon=json
    ?.  ?=([%s *] jon)  ~
    (bind (stud (trip p.jon)) |=(a=date (year a)))
  ::

Reparser modifier. Reparses a UTC date string to a ++unit.

~zod/try=> (da:jo [%s 'Wed, 29 Oct 2014 0:26:15 +0000'])
[~ ~2014.10.29..00.26.15]
~zod/try=> (da:jo [%s 'Wed, 29 Oct 2012 0:26:15'])
[~ ~2012.10.29..00.26.15]
~zod/try=> (da:jo [%n '20'])
~

++di

Reparse millisecond date

  ++  di                                                ::  millisecond date
    |=  jon=json
    %+  bind  (ni jon)
    |=  a=@u  ^-  @da
    (add ~1970.1.1 (div (mul ~s1 a) 1.000))
  ::

Reparser modifier. Reparses the javascript millisecond date integer to a ++unit.

~zod/try=> (di:jo [%s '2014-10-29'])
~
~zod/try=> (di:jo [%n '1414545548325'])
[~ ~2014.10.29..01.19.08..5333.3333.3333.3333]
~zod/try=> (di:jo [%n '1414545615128'])
[~ ~2014.10.29..01.20.15..20c4.9ba5.e353.f7ce]
~zod/try=> (di:jo [%n '25000'])
[~ ~1970.1.1..00.00.25]

++mu

Reparse unit

  ++  mu                                                ::  true unit
    |*  wit=fist
    |=  jon=json
    ?~(jon (some ~) (bind (wit jon) some))
  ::

Reparser modifier. Reparses wit to a ++unit.

A- JSON units are considered to be either JSON null or the requested value, and are reparsed to results of ~ or (some {value}) respectively

wit is a ++fist.

~zod/try=> ((mu ni):jo [%n '20'])
[~ [~ u=q=20]]
~zod/try=> ((mu ni):jo [%n '15'])
[~ [~ u=q=15]]
~zod/try=> ((mu ni):jo ~)
[~ u=~]
~zod/try=> ((mu ni):jo [%s 'ma'])
~

++ne

Reparse number as real

  ++  ne                                                ::  number as real
    |=  jon=json
    ^-  (unit ,@rd)
    !!
  ::

XX Currently unimplemented

A- yup, this will eventually reparse a floating point atom, but interfaces for the latter are not currently stable.

++ni

Reparse number as integer

  ++  ni                                                ::  number as integer
    |=  jon=json 
    ?.  ?=([%n *] jon)  ~
    (rush p.jon dem)
  ::

Reparser modifier. Reparses an integer representation to a `++unit.

~zod/try=> (ni:jo [%n '0'])
[~ q=0]
~zod/try=> (ni:jo [%n '200'])
[~ q=200]
~zod/try=> (ni:jo [%n '-2.5'])
~
~zod/try=> (ni:jo [%s '10'])
~
~zod/try=> (ni:jo [%b |])
~
~zod/try=> (ni:jo [%n '4'])
[~ q=4]
~zod/try=> (ni:jo [%a ~[b/& b/& b/& b/&]])
~

++no

Reparse number as text

  ++  no                                                ::  number as text
    |=  jon=json
    ?.  ?=([%n *] jon)  ~
    (some p.jon)
  ::

Reparser modifier. Reparses a numeric representation to a ++cord.

~zod/try=> (no:jo [%n '0'])
[~ u=~.0]
~zod/try=> (no:jo [%n '200'])
[~ u=~.200]
~zod/try=> (no:jo [%n '-2.5'])
[~ u=~.-2.5]
~zod/try=> (no:jo [%s '10'])
~
~zod/try=> (no:jo [%b |])
~
~zod/try=> (no:jo [%n '4'])
[~ u=~.4]
~zod/try=> (no:jo [%a ~[b/& b/& b/& b/&]])
~

++of

Reparse object to frond

  ++  of                                                ::  object as frond
    |*  wer=(pole ,[cord fist])
    |=  jon=json
    ?.  ?=([%o [@ *] ~ ~] jon)  ~
    |-
    ?~  wer  ~
    ?:  =(-.-.wer p.n.p.jon)  
      ((pe -.-.wer +.-.wer) q.n.p.jon)
    ((of +.wer) jon)
  ::

Reparser generator. Reparses an object, succeeding if it corresponds to one of the key-value pairs in wer.

wer is a ++pole, a ++faceless list of ++cord and ++fist key-value pairs.

~zod/try=> ((of sem/sa som/ni ~):jo %o [%sem s/'hi'] ~ ~)
[~ [%sem "hi"]]
~zod/try=> ((of sem/sa som/ni ~):jo %o [%som n/'20'] ~ ~)
[~ [%som q=20]]
~zod/try=> ((of sem/sa som/ni ~):jo %o [%som s/'he'] ~ ~)
~
~zod/try=> ((of sem/sa som/ni ~):jo %o [%som s/'5'] ~ ~)
~
~zod/try=> ((of sem/sa som/ni ~):jo %o [%sem s/'5'] ~ ~)
[~ [%sem "5"]]
~zod/try=> ((of sem/sa som/ni ~):jo %o [%sem n/'2'] ~ ~)
~
~zod/try=> ((of sem/sa som/ni ~):jo %o [%sem b/&] ~ ~)
~
~zod/try=> ((of sem/sa som/ni ~):jo %a ~[s/'som' n/'4'])
~
~zod/try=> ((of sem/sa som/ni ~):jo %o [%sem s/'hey'] ~ [%sam s/'other value'] ~ ~)
~

++ot

Reparse object as tuple

  ++  ot                                                ::  object as tuple
    |*  wer=(pole ,[cord fist])
    |=  jon=json
    ?.  ?=([%o *] jon)  ~
    =+  raw=((ot-raw wer) p.jon)
    ?.((za raw) ~ (some (zp raw)))
  ::

Reparser generator. For every key in wer that matches a key in the [++edge], the fist in wer is applied to the corresponding value in the ++edge, the results of which are produced in a tuple.

wer is a ++pole of ++cord to ++fist key-value pairs.

~zod/try=> (jobe [%sem s/'ha'] [%som n/'20'] ~)
[%o p={[p='sem' q=[%s p=~.ha]] [p='som' q=[%n p=~.20]]}]
~zod/try=> ((ot sem/sa som/ni sem/sa ~):jo (jobe [%sem s/'ha'] [%som n/'20'] ~))
[~ u=["ha" q=20 "ha"]]

++ot-raw

    ++  ot-raw                                            ::  object as tuple
    |*  wer=(pole ,[cord fist])
    |=  jom=(map ,@t json)
    ?~  wer  ~
    =+  ten=(~(get by jom) -.-.wer)
    [?~(ten ~ (+.-.wer u.ten)) ((ot-raw +.wer) jom)]
    ::

Reparser generator. Reparses a map jom using wer; for every key in wer that matches a key in map, the corresponding ++fist is applied to the corresponding value in jom, the results of which are produced in a tuple.

~zod/try=> ((ot-raw sem/sa som/ni sem/sa ~):jo (mo [%sem s/'ha'] [%som n/'20'] ~))
[[~ u="ha"] [~ q=20] [~ u="ha"] ~]
~zod/try=> ((ot-raw sem/sa som/ni sem/sa ~):jo (mo [%sem s/'ha'] [%som b/|] ~))
[[~ u="ha"] ~ [~ u="ha"] ~]

++om

Parse object to map

    ++  om                                                ::  object as map
    |*  wit=fist
    |=  jon=json
    ?.  ?=([%o *] jon)  ~
    (zm ~(run by p.jon) wit)
  ::

Reparser modifier. Reparses a ++json object to a homogenous map using wit.

wit is a ++fist.

~zod/try=> ((om ni):jo (jobe [%sap n/'20'] [%sup n/'5'] [%sop n/'177'] ~))
[~ {[p='sup' q=q=5] [p='sop' q=q=177] [p='sap' q=q=20]}]
~zod/try=> ((om ni):jo (jobe [%sap n/'20'] [%sup n/'0x5'] [%sop n/'177'] ~))
~    

++pe

Add prefix

  ++  pe                                                ::  prefix
    |*  [pre=* wit=fist]
    (cu |*(a=* [pre a]) wit)
  ::

Reparser modifier. Adds a static prefix pre to the parse result of wit. See also: ++stag.

pre is a prefix noun.

~zod/try=> (ni:jo n/'2')
[~ q=2]
~zod/try=> (ni:jo b/|)
~
~zod/try=> ((pe %hi ni):jo n/'2')
[~ [%hi q=2]]
~zod/try=> ((pe %hi ni):jo b/|)
~

++sa

Reparse string to tape

  ++  sa                                                ::  string as tape
    |=  jon=json
    ?.(?=([%s *] jon) ~ (some (trip p.jon)))
  ::

Reparser modifier. Reparses a ++json string to a ++tape.

~zod/try=> (sa:jo s/'value')
[~ u="value"]
~zod/try=> (sa:jo n/'46')
~
~zod/try=> (sa:jo a/~[s/'val 2'])
~

++so

Reparse string to cord

  ++  so                                                ::  string as cord
    |=  jon=json
    ?.(?=([%s *] jon) ~ (some p.jon))
  ::

Reparser modifier. Reparses a string to a ++cord.

~zod/try=> (so:jo s/'value')
[~ u=~.value]
~zod/try=> (so:jo n/'46')
~
~zod/try=> (so:jo a/~[s/'val 2'])
~

++su

Reparse string

  ++  su                                                ::  parse string
    |*  sab=rule
    |=  jon=json
    ?.  ?=([%s *] jon)  ~
    (rush p.jon sab)
  ::

Reparser generator. Produces a reparser that applies sab to a string.

sab is a ++rule.

~zod/try=> ((su:jo fed:ag) s/'zod')
[~ 0]
~zod/try=> ((su:jo fed:ag) s/'doznec')
[~ 256]
~zod/try=> ((su:jo fed:ag) s/'notship')
~
~zod/try=> ((su:jo fed:ag) n/'20')
~

++ul

Reparse null

  ++  ul  |=(jon=json ?~(jon (some ~) ~))               ::  null

Reparser modifier. Reparses a null value.

~zod/try=> (ul:jo `json`~)
[~ u=~]
~zod/try=> (ul:jo s/'null')
~
~zod/try=> (ul:jo b/|)
~
~zod/try=> (ul:jo b/&)
~

++za

Pole of nonempty units

  ++  za                                                ::  full unit pole
    |*  pod=(pole (unit))
    ?~  pod  &
    ?~  -.pod  |
    (za +.pod)
  ::

Determines if pod contains no empty units, producing a loobean. Used internally.

pod is a ++pole of ++unit.

~zod/try=> (za:jo ~[`1 `2 `3])
%.y
~zod/try=> (za:jo ~[`1 ~ `3])
%.n

++zl

Collapse unit list

  ++  zl                                                ::  collapse unit list
    |*  lut=(list (unit))
    ?.  |-  ^-  ?
        ?~(lut & ?~(i.lut | $(lut t.lut)))
      ~
    %-  some
    |-
    ?~  lut  ~
    [i=u:+.i.lut t=$(lut t.lut)]
  ::

Produces a unit of the values of lut if every unit in lut is nonempty. Otherwise, produces ~. If any of the ++units in lut are empty, produces null.

lut is a ++list of ++units.

~zod/try=> (zl:jo `(list (unit))`~[`1 `2 `3])
[~ u=~[1 2 3]]
~zod/try=> (zl:jo `(list (unit))`~[`1 `17 `3])
[~ u=~[1 17 3]]
~zod/try=> (zl:jo `(list (unit))`~[`1 ~ `3])
~

++zp

Parses a

  ++  zp                                                ::  unit tuple
    |*  but=(pole (unit))
    ?~  but  !!
    ?~  +.but  
      u:->.but
    [u:->.but (zp +.but)]
  ::

Collapses a ++pole of ++units but, producing a tuple.

but is a ++pole of ++unit.

~zod/try=> (zp:jo `(pole (unit))`~[`1 `2 `3])
[1 2 3]
~zod/try=> (zp:jo `(pole (unit))`~[`1 `17 `3])
[1 17 3]
~zod/try=> (zp:jo `(pole (unit))`~[`1 ~ `3])
! exit

++zm

Collapse unit map

  ++  zm                                                ::  collapse unit map
    |*  lum=(map term (unit))
    ?:  (~(rep by lum) | |=([[@ a=(unit)] b=?] |(b ?=(~ a))))
      ~
    (some (~(run by lum) need))
::

Produces a ++unit of the map lum of term to ++unit key value pairs, with all of the nonempty values stripped of their ++unit wrappers. If any of the ++units in lum are empty, ~ is produced. See also: ++zp, ++zl.

lum is a map of ++term to ++units.

~zod/try=> (zm:jo `(map term (unit ,@u))`(mo a/`4 b/`1 c/`2 ~))
[~ {[p=%a q=4] [p=%c q=2] [p=%b q=1]}]
~zod/try=> (zm:jo `(map term (unit ,@u))`(mo a/`4 b/~ c/`2 ~))
~
~zod/try=> (~(run by `(map ,@t ,@u)`(mo a/1 b/2 c/3 ~)) (flit |=(a=@ (lth a 5))))
{[p='a' q=[~ u=1]] [p='c' q=[~ u=3]] [p='b' q=[~ u=2]]}
~zod/try=> (zm:jo (~(run by `(map ,@t ,@u)`(mo a/1 b/2 c/3 ~)) (flit |=(a=@ (lth a 5)))))
[~ {[p='a' q=1] [p='c' q=3] [p='b' q=2]}]
~zod/try=> (zm:jo (~(run by `(map ,@t ,@u)`(mo a/1 b/7 c/3 ~)) (flit |=(a=@ (lth a 5)))))
~
~zod/try=> (~(run by `(map ,@t ,@u)`(mo a/1 b/7 c/3 ~)) (flit |=(a=@ (lth a 5))))
{[p='a' q=[~ u=1]] [p='c' q=[~ u=3]] [p='b' q=~]}

++joba

++json from key-value pair

++  joba                                                ::  object from k-v pair
  |=  [p=@t q=json]
  ^-  json
  [%o [[p q] ~ ~]]
::

Produces a ++json object with one key.

p is a @t key.

q is a ++json.

~zod/try=> (joba %hi %b |)
[%o p={[p='hi' q=[%b p=%.n]]}]
~zod/try=> (crip (pojo (joba %hi %b |)))
'{"hi":false}'
~zod/try=> (joba %hi (jone 2.130))
[%o p={[p='hi' q=[%n p=~.2130]]}]
~zod/try=> (crip (pojo (joba %hi (jone 2.130))))
'{"hi":2130}'

++jobe

Object from key-value list

++  jobe                                                ::  object from k-v list
  |=  a=(list ,[p=@t q=json])
  ^-  json
  [%o (~(gas by *(map ,@t json)) a)]
::

Produces a ++json object from a list a of key to ++json values.

a is a ++list of ++cord to ++json values.

~zod/try=> (jobe a/n/'20' b/~ c/a/~[s/'mol'] ~)
[%o p={[p='a' q=[%n p=~.20]] [p='c' q=[%a p=~[[%s p=~.mol]]]] [p='b' q=~]}]
~zod/try=> (crip (pojo (jobe a/n/'20' b/~ c/a/~[s/'mol'] ~)))
'{"b":null,"c":["mol"],"a":20}'

++jape

++json string from tape

++  jape                                                ::  string from tape
  |=  a=tape
  ^-  json
  [%s (crip a)]
::

Produces a ++json string from a ++tape.

~zod/try=> (jape ~)
[%s p=~.]
~zod/try=> (jape "lam")
[%s p=~.lam]
~zod/try=> (crip (pojo (jape "lam")))
'"lam"'
~zod/try=> (crip (pojo (jape "semtek som? zeplo!")))
'"semtek som? zeplo!"'

++jone

++json number from unigned

++  jone                                                ::  number from unsigned
  |=  a=@u
  ^-  json
  :-  %n
  ?:  =(0 a)  '0'
  (crip (flop |-(^-(tape ?:(=(0 a) ~ [(add '0' (mod a 10)) $(a (div a 10))])))))
::

Produces a ++json number from an unsigned atom.

a is a @u.

~zod/try=> (jone 1)
[%n p=~.1]
~zod/try=> (pojo (jone 1))
"1"
~zod/try=> (jone 1.203.196)
[%n p=~.1203196]
~zod/try=> (pojo (jone 1.203.196))
"1203196"

++jesc

Escape JSON character

++  jesc
  |=  a=@  ^-  tape
  ?+  a  [a ~]
    10  "\\n"
    34  "\\\""
    92  "\\\\"
  ==
::

Produces a ++tape of an escaped ++json character a.

a is an atom

~zod/try=> (jesc 'a')
"a"
~zod/try=> (jesc 'c')
"c"
~zod/try=> (jesc '\\')
"\\"
~zod/try=> (jesc '"')
"\""

++scanf

Formatted scan

++  scanf                                              ::  formatted scan
  |*  [tape (pole ,_:/(*$&(_rule tape)))]
  =>  .(+< [a b]=+<)
  (scan a (parsf b))

Scan with ;"-interpolated parsers.

A- here there be monsters, monsters of my making. But the basic idea is you use ;" (which currently is parsed by sail but shouldn't be) to mix literal text and [++rule]s, and apply this to text which is a correspending mixture of aforementioned literals and sections parsable by the relevant rules. ++parsf is the parser form that combines a tape-rule mix into one big ++rule, ++norm being a parsf internal that winnows the ;" result into a list of discriminate literals and rules, and ++bill doing the actual composing: ++$:parsf just adds a layer that collapses the result list to a tuple, such that (scanf "foo 1 2 bar" ;"foo {dem} {dem} bar") parses [1 2] and not [1 2 ~].

~zod/try=> `[p=@ud q=@ud]`(scanf "Score is 5 to 2" [;"Score is {n} to {n}"]:n=dim:ag)
[p=5 q=2]

~zod/try=> =n ;~(pfix (star (just '0')) (cook |=(@ud +<) dim:ag))
~zod/try=> (scanf "2014-08-12T23:10:58.931Z" ;"{n}\-{n}\-{n}T{n}:{n}:{n}.{n}Z")
[2.014 8 12 23 10 58 931]
~zod/try=> =dat (scanf "2014-08-12T23:10:58.931Z" ;"{n}\-{n}\-{n}T{n}:{n}:{n}.{n}Z")
~zod/try=> `@da`(year `date`dat(- [%& -.dat], |6 ~[(div (mul |6.dat (bex 16)) 1.000)]))
~2014.8.12..23.10.58..ee56

++parsf

++  parsf                                              ::  make parser from:
  |^  |*  a=(pole ,_:/(*$&(_rule tape)))               ::  ;"chars{rule}chars"
      %-  cook  :_  (bill (norm a))
      |*  (list)
      ?~  +<  ~
      ?~  t  i
      [i $(+< t)]
  ::

parsf generates a _rule from a tape with rules embedded in it, literal sections being matched verbatim. The parsed type is a tuple of the embedded rules' results.

Two intermediate arms are used:

++norm

  ::  .=  (norm [;"{n}, {n}"]:n=dim:ag)  ~[[& dim] [| ", "] [& dim]]:ag
  ++  norm                                             
    |*  (pole ,_:/(*$&(_rule tape)))
    ?~  +<  ~
    =>  .(+< [i=+<- t=+<+])
    :_  t=$(+< t)
    =+  rul=->->.i
    ^=  i
    ?~  rul     [%| p=rul]
    ?~  +.rul   [%| p=rul]
    ?@  &2.rul  [%| p=;;(tape rul)]
    [%& p=rul]
  ::

norm converts a ;" pole of [[%~. [%~. ?(tape _rule)] ~] ~] into a more convenient list of discriminated tapes and rules.

++bill

  ::  .=  (bill ~[[& dim] [| ", "] [& dim]]:ag)
  ::  ;~(plug dim ;~(pfix com ace ;~(plug dim (easy)))):ag
  ++  bill
    |*  (list (each ,_rule tape))
    ?~  +<  (easy ~)
    ?:  ?=(| -.i)  ;~(pfix (jest (crip p.i)) $(+< t))
    %+  cook  |*([* *] [i t]=+<)
    ;~(plug p.i $(+< t))
  --
::

bill builds a parser out of rules and tapes, ignoring the literal sections and producing a list of the rules' results.

++taco

++  taco                                                ::  atom to octstream
  |=  tam=@  ^-  octs
  [(met 3 tam) tam]
::

An [octs] contains a length, to encode trailing zeroes.

~zod/try=> (taco 'abc')
[p=3 q=6.513.249]
~zod/try=> `@t`6.513.249
'abc'

++tact

++  tact                                                ::  tape to octstream
  |=  tep=tape  ^-  octs
  (taco (rap 3 tep))
::

octs from tape

~zod/try=> (tact "abc")
[p=3 q=6.513.249]
~zod/try=> `@t`6.513.249
'abc'

++tell

++  tell                                                ::  wall to octstream
  |=  wol=wall  ^-  octs
  =+  buf=(rap 3 (turn wol |=(a=tape (crip (weld a `tape`[`@`10 ~])))))
  [(met 3 buf) buf]
::

octs from wall

~zod/try=> (tell ~["abc" "line" "3"])
[p=11 q=12.330.290.663.108.538.769.039.969]
~zod/try=> `@t`12.330.290.663.108.538.769.039.969
'''
abc
line
3
'''

++txml

++  txml                                                ::  string to xml
  |=  tep=tape  ^-  manx
  [[%$ [%$ tep] ~] ~]
::

Tape to xml CDATA node

~zod/try=> (txml "hi")
[g=[n=%$ a=~[[n=%$ v="hi"]]] c=~]
~zod/try=> (txml "larton bestok")
[g=[n=%$ a=~[[n=%$ v="larton bestok"]]] c=~]