Regex (Perl / PCRE), 63 60 56 53 bytes
((_?)(\2.)?(?=.*
(\4?(?(?=\2)\3|(\d+,?|{(?5)*})))))+
Try it online! (Perl)
Try it on regex101 (PCRE)
The wildcard is _
and the delimiter between the pattern and the ragged list is a newline.
This regex works by going through the pattern one character at a time; any character other than _
must be matched with the identical character in the pattern, and _
is matched with any positive integer or ragged list. This is done while cumulatively building up a backreference of the full ragged list, as each piece of it is matched.
Due to the optimization of requiring the input to be well-formed, only the ragged lists matched with _
need to be checked for matched brackets. The regex uses {
and }
(curly braces) as its brackets to avoid having to \
-escape them. Similarly, spaces must not be present in the input.
Explanation
(
... )+
Match what is inside any nonzero number of times.
(_?)(\2.)?
Consume the current pattern character. If it is _
, set \2
to contain it. Otherwise, set \2
to be empty and \3
to contain the character. Note that the latter can also happen if the character is _
, but this cannot lead to a subsequent match, because _
cannot be in the input ragged list.
(?=.*
... )
Process the input ragged list in a lookahead, so that outside this lookahead, the input pattern can still be processed picking up where it left off. The .*
skips over what remains of the input pattern, and the newline skips over the newline delimiter.
(\4?(?(?=\2)\3|(\d+,?|{(?5)*})))
Validate that the appropriate section of the input ragged list matches the current character of the input pattern, capturing the entire portion of it matched so far (starting from its beginning) in \4
.
\4?
If this is the first character being processed, \4
will be unset, and this will consume zero characters of the input ragged list. Otherwise, this can either match with what \4
has been set to, or match an empty string. Since the regex is designed only to work on well-formed input, it will in practice always match \4
(the portion of the input ragged list already matched) when it is set, skipping to the appropriate part of the input ragged list to match against the corresponding current input pattern character. The unoptimized version of this would be (?(4)\4)
or \4?+
.
(?(?=\2)
... |
... )
If \2
matches (without consuming it), match against the first part, otherwise match against the second part. \2
will contain _
if that (a wildcard) is the current pattern character, otherwise it will be empty. Since well-formed input can never contain _
in the input ragged list, \2
will only match if the current pattern character is not a wildcard.
\3
Match against the current non-wildcard character.
(\d+,?|{(?5)*})
Match any single positive integer or ragged list. Balancing of opening and closing braces is done here using recursion; (?5)
recursively calls the entire expression above. Since the input is assumed to be well-formed, we can get away with the optimization of not enforcing that there is an integer directly before and after every comma.
Once the entire input pattern has been processed, the newline delimiter is skipped, enforcing that the entire input pattern has been matched against. There is no need to anchor at the beginning or end of the input string, because as long as the input is well-formed (with balanced braces), there is no way that the entire input pattern can be matched without also matching the entire input ragged list.
Regex (.NET), 71 bytes
(?<=(.)+);(?<-1>\1|(?<=(?=\1)_.*)(\d+|<((<)|(?<-4>>)|\d|,)+(?(4)^)>))+$
Try it online!
The wildcard is _
and the delimiter between the pattern and the ragged list is ;
. The brackets are <
and >
.
This regex works by capturing the input pattern character by character in reverse, pushing it onto the Balanced Group stack of Group 1. Then the input ragged list is tested against this by popping the stack in a loop, and for each popped character, matching directly against it if possible, and otherwise, checking if the popped character is _
, and if it is, matching against any ragged list with balanced brackets.
Explanation
(?<=(.)+);
Push the input pattern onto the Group 1 stack, one character at a time in reverse order, then skip to the input ragged list.
(?<-1>
... )+$
Pop the Group 1 stack in a loop, executing the following for each popped character, and then assert that the end of the string (and thus the end of the input ragged list) has been reached.
\1|(?<=(?=\1)_.*)(\d+|<((<)|(?<-4>>)|\d|,)+>)
What to execute for each character popped from the Group 1 stack.
\1
If the character matches, consume it. This can never happen if the character is _
, because a well-formed input ragged list can't contain that.
|
Otherwise, execute the following.
(?<=(?=\1)_.*)
Assert that the pattern character is _
.
(\d+|<((<)|(?<-4>>)|\d|,)+(?(4)^)>)
Match either a single positive integer, or a ragged list with balanced brackets.
(<)
- push an opening bracket onto the Group 4 stack
(?<-4>>)
- pop an entry from the Group 4 stack, and match a closing bracket
\d|,
- match a numeral or comma
(?(4)^)
- assert that the Group 4 stack is empty (literally, assert that if the Group 4 stack is not empty, we're at the beginning of the input string)
0
can match an element which is a list of more than two items, but it also can't be skipped or used to match two elements, so where does the "more than 2" come in? \$\endgroup\$[1, 0], [1, [[2, 3]]]
- my solution was failing that one (although I think it may be R-specific). \$\endgroup\$[[]], []
\$\endgroup\$