ISR macro not recognised

I've copied a code example which should allow an Arduino to perform SPI communication as a slave rather than a master. My PCB is an MKR WAN 1300 (to my understanding this is a SAMD device). The code is reproduced below.
When I compile I get errors, the first of which is:

error: expected constructor, destructor, or type conversion before '(' token
 ISR (SPI_STC_vect)
     ^

I am inferring from this that the ISR macro is not being recognised by the preprocessor. I do not know in what header file this macro is supposed to reside (I have so far failed to determine this from searching the World Wide Web), but a search of all the header files on my system finds no instance of "#define ISR". Various examples (including one associated with the one shown below) did begin with "#include <SPI.h>" so I wondered whether this might somehow bring the macro into existence, but even adding that made no difference. The online documentation does state that the SPI library is included in all Arduino core libraries and so does not have to be explicitly added via the Library Manager.
I am new to Arduino so no doubt this boils down to something that I don't know and that I simply haven't yet encountered in any of the documentation that I have found. All suggestions greatly appreciated.

#include "pins_arduino.h"

char buf [100];
volatile byte pos;
volatile boolean process_it;

void setup (void)
{
  Serial.begin (9600);   // debugging

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);
  
  // turn on interrupts
  SPCR |= _BV(SPIE);
  
  pos = 0;
  process_it = false;
}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;
  
  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    
    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;
      
    }  // end of room available
}

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set
    
}  // end of loop

Does the documentation for the code you copied state what processors it will run on? The code you posted will never run on a SAMD. The low-level registers it accesses (SPCR, SPDR) are AVR-specific.

That's true. But then you have to write code that uses only the SPI Library's APIs rather than trying access the hardware using low-level, processor-specific registers.

No, it doesn't state what processors it will run on. I assumed all Arduinos are pretty similar as regards CPU registers because they would probably all be based on an ARM CPU. I have no idea what actually differentiates SAMD from other classes of Arduino; I have yet to find decent documentation that explains all the stuff you need to know in order to understand the differences and make informed decisions - I feel like I've been thrown in at the deep end.
I think the SPI library's APIs are being avoided because they have been written based on the premise that the Arduino is always the master, whereas what we're trying to do here is make it act as a slave.
All that said, either way I'm still stumped as to where this ISR macro comes from; I've seen multiple Arduino code samples using it so I'd imagine it's pretty standard - all CPUs support interrupts so it would make sense for all Arduinos to use a common macro to define an interrupt handler.

Both of those assumptions are incorrect.

The original Arduinos (as well as the current Uno, Micro, Nano, Mega, Nano Every, and Uno WiFi 2) are all based on the Atmel AVR CPU (which is not an ARM.) (Atmel was acquired by Microchip some years ago, giving Microchip at least 7 different CPU architectures that they sell.)

all CPUs support interrupts so it would make sense for all Arduinos to use a common macro to define an interrupt handler.

ISR() is a macro provided by the avr-libc package and supported by the compiler. (Documented HERE.) On an AVR, an ISR is required to save additional context that isn't saved by the normal call ABI. On an ARM, the hardware save exactly the same context that the ABI specifies, so ISR don't need to be anything more than normal C function (with the right name, to match the vector table.)

A library that contains ISR() macros is NOT portable to non-AVR boards. (unless that code is also "protected" by assorted #ifdef or similar statements.)

Thanks. OK, so it's a mistake to think of Arduinos as a generic or homogenous product line. I was misled down that path of thinking by the presentation of them all using a common IDE and language (.ino "sketches"). Better to view each product as a separate PCB and educate oneself in the libraries for its primary CPU. This is a fairly fundamental piece of knowledge that one needs to understand - I do wish the powers that be would make these things clear up front. Instead I read pages like this telling me that there's a (by implication common) "Arduino programming language", when as far as I can tell we're actually writing C++ in these .ino files and compiling them with gcc.
Is it just me missing something or is the online documentation misleading?

The Board Support Package ("Arduino core") created for each board as well as all of the most-used libraries do a remarkable (IMO) job of presenting a common API to the application programmer. This along with the standard C++ high level language (which the Arduino documentation clearly states is the foundation) allows most code to run as-is across an impressive range of microcontrollers and boards.

It's really only when you go outside that paradigm (as you did with trying to run AVR-specific code on an ARM) do things break down.

Thanks for all the very helpful information.
I have just discovered that community member Maverick123 has contributed library SercomSPISlave for SAM D21-based Arduino boards. Hopefully this will make things straightforward. :slight_smile: