Regex (.NET), 55 bytes
^.((.))*(?<-1>
.(?=(?<-2>.)*(?(2).)
)(?<2>.)*)*$(?(1).)
Try it on regex101
Neil said in his Retina answer, "It could be done without, but this is code golf, not code challenge." Well, here's a pure regex answer that needs to do it that way (without substitutions done beforehand to lighten the load).
The last line must end with exactly one newline. To remove this restriction would result in worse golf.
In this explanation, ¶
indicates a raw newline in the regex:
^ # Anchor to start of string
. # Skip one character, due to the fact that when we count
# the number of rows, matching it up with our capture count,
# the first row won't becounted.
((.))* # Add the remaining length of this line to the capture count
# of both \1 and \2.
(?<-1> # If there is at least one capture on the \1 stack, do the
# following:
¶ # Advance to the next line (thus proving that the entire
# previous line was processed)
. # Advance by one character (because we already did the same
# on the first line)
(?= # Lookahead - allows the same part of the string to be
# matched in multiple ways.
(?<-2>.)* # For each capture remaining on the \2 stack, advance one
# character. Will stop short if it reaches the end of the
# line before the \2 stack is emptied.
(?(2).)¶ # Assert that if the \2 stack is not empty, there is at
# least one more character remaining in this line, but also
# assert that this is the end of the line - effectively
# asserting that the \2 stack is empty and we're at the end
# of the line.
)
(?<2>.)* # Having already proved that this line is of equal length to
# the previous, transfer its length back into the capture
# count of group \2.
)* # Iterate the above as many times as possible, minimum zero.
$ # Assert that we've reached the end of the string, or a
# newline after which it is the end of the string.
(?(1).) # Effectively assert that the \1 stack is empty, as it is
# impossible to match one more non-newline character after
# reaching the end of the string.
Regex (Perl / PCRE2), 69 64 bytes
^(?=((?|
()|.(?=.*
(\2?+.)))+)+
\2$)((?=^|.+(\4?+
.+)).)+\4$|^.$
Try it online! - Perl
Try it on regex101 - PCRE2
A trailing newline on the last line is optional. Squares with or without it will both match. (In the 69 byte version, the trailing newline was mandatory.)
In PCRE, acceptance of a trailing newline can be disabled with a flag: Try it on regex101
But in Perl, the only way to do this is to replace $
with \z
(+1 byte): Try it online!
The regex does not work in PCRE1 (the older and no longer maintained version of PCRE) due to a PCRE1 bug in which \2
, a nested backreference, is not reverted to its earlier value when backtracking, causing it to have false positives on shapes that have an equal number of lines as characters in their first line, but whose remaining lines monotonically increase (or stay the same) in length.
In this explanation, ¶
indicates a raw newline in the regex:
^ # Anchor to start of string
(?= # Lookahead - allows the same part of the string to
# be matched in multiple ways.
# Assert this is a rectangle (all lines have an equal number of characters)
# with at least 2 lines. A trailing newline on the last line is optional.
(
(?| # Branch Reset Group - allows the same group numbers
# to be captured in different parts of the regex.
# This alternative is to be taken when the inner loop has reached
# the end of a line, finishing this iteration of the outer loop.
¶
() # \2 = empty, in preparation for the next iteration
# of the outer loop
|
# This alternative is to be taken while still processing the line.
. # Go forward one character.
(?= # Lookahead - match what's inside, then jump back to
# this position upon exiting it.
.*¶ # Skip to the next line
( # \2 = the following:
\2?+ # Match previous value of \2 if set
. # Match one extra character in this line
)
)
)+ # Iterate the above as many times as possible
)+ # Iterate the above as many times as possible
¶ # Match a newline. Note that the regex engine will
# be forced to backtrack, because it will have
# already matched this newline inside the loop, and
# erased the contents of \2. Backtracking allows it
# to undo this, restoring the contents of \2 and
# allowing us to match the newline and \2 here.
\2$ # Assert that the last line is not longer than the
# penultimate line, by asserting that the string
# ends after the \2 that was captured. Note that "$"
# will match whether this is the end of the string,
# or a newline followed by the end of the string.
# "\z" only matches at the exact end of the string,
# but we don't need to use it.
)
# Assert the number of characters in the first line equals the number of lines
(
(?= # Lookahead
^ # On the leftmost character, match nothing else.
| # On all subsequent characters, do the following:
.+ # Skip to the end of this line
( # \4 = the following:
\4?+ # Match previous value of \4 if set
¶.+ # Match a newline, followed by one more entire line,
# which we assert is at least one character long.
)
)
. # Go forward one character.
)+ # Iterate the above as many times as possible
\4$ # Assert that the string ends after \4.
|^.$ # Or, match a 1×1 square, which the above code can't
# do. Again, a trailing newline is optional due to
# the behavior of "$".