52
\$\begingroup\$

If you have ever looked at an objdump of a C++ program, you have likely seen something like this:

_ZN3foo3bar3bazI6sampleEE3quxvi3foo

This is a C++ mangled symbol, which encodes the namespaces, classes, and function/template arguments, using the Itanium ABI.

Specifically, it is for the following function:

void foo::bar::baz<sample>::qux(int, foo);

Your job is to demangle a C++ symbol in a dramatically simplified version of the Itanium ABI. (That is incompatible with c++filt or __cxa_demangle(), so don't try it).

Everything will be plain identifiers (so no int, void, or "special" identifiers like std::), no const/pointers, no return type encoding, no reuse of template args, etc.

Specifically, these are the syntax rules:

  • All mangled symbols start with _Z.
  • Everything is case sensitive.
  • All identifiers are encoded as <length><name>, where <length> is the positive length of <name> in base 10, so foo is encoded as 3foo, sample is encoded as 6sample, etc.
    • Naturally, this means identifiers will never start with a number (but they can contain numbers after the first letter/underscore). For you regex fans, it is [a-zA-Z_][a-zA-Z0-9_]*.
  • Each symbol is one base identifier, optionally followed by a list of function parameters.
  • Each identifier can be prefixed with a namespace, or suffixed with a template.
    • Namespaces are encoded as a sequence of 1 or more identifiers between an N and an E. Each identifier is printed as the identifier, followed by ::. So N3fooE is foo::, and N3foo3barE is foo::bar::.
      • Namespaces will never have a namespace themselves: you don't have to worry about NN3fooE3barE.
    • Templates are encoded similar to namespaces, only using I..E instead of N..E. They are printed as a comma separated list of identifiers, wrapped in <angle brackets>. These come before :: in a namespace. So I3fooE is <foo>, and I3foo3barE is <foo,bar>.
    • These may be nested.
  • All identifiers after the base symbol are to be treated as function parameters, and they are to be printed as a comma separated list wrapped in (parentheses). This does not apply to namespaces or template arguments.

So, let's take a simpler example:

_ZN3fooI3barEE3baz3qux
_Z                      Mangle prefix
              3baz      Base: Identifier baz
  N          E           Namespace
   3foo                   Identifier foo
       I    E              Template
        3bar                Identifier bar
                  3qux  Parameters: Identifier qux

The result is this:

foo<bar>::baz(qux) 

Your function or program will take a single string containing a mangled symbol, and the output will be the demangled symbol.

You can safely assume each string will only contain numbers, letters, and underscores, and that every identifier will be 99 characters or less.

Assume all symbols are valid, standard input/output format, you know the deal.

You can have any amount of whitespace between identifiers, however, empty parameter/template/namespaces and trailing commas are not allowed.

This is , so shortest program in bytes wins.

Test cases:

_Z1x -> x
_Z3foo3bar -> foo(bar)
_ZN3fooE3bar -> foo::bar
_Z3FOOI3barE -> FOO<bar>
_Z3foo3bar3baz -> foo(bar,baz)
_Z3fooI3barE3baz -> foo<bar>(baz)
_Z3fooI3bar3bazE -> foo<bar,baz>
_ZN3fooE3bar3baZ -> foo::bar(baZ)
_ZN3fooI3barEE3baz3qux -> foo<bar>::baz(qux)
_ZN9CPlusPlusI2isEE11soooooooooo5great -> CPlusPlus<is>::soooooooooo(great)
_ZN2soI1II4herdEE1UI4liekEE9templates -> so<I<herd>>::U<liek>::templates
_Z12identifier12 -> identifier12
_Z2_Z -> _Z
_ZN1a1b1c1d1e1f1g1hE1i -> a::b::c::d::e::f::g::h::i
_ZN1a1bI1c1d1eE1fE1gN1hE1iIN1jE1kI1lEEN1m1nE1o -> a::b<c,d,e>::f::g(h::i<j::k<l>>,m::n::o)
_Z14N3redE7herring1x7foo_bar -> N3redE7herring(x,foo_bar)

Sandbox

\$\endgroup\$
2
  • \$\begingroup\$ Is there a limit to the length of an identifier name? (I ask because at least one answer assumed that it was less than or equal to 99.) \$\endgroup\$
    – Arnauld
    Jun 18 at 15:57
  • \$\begingroup\$ Sure, I'll add that. \$\endgroup\$
    – EasyasPi
    Jun 18 at 16:19

11 Answers 11

17
\$\begingroup\$

C (gcc), 267 ... 222 221 bytes

-30 bytes thanks to @EasyasPi
-8 more bytes thanks to @EasyasPi and @ceilingcat
-1 more byte thanks to @ceilingcat

#define P puts(
char*m;a(){if(*m==73){P"<");for(++m;*m^69;*m-69&&P","))p();m++;P">");}}p(){if(*m==78){for(++m;*m^69;P"::"))p();++m;}a(m+=write(1,m,strtol(m,&m,!a())));}z(char*t){p(m=t+2);for(*m&&P"(");*m;P!*m+L",)"))p();}

Try it online! For pretty output, see previous revisions.

\$\endgroup\$
2
  • \$\begingroup\$ 230 bytes (although it is based off your previous revision) \$\endgroup\$
    – EasyasPi
    Jun 18 at 3:43
  • \$\begingroup\$ 223 bytes: A little ugly but It Works™ \$\endgroup\$
    – EasyasPi
    Jun 18 at 4:43
13
\$\begingroup\$

Python 3, 460 417 362 355 bytes

Try it online!

-43 bytes thanks to @Easyaspi
-55 bytes thanks to @ovs
-7 bytes thanks to @Arnauld

import re
m=input()[2:]
o=""
def a():
 global m,o
 if"I"==m[:1]:
  m=m[1:];o+="<"
  while"E"!=m[:1]:p();o+=","
  m=m[1:];o=o[:-1];o+=">"
def p():
 global m,o
 if"N"==m[:1]:
  m=m[1:]
  while"E"!=m[:1]:p();o+="::"
  m=m[1:]
 a();j=int(re.match(r'\d+',m)[0]);b=len(str(j));o+=m[b:j+b];m=m[j+b:];a()
p()
if m:
 o+="("
 while m:p();o+=","if m else")"
print(o)
\$\endgroup\$
5
  • 1
    \$\begingroup\$ 417 bytes \$\endgroup\$
    – EasyasPi
    Jun 17 at 22:01
  • 5
    \$\begingroup\$ Very nice first answer! A few tips: m[0]if m else"" is the same as m[:1]; You call n and I only once, just replacing the calls with the function body is shorter; if g()=="I" -> if"I"==g(). \$\endgroup\$
    – ovs
    Jun 17 at 22:27
  • \$\begingroup\$ thanks for the suggestions! \$\endgroup\$ Jun 17 at 22:56
  • 1
    \$\begingroup\$ If youre trying to shrink on whitespace, why not use tabs to make it a bit more readable? \$\endgroup\$
    – Sampersand
    Jun 19 at 5:38
  • 2
    \$\begingroup\$ Imagine caring about readability in code golf... \$\endgroup\$
    – EasyasPi
    Jun 19 at 21:54
13
\$\begingroup\$

brainfuck, 494 bytes

>>+++++[->++++++++<]>>>>>,,,[<+>>+++++++[-<---------->]<+[----[<<[<<[.<]>[>]++++++++[-<----->]<[>>-<<[->+<]]>[-<+>]++++++++[-<+++++>]>[<<++++<++++++[-<+++++++>]<->>>>-]]>>-----[>+++++[-<++++++>],[>++++++[-<-------->]<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[->+<[<<->>>-[-<+++++++>]<+.[-]]]]]]]]]]]<<[>[->>++++++++++<<]>>[-<<+>>]<,<<-]+>>]<-[-<,.>]<[-]<+>>]<[->>+++++++[->++++++++<]>++[->+>+<<<<+>+>]>>>>>]>]<[+++++[->++++++++++>>+++++++<<<]>.++>>++>>>]>]<[-<[-]<<[[-]<]<[.[-]<]]>,]<<<<[<]<[.<]

Try it online!

While working on my JavaScript solution, I noticed that a relatively simple stack machine would be able to solve this task. So, naturally, I wrote a Brainfuck solution. Yes, I was bored, what did you expect? Probably still beats Java.

The TIO interpreter handles identifiers of up to 255 chars. Any length should work with an interpreter that has bigint cells and returns 0 for EOF.

Annotated (with a chance of undecipherability)

cell layout
| 0 | finterm | 0 | finsep | 0 | sepflag | parseflag | char/accum | digit | holding |
                                         | 0         | term       | 0     | sep     | etc

>> make space for terminator
+++++ create 5
[- > ++++++++ <] >>> convert to 0 40 0 = (
> add sepflag cell
> add parseflag cell

,, skip _Z

, start reading #
[ loop over input (char cell)
  <+> set parseflag
  > +++++++ set digit cell to 7
  [- < ---------- >] < + subtract E
  [ not E
    ---- subtract I
    [ not I; must be some kind of identifier start
      <<[ sepflag cell set
        <<[.<] print separator
        >[>] return to right of separator
        ++++++++ create 8
        [- < ----- >] subtract ( from separator
        <[ separator wasn't (
          >> - clear sepflag
          << [- > + <] move separator to right
        ]
        > [- < + >] move separator back
        ++++++++ create 8
        [- < +++++ >] fix separator
        >[ sepflag is set; separator was (
          << ++++ update separator to comma
          < ++++++ create 6
          [- < +++++++ >] < - set terminator to 41 )
          >>>>- clear sepflag
        ]
      ]
      >> back to char cell
      ----- subtract N
      [ not N; must be simple identifier #
        > +++++ create 5
        [- < ++++++ >] add back to ch minus 0
        , read into digit cell
        [
          > ++++++ create 6
          [- < -------- >] subtract 0
          < [ not 0
            ->+< [ not 1
              ->+< [ not 2
                ->+< [ not 3
                  ->+< [ not 4
                    ->+< [ not 5
                      ->+< [ not 6
                        ->+< [ not 7
                          ->+< [ not 8
                            ->+< [ not 9 or any digit #
                              <<- clear parseflag
                              >>> - set digit holding cell to 8
                              [- < +++++++ >] < + return the char to its former glory (add 57)
                              . print as first identifier char
                              [-] clear digit cell
                            ]
                          ]
                        ]
                      ]
                    ]
                  ]
                ]
              ]
            ]
          ]
          <<[ parseflag still set; was some digit #
            > back to accumulator
            [->>++++++++++<<] add 10x accumulator to holding
            >>[-<<+>>] move the value back to accumulator
            <, read new digit cell
            <<- clear parseflag to exit this if
          ]
          + reset parseflag to keep parsing digits
          >> back to digit cell
        ]
        digit cell clear; accumulator is valid number now #
        < go to accumulator
        -[-<,.>] print n minus 1 chars; clears accumulator and overwrites parseflag
        <[-] clear parseflag
        <+ set sepflag
        >> back to char cell
      ]
      <[ parseflag set; was N #
        - clear parseflag
        >> +++++++ create 0 0 7 starting from parseflag
        [- > ++++++++ <] > ++ convert to 0 0 0 (58)
        [- > + > + <<<< + > + >] convert to 0 58 58 0 > 58 > 58 > 0 > 0 > (0)
        we are now at new parseflag
      ]
      > back to char cell
    ]
    <[ parseflag set; was I #
      +++++ set parseflag cell to 6
      [- > ++++++++++ >> +++++++ <<<] convert to 0 > (60) 0 42
      . print 60 = lbracket
      ++ >> ++ > convert to 0 62 0 44 0 > 0 > (0)
      we are now at new parseflag
    ]
    > back to char cell
  ]
  <[ parseflag set; was E #
    - clear parseflag
    <[-] clear sepflag cell
    <<[[-]<] clear separator
    <[.[-]<] print and clear terminator
    we're now in prev parseflag cell which is clear
  ]
  > back to char cell
  , read new char #
]
<<<<[<] skip separator
<[.<] print terminator
\$\endgroup\$
8
\$\begingroup\$

Haskell, 257 bytes

(l:r)%x=l:tail(x>>=(',':))++r
h('I':r)|(x,e:r)<-f r=("<>"%x,r)
h e=("",e)
f('N':r)|(x,e:r)<-f r,(h:t,r)<-f r=(((x>>=(++"::"))++h):t,r)
f r|[(n,r)]<-reads r,(i,u)<-h(drop n r),(x,u)<-f u=((take n r++i):x,u)
f e=([],e)
p[x]=x
p(x:y)=x++"()"%y
z=p.fst.f.drop 2

Try it online!

\$\endgroup\$
7
\$\begingroup\$

JavaScript (Node.js), 287 233 bytes

Saved a ton of bytes thanks to @A username, @FZs, @Arnauld and @Recursive Co.!
(Hope I didn't miss anyone!)

(i,r="",P=2,s=c=>i[P]!=c,e=_=>{if(!s`N`)for(P++;s`E`||!++P;r+="::")e();r+=i.slice(P+=b=`${l=parseInt(i.slice(P))}`.length,P+=l);s`I`||P++&a`<>`&P++},a=([[p,q]])=>{for(r+=p;i[P]&&s`E`;i[P]&&s`E`?r+=",":0)e();r+=q})=>i[e(),P]&&a`()`||r

Try it online!

\$\endgroup\$
15
  • 1
    \$\begingroup\$ 250 bytes (conflicts with @Ausername's last change) \$\endgroup\$
    – FZs
    Jun 18 at 11:37
  • 2
    \$\begingroup\$ 247 based on @FZs version \$\endgroup\$
    – emanresu A
    Jun 18 at 12:02
  • 1
    \$\begingroup\$ And... since 'foo' == ['foo'], we don't have to destructure in s, so 241 bytes! I love the loose equality operator! (after 3 comment edits, I finally got the link right) \$\endgroup\$
    – FZs
    Jun 18 at 12:26
  • 2
    \$\begingroup\$ This b=1+l/10|0 really should be b=(l+'').length to support longer identifier names. Here is a version that is still 241 bytes with a few extra golfs. \$\endgroup\$
    – Arnauld
    Jun 18 at 14:00
  • 1
    \$\begingroup\$ By stuffing the first call to e inside the i[P] definition, 233 \$\endgroup\$
    – emanresu A
    Jun 19 at 21:48
6
\$\begingroup\$

JavaScript (Node.js), 216 215 213 204 197 188 bytes

(m,o=2,X=A=>m[o]<X?X(n=A+m[o++]):o,M=x=>m[o]==x?o++:"",I=(e="")=>m[o]?M`E`?"":e+(M`N`&&I()+I`::`+"::")+m.slice(X``,o+=+n)+(M`I`&&`<${I()+I`,`}>`)+(e&&I(e<I?(a=")",","):e)):a)=>I(a="")+I`(`

Try it online! (comes with test cases)

Turns out keeping a major fork of my approach was useful after all. I ended up dropping half of the functions, switching from arrays to strings, and weirdly enough, this new code has lots of repetition that wasn't in the old one.

I wrote this independently based on my Pyth answer's algorithm, and it's currently shorter than any suggestion on the other JS answer, so I'm posting it separately.

Previous approach (197 bytes)

([,,...m],X=A=>m<X?X(A+M(m[0])):A,M=x=>m[0]==x?m.shift():"",N=e=>M`E`?[]:[I()+e,...N(e)],I=_=>[...M`N`&&N`::`,...m.splice(0,X``),M`I`&&`<${N``}>`].join``,R=s=>m+""&&s+I(a=")")+R`,`)=>I(a="")+R`(`+a

Explained

(
  m,       // Input string.
  o = 2,   // Offset into input string.

  X = A =>     // Matches and consumes numbers in input.
    m[o] < X      // Compare input to the stringification of the
                  // function X. Since that begins "A=>", and
                  // valid identifier start chars are all >= "A",
                  // this only matches numbers.
    ? X(          // If we got a number,
      n = A + m[o++] // add it to arg and consume it,
                     // save the result to n,
    )             // then keep parsing.
    : o           // Otherwise, return the current input offset.

  M = x =>     // Matches and consumes input. Used as M`E` or M("E").
    m[o] == x     // Check if first char of input equals (stringified) arg.
    ? o++         // If matched, consume and return something truthy.
    : "",         // Otherwise return "" (falsy and a string).

  I = (e = "") =>  // Recursively demangles identifiers. E-terminated if e!="".
    m[o]              // See if the input is empty.
    ? M`E`            // If input not empty, try to consume "E" from input.
    ? ""              // If successful, return "".
    :                 // Otherwise:
    e                    // Start with the separator.
    + (
      M`N`               // Try to consume "M" from input.
      &&                 // If the match fails, "".
      I()                // If successful, parse an identifier,
      + I`::`            // parse an E-terminated identifier, prefixing
                         // each part with "::",
      + "::"             // and add an additional "::".
    )                    // Append this to the result.

    + m.slice(
      X``,               // Parse a number from input, store it in n and
                         // get the input offset after the number.
      o += +n            // Add n to the input offset.
    )                    // Get the n characters between these indices
                         // and append them to the result.
    + (
      M`I`               // Try to consume "I" from input.
      &&                 // If the match fails, "".
      `<${
        I()              // If successful, parse an identifier,
        + I`,`           // parse an E-terminated identifier, prefixing
                         // each part with ",",
      }>`                // and wrap these in <>.
    )                    // Append this to the result.
    + (                 
      e &&               // If not parsing an E-terminated identifier, "".
      I(
        e < I ? (        // Otherwise, if the separator is less than the
                         // stringification of I (starting "(e=" which
                         // only matches "("),
          a = ")",       // set the terminator to ")"
          ","            // and use "," as the next separator.
        ) : e            // Otherwise, keep using the same separator.
      )                  // Keep searching with that separator.
    ) : a             // If the input was empty, return the terminator.
) =>
  I(           // Parse an identifier
    a=""       // (and initialize a=""),
  ) + I`(`     // then parse an E-terminated identifier (until EOF).
\$\endgroup\$
6
\$\begingroup\$

Pyth, 97 96 95 93 bytes

L*bnz=:z+\^bkL?y\Ek++Eb'bDER&z++&y\N'"::"&yJh:z"\D"3y<zsJ&y\IjP'\,"<>"=ttzpEIzjj\,ufTaYE0)"()

Try it online!

I really didn't expect to beat Retina, and I still think it might come back. Pyth kinda shows its age, but this is starting to feel pretty nicely optimized. I also got to use some very rarely used Pyth features like the imperative D, R and I.

Passes all tests. Requires the -M flag to disable memoization.

Explanation

The program consists of three functions and main code. At the start, input is read to z.

The first function L&nz=:z+\^bkb consumes its argument from the input if it matches.

L                Define y(b):
L*bnz=:z+\^bk
        +\^b      Compute "^" + b.
      :z    k     Replace this regex in z with k="".
     =            Save this in z.
   nz             See if it is different from the previous value of z.
 *b               If so, return b, otherwise "". (Multiply b by 1 or 0).

We'll only be passing N, I, E, numbers and simple identifiers to this, so we can safely use them as regex. There will only ever be 0 or 1 matches thanks to ^.

The second function L?y\Ek++Eb'b recursively demangles a namespace or template, adding its argument after each identifier.

L              Define '(b):
  y\E            Match, consume and return E from z.
 ?               If it was present,
     k           return k.
        E        Otherwise, call E() to demangle an identifier,
       + b       add b,  
      +   'b     and recursively call '(b) and add that.

The third (monster) function DER&z++&y\N'"::"&yJh:z"\D"3y<zsJ&y\IjP'\,"<>" recursively demangles identifiers with namespaces and templates.

DE                                               Define function E():
   &z                                              If z is "", return it.
     ++                                            Concatenate:
        y\N                                        - Match, consume and return N from z.
       &   '"::"                                     If it matched, '("::").
                                                     Otherwise, "".
                    :z"\D"3                       - Split z by non-digits.
                   h                                Take the first piece.
                  J                                 Save it in J.
                 y                                  Consume and return it from z.
                &                                   Always truthy, continue.
                              sJ                    Convert J to int,
                            <z                      take that many chars from z,
                           y                        consume and return them.
                                 y\I              - Match, consume and return I from z.
                                &     '\,           If it matched, call '(","),
                                     P              remove the last ",",
                                    j    "<>"       and put the rest in <>.
                                                    Otherwise, "".
  R                                               Return that.

Finally, the main code.

 ttz                      Strip the first 2 chars of z
=                         and store the result back in z.
     E                    Call E()
    p                     and print the result without newline.
      Iz                  If z isn't empty,
            u     0)        For all H=0,1,..., repeat until results stop changing:
                 E            Call E(),
               aY             append that to Y (initially []),
             fT               and remove falsey elements (i.e. "").
         j\,                Join the values (of Y) with ",".
        j           "()     Put the result in () and print.
\$\endgroup\$
5
\$\begingroup\$

Retina 0.8.2, 133 bytes

1`\d+(?=.*\z)
$*#
}`(#)+((?<-1>.)+)
@$2¶
{`N(@.*)¶@
$1::N@
N(@.*)¶E@
$1::
(¶I@.*)¶@
$1,
}`¶I@(.*)¶E
<$1>¶
^_Z|@|¶$

1>`¶
,
¶(.+)
($1)

Try it online! Link includes test cases. Explanation:

1`\d+(?=.*\z)
$*#

Convert the first integer in the (remainder of the) unconverted string to unary.

(#)+((?<-1>.)+)
@$2¶

Extract the identifier after the integer, mark it so that it doesn't get confused with N or E marks, and put it on its own line with the remainder of the unconverted string on the next line.

}`

Repeat until all of the identifiers have been extracted.

{`
}`

Repeat until all namespaces and templates have been processed.

N(@.*)¶@
$1::N@

If a type has more than one namespace then join each one with the next using ::.

N(@.*)¶E@
$1::

If this is the last namespace then join it to the type and remove the namespace markers.

(¶I@.*)¶@
$1,

If a type has more than one template then join it to the next using ,.

¶I@(.*)¶E
<$1>¶

If this is the last type in the template then wrap the types in <> and remove the template markers.

^_Z|@|¶$

Remove all the remaining leftovers.

1>`¶
,
¶(.+)
($1)

If this is a function then separate the parameters with , and wrap them in ().

\$\endgroup\$
4
\$\begingroup\$

Knight (UB), 142 bytes

;=xP;=l';W&x-69Ax;Cp&&x-69AxO","Cn';C=nB=xSxF1"";Cn;C=pB;O;&?78Ax;E;CnSl 22T"::"O"::"GxL=m+0x m;=xSxF+mLm""&?73Ax;O"<";Cn;ElO">"&x;O"(";ElO")"

Try it online!

Shameless non-winning self-answer. 😏

It is a pretty good example of recursion and EVAL/BLOCK in Knight.

Abuses this interpreter's acceptance of out of bounds ASCII and GET. This won't work on the c/ast interpreter as ASCII("") is an error and so is reading past the end of a string.

# Read line
; = symbol PROMPT
# Template for printing identifier lists
; = parse_iden_list '
    # While symbol is not empty and not E
    ; WHILE & symbol - 69 ASCII(symbol)
        # Recurse into parse()
        ; CALL parse()
        # If symbol is not empty and not E, print sep
        : &(& symbol - 69 ASCII(symbol))
            # dynamically replaced separator
            OUTPUT ","
    # Pop the "E". 
    # UB: This actually cuts off an extra char from the string,
    # but since the interpreter uses strchr to replace newlines,
    # there is going to be an extra null byte to save us.
    : CALL pop()
'
; = pop() BLOCK {
    : = symbol SUBSTITUTE(symbol 0 1 "")
}
# Pop the _Z
; CALL pop()
; CALL pop()
; = parse() BLOCK {
    # Check for N
    ; & ? 78 ASCII(symbol)
        ; CALL pop()
        # Parse the identifier list, replacing "," with "::".
        # I cba to update the offset for the ungolfed version
        ; EVAL SUBSTITUTE(parse_iden_list 22 1 "::")
        # print trailing "::"
        : OUTPUT "::"
    # Get the length (done inline in golfed code)
    # by converting the string to a number.
    ; = length + 0 symbol
    # Print length chars after the number
    # LENGTH(number) will convert int to string and take the length.
    ; OUTPUT GET(symbol, LENGTH(length), length)
    # Pop the number and the identifier
    ; = symbol SUBSTITUTE(symbol (+ length LENGTH(length)) 0 "")
    # Check for I
    # UB: Again, this will technically read out of bounds, but
    # this will in practice return the null terminator.
    : & (? 73 ASCII(symbol))
        ; CALL pop()
        ; OUTPUT "<"
        ; EVAL parse_iden_list
        : OUTPUT ">"
}
# Parse base identifier
; CALL parse()
# Check for parameters
: & symbol 
    ; OUTPUT "("
    ; EVAL parse_iden_list
    : OUTPUT ")"

Knight (no known UB), 146 bytes

;=xP;=l';W&x-69Ax;Cp&&x-69AxO","&xCn';C=nB=xSxF1"";Cn;C=pB;O;&?78Ax;E;CnSl 22T"::"O"::"GxL=m+0x m;=xSxF+mLm""&&x?73Ax;O"<";Cn;ElO">"&x;O"(";ElO")"

Try it online!

Simply adds some range checks to avoid reading an empty string.

\$\endgroup\$
2
\$\begingroup\$

Raku, 221 218 bytes

->[\a,*@b]{a~"({join ',',@b})"x?@b}o{my rule s{(\d+){}:my$n=$0;(.**{$n})}
my rule i{(N$<n>=<~~>+E)?<s>(I$<t>=<~~>+E)?}
sub p($/){join('::',$0<n>».&p,'')xx?$0~$<s>[1]~("<{join ',',$1<t>».&p}>" if $1)}
m/<i>+/<i>».&p}

Try it online!

Leverages the considerable power of Raku's regexes.

\$\endgroup\$
2
  • \$\begingroup\$ 218; you have a trailing newline in your code \$\endgroup\$
    – EasyasPi
    Jun 19 at 23:20
  • \$\begingroup\$ Yowza; thanks for the tip! \$\endgroup\$
    – Sean
    Jun 20 at 0:37
2
\$\begingroup\$

Red, 236 bytes

func[s][d: charset[#"0"-#"9"]i:[copy x[1 2 d](x: do x)keep x skip]n:["N"any[b keep("::")]"E"]t:["I"keep("<")any[b["E"keep(">")break | keep(",")]]]b:[any[n]i opt t]parse s[collect["_Z"b[end | keep("(")any[b[end keep(")")| keep(",")]]]]]]

Try it online!

Output is block that gets printed with a lot of extraneous whitespace, which is allowed, but prepending it with a simple rejoin will produce precisely the same results as in the examples.

Overall, this looks like a perfect task for learning Red's Parse dialect, so here's my attempt at it:

func[s][
 ; Digits rule:
 d: charset[#"0"-#"9"] 
 ; Identifiers rule:
 i: [copy x[1 2 d](x: do x)keep x skip] 
  ; Match 1-2 digits, copy to x, eval x as number, skip and save next x characters
 ; Namespaces rule:
 n: ["N"any[b keep("::")]"E"]
  ; "N", any number of Base components (b) appended with "::" ,"E"
 ; Templates rule:
 t: ["I"keep("<")any[b["E"keep(">")break | keep(",")]]] 
  ; "I" (change to "<"), any number of b appended with "," or with ">" if followed by "E"
 ; Base component rule:
 b: [any[n]i opt t] 
  ; Any number of namespaces, main identifier and optional template
 ; Actual parse operation:
 parse s [collect ; 'collect' all entries after 'keep'
   ; Final rule:
   ["_Z" b [end | keep("(") any [ b [end keep(")") | keep(",")]]]]] 
   ; "_Z", base component, "(" if the input hasn't ended yet,
   ; any number of subsequent b's as parameters, separated by "," and ")" in the end
]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.