6502 assembly is a language meant for humans to compile it themselves, not compilers.
"Assembly language" is not compiled. It's assembled by a program called an assembler.
Consider the following simple 6502 assembly program which, on a Commodore 64, will print the string "HELLO WORLD."
LOOP LDA STRING,Y
STRING .DB 'HELLO WORLD'
Above, you have instructions (e.g.
LDY - load Y) and the operand to that instruction (
#0). The operand also expresses the addressing mode, which is "how" the instruction is supposed to get its data.
#$00 means immediate - which means we want to load Y with the literal number 0 and not what is at address $00.
STRING,Y is Y-indexed - this means we want the value at address STRING plus Y.
Each instruction + addressing mode combination results in a unique 1-byte opcode. Opcode list is here.
So you can see from the following excerpt from that site what the assembler does:
Absolute,X LDA $4400,X $BD 3 4+
Absolute,Y LDA $4400,Y $B9 3 4+
The assembler will take the text "LDA $4400,X" and generate the 3 byte sequence
BD 00 44. 6502 consumes its addresses in the instruction stream in low-byte, high-byte order.
Going back to my example program, the assembler would take the line "JSR $FFD2" and at that point output the 3 byte sequence
20 D2 FF.
Can you take the opcode table, look up each opcode in your program yourself, and manually specify the byte stream? Sure. I believe this is what Wozniak did when developing Integer BASIC for the Apple in the 70's. Bill Gates probably did this do for the first BASICs he wrote at that time as well. This is what it means to assemble a program yourself.
You noticed in the short program I wrote that I used labels. This is one of the higher-level things a good assembler does - it goes through the program in 2 passes and inserts/resolves addresses (somewhat like a linker), and this allows you to use labels and it will figure out the addresses.
Compilers will take higher-level constructions like if statements and mathematical expressions, and generate complex sequences of opcodes (or assembler text to be later processed by an assembler) . An assembler is merely translating mnenomics to opcodes and possibly supporting some very simple math like adding two labels or a label and constant together.
Of course some assemblers have macros and a sophisticated preprocessor that blurs the line between assembler and compiler.
Can I compile 6502 assembly into a stand alone application, preferably for Windows?
Obviously a raw 6502 bytestream will not be understood by x86 CPU, and you noticed in my example Commodore 64 program, I used a
JSR $FFD2. This is a ROM routine available only on the Commodore 64 platform. It won't exist on any PC-compatible system. The ROM routine also creates text by writing to "screen memory" - the area of memory $0400-$07FF which the VIC chip reads to generate a display. This VIC chip also doesn't exist on any PC-compatible system.
Emulators can help, of course. There are many 6502 emulators that could be included with 6502 code bytestreams to form a standalone executable. Nothing really stopping anyone from doing that.
But including an emulator as part of an executable - this doesn't solve the problem of how you get 6502 code to perform I/O. You'd have to define a "virtual hardware" platform and also have code that bridges between that virtual platform and the operating system to produce graphical or text out.
UNIX-style text-based terminal I/O would not be too difficult, but something like outputting to Windows forms or interacting with Windows graphics subsystems in a meaningful manner would require more work. And that means the 6502 code you write will only work for this virtual platform.