Matag is an esolang which is almost a self-redefining matrix-based tag system. 'Almost' because it doesn't completely fit any of them but at the same time fits all of these descriptors to a certain degree.
Matag has a queue of matrices, but the matrices behave more like they normally do in math than in programming. All elements inside of the matrices are bytes. There is also a separate byte state, by default zero. Finally, there is one redefinable command, which is the only measure of doing any sort of looping or conditionals. Thankfully, subprograms are relatively easy to store in the queue, so by constantly switching out the definitions you can accomplish some actual goals. The subprogram is by default a no-op.
The code starts with the initial queue. Each matrix is on its own line, starting with the ones to be dequeued first. The whole matrix is surrounded by square brackets, with another pair surrounding each row. Row contents are a comma separated list of whole numbers from 0-255.
Then comes a blank line, and then the commands.
Each command gets its own ascii character, but they also get a number used inside of subprogram matrices.
(any non-command character) 0 - no-op : 1 - pop x, pushes it twice ; 2 - pop x / 3 - pop x, pop y, push y, push x + 4 - pop x, pop y, push x + y. If they aren't the same dimensions, enqueue the 2x2 identity - 5 - same as +, but subtracts instead * 6 - pop x, pop y, push x * y. If the number of rows in x does not equal the number of columns in y, push the 2x2 identity | 7 - pop x, push the inverse of x, if it does not have an inverse, push the 2x2 identity # 8 - push the 2x2 identity & 9 - pop x, pop y, redefine the @ command. See the subprogram section @ 10 - execute the @ command ? 11 - continue rotating the queue until the front contains an identity matrix, or until a full rotation has been performed ^ 12 - pop x, pop y, push x resized to y, deleting the rightmost rows and lowermost columns if y is smaller, or padding with zeroes to the right and bottom if y is bigger. > 13 - pop x, move in English reading order through x, printing the character of each ascii value < 14 - pop x, input enough ascii characters to fill a matrix the same dimensions as x, push that matrix . 15 - pop x, push x ' 255 - pops x. Depending on x, executes a different command. X may be any matrix. Any matrix with the top left as 0 is reserved for future versions of this specification, if there are any. Any others may be used for whatever. Mainly intended for extensions to the language.
When defining a subprogram, two matrices are popped. When @ is run, it dequeues an element. If it is not an identity, the first is run. If it is, the second is run. This is a nice way to handle conditional branching.
The matrix will contain various rows. The first element in the row is the state to check, and the second is the state to change it to. The rest are commands, notated with their subprogram number, the leftmost executing first.
Recursion can be used, and @ can redefine itself while executing. If it redefines itself, the rest of the original definition still finishes. You can even redefine and call @, then return back to an old definition on a higher level that didn't finish yet. Although once you redefine @, you can no longer recurse or loop. Luckily, the system makes it pretty easy to store definitions away. We can duplicate both definitions. This makes it xxyy. Then we can duplicate the first x, redefine with the middle ones, and duplicate the 2nd y. This makes xxyy at the back again, so we can keep subprograms indefinitely for whenever it needs to be used.
[[0,0,13]] [[0,0,1,13,10]] [] [] &</:..-@