Special Character Coding Challenge - Check string for validity (Lots of rules)

Raku, 41 bytes

{?/^[\s*\!?\s*\d+\.?\d*\s*]+%<[%&#$!]>$/}

Try it online!

Checks if the input matches against a regex.

Explanation:

{?/^                                 $/}  # Check if the input is
    [                     ]+  # A series of
        \!?                   # An optional ! followed by
              \d+\.?\d*       # A number
     \s*   \s*         \s*    # Interspersed with optional whitespace
                            %           # Joined by
                             <[    ]>   # Any of
                               %&#!     # The special characters

05AB1E, 46 45  44  43 bytes

Crossed out &nbsp;44&nbsp; is no longer 44 :)

þ'.ªžQ7£`)5L.Þ:DÔƵKå≠s2K¤s¬4%s•8мZ_r•₄вå≠`P

-2 bytes thanks to @ExpiredData.

Outputs 1 for truthy and 0 or a positive integer for falsey (NOTE: only 1 is truthy in 05AB1E, so this is completely fine with the truthy/falsey definition of the meta).

Try it online or verify all test cases.

Explanation:

Unfortunately 05AB1E doesn't have any regexes. So instead I'll:

Convert the input to something that's easier to work with:

þ             # Only leave the digits of the (implicit) input-string
 '.ª         '# Convert the number to a list of digits, and append a "." to this list
žQ            # Push a constant with all printable ASCII characters (range [32,126])
  7£          # Only leave the first 7: ' !"#$%&'
    `         # Push them all separated to the stack
     )        # Wrap everything on the stack into a list
      5L      # Push a list [1,2,3,4,5]
        .Þ    # Extend the trailing item infinitely: [1,2,3,4,5,5,5,...]
          :   # Replace all characters in the (implicit) input-string with these
              # We now have a number consisting only of the digits [1,2,3,5],
              # where 1 is a digit or "."; 2 are spaces; 3 is "!"; and 4 is one of "%&#$"
              #  i.e. "5.4 $ 2.9 !3.4 &! 89.8213" → 1112521112311125321111111

After which it's easier to check whether it complies to all the rules:

Rule 5: check that there are no two numbers with spaces in between:

D             # Duplicate the number
 Ô            # Connected uniquify them (i.e. 1112521112311125321111111 → 1252123125321)
  ƵK          # Push compressed integer 121
    å≠        # And check that the number does NOT contains 121 as substring

Remove all spaces. And then rules 3 and 4: check that the input ends with a digit, and starts with a "!" or digit:

s             # Swap to get the number at the top again
 2K           # Remove all 2s (the spaces)
   ¤          # Push the last digit (without popping the number itself)
    s         # Swap again
     ¬        # And push the first digit (without popping the number itself)
      4%      # And take modulo 4, so 1 remains 1, and 5 becomes 1

Rules 1 and 2: check that it does not contain "!S"/"!!"/"!!!"/S!! (where S is one of the special characters "%&#$"):

s             # Swap to get the number at the top again
 •8мZ_r•      # Push compressed integer 35055333533
        ₄в    # Convert it to base-1000 as list: [35,55,333,533]
          å≠  # Check for each that the number does NOT contain it as substring
            ` # And push all four checks separated to the stack

And finally check if all were truthy:

P             # Then take the product of all values on the stack
              # (after which this is output implicitly as result)

See this 05AB1E tip of mine (sections How to compress large integers? and How to compress integer lists?) to understand why ƵK is 121; •8мZ_r• is 35055333533; and •8мZ_r•₄в is [35,55,333,533].


Perl 5 -p, 72 bytes

$_=!/^[%#&\$]|^!!|([%#&!\$]\s*){3}|[%#!&\$]\s*[%#&\$]|\d\s+\d|\D$/&&/\d/

Try it online!