Ha, I've done this. Many CPUs have simple, fixed-size instructions that are just a couple of bytes long. For a simple CPU like a Motorola 6800 for example, you could fit all of its instructions on a single sheet of paper. Each instruction would have a two-byte opcode associated with it, and arguments. You could hand-assemble a program by looking up each instruction's opcode. You'd then write your program on paper, annotating each instruction with its corresponding opcode. Once you had written out your program, you could could burn each opcode in sequence to an EPROM which would then store your program. Wire the EPROM up to the CPU with just the right instructions at the right addresses, and you have a simple working program. And to answer your next question, yes. It was painful (we did this in high school). But I have to say that wiring up every chip in an 8-bit computer and writing a program manually gave me a depth of understanding of computer architecture which I could probably not have achieved any other way.
More advanced chips (like x86) are far more difficult to hand-code, because they often have variable-length instructions. VLIW/EPIC processors like the Itanium are close to impossible to hand-code efficiently because they deal in packets of instructions which are optimized and assembled by advanced compilers. For new architectures, programs are almost always written and assembled on another computer first, then loaded into the new architecture. In fact, for firms like Intel who actually build CPUs, they can run actual programs on architectures which don't exist yet by running them on simulators. But I digress...
As for compilers, at their very simplest, they can be little more than "cut and paste" programs. You could write a very simple, non-optimizing, "high level language" that just clusters together simple assembly language instructions without a whole lot of effort.
If you want a history of compilers and programming languages, I suggest you GOTO a history of FORTRAN.
for the very first assembler ever written (i.e. in history), wouldn't it need to be written in machine code
Not necessarily. Of course the very first version v0.00 of the assembler must have been written in machine code, but it would not be sufficiently powerful to be called an assembler. It would not support even half the features of a "real" assembler, but it would be sufficient to write the next version of itself. Then you could re-write v0.00 in the subset of the assembly language, call it v0.01, use it to build the next feature set of your assembler v0.02, then use v0.02 to build v0.03, and so on, until you get to v1.00. As the result, only the first version will be in machine code; the first released version will be in the assembly language.
I have bootstrapped development of a template language compiler using this trick. My initial version was using printf
statements, but the first version that I put to use in my company was using the very template processor that it was processing. The bootstrapping phase lasted less than four hours: as soon as my processor could produce barely useful output, I re-wrote it in its own language, compiled, and threw away the non-templated version.
Best Answer
This has a very clear answer, actually: Source code came first – by a big margin.
Before giving the technical details, a bit of perspective:
The first programming languages were all translated into machine language or assembler by hand. The idea of using a piece of software to automate this translation (either via a compiler or evaluator) always came later, and was far from intuitive.
Consider this quote of the Wikipedia article on FORTRAN which illustrates the reluctance compilers had to face:
=> By the time the FORTRAN compiler hit the market (1957), people were already happily programming both in assembly language and FORTRAN.
The case was similar for LISP (from Hackers & Painters):
Once again, not only does the source code (in LISP) predate the interpreter, the latter wasn’t even implicit in the former.
But these developments are relatively late. Even not considering Charles Babbage’s Analytical Engine and Ada Lovelace’s related first program, there were programming languages in the 20th century which predated compilers:
Konrad Zuse’s Plankalkül and the mathematical construct of λ-calculus introduced by Alonzo Church. Both of these are indubitably formally specified programming languages, but neither had a compiler at the time.
To put this into perspective, λ-calculus is from the 1930s, and Plankalkül was developed around 1945. By contrast, the first FORTRAN compiler came out in 1957 (but again three years after FORTRAN was specified).