Summary Files Commits Branches @commit @diff @stats
diff options
context:
space:
mode:
Commit65500035eb3a06cdc13110d4a6ca5c5a9670cac6 (patch)
Author
 Syndamia <kamen@syndamia.com>
2025-07-31 00:29:49 +0300
Committer
 Syndamia <kamen@syndamia.com>
2025-07-31 00:29:49 +0300
Files7a9a357008c18a032be3a7942b54ff225da8bb49
c64-programs-65500035eb3a06cdc13110d4a6ca5c5a9670cac6.tar
c64-programs-65500035eb3a06cdc13110d4a6ca5c5a9670cac6.tar.gz
c64-programs-65500035eb3a06cdc13110d4a6ca5c5a9670cac6.zip
Parentac48787507f35a3e2c178c8b075cecd23330f9cb (diff)
feat: Add initial hello world program
1 files changed, 118 insertions, 0 deletions
-rw-r--r--hello_world/hello.oph118
diff --git a/hello_world/hello.oph b/hello_world/hello.oph
new file mode 100644
index 0000000..1d4e3ba
--- /dev/null
+++ b/hello_world/hello.oph
@@ -0,0 +1,118 @@
+; ophis hello.oph && x64 hello.prg >/dev/null
+
+; The 6502 is little endian! (4 3 2 1)
+
+; PRG format
+; Contains two bytes of header data, address from which
+; the rest of the file will be loaded into memory.
+; BASIC memory starts at addr 2048, we effectively place "10 SYS 2064"
+; and an end of program after it. Then immedately afterwards
+; we place our command on addr 2064.
+; When BASIC loads up, it runs "10 SYS 2064" and passes up execution there.
+; Then, when we return, we get a zero which indicates termination.
+
+; lables
+; [a-z]: Normal label
+; _[a-z]: Temporary label (only available in scope)
+; * Anonymous label, use "+" for next anonymous, "++" for next after next anonymous
+; and "-" for prev anonymous, "--" for prev before prev anonymous and so on.
+; constants
+; [0-9]+ decimal
+; $[0-9]+ hex
+; 0[0-9]+ octal
+; %[01]+ binary
+; '[ -~] ASCII character
+; ".*" string, backslash escapes are available, produces a series of words/dwords
+; addresses
+; number := placing a constant signifies address of memory where value is stored
+; #number := is immediate value, not an address to a value in memory, but the value itself
+;
+; implicit - number is interpreted, depending on the function (instruction) itself
+; zero page - address is only 8 bits ($00 to $FF), limits only to first page in memory ($0000 to $00FF)
+; zero page,x - same as zero page, but address is "value + x register" (where result wraps around, into the zero page)
+; zero page,y - same as zero page, but address is "value + y register" (where result wraps around, into the zero page)
+; absolute - 16 bit address ($0000 to $FFFF)
+; absolute,x - 16 bit address + x register ($0000 to $FFFF; value wraps around)
+; absolute,y - 16 bit address + y register ($0000 to $FFFF; value wraps around)
+; (indirect) - the desired address can be found at the given address (i.e. the address to the value is stored in memory; only used in jump instruction?)
+; operators (unary)
+; < := low byte of a word
+; > := high byte of a word
+; [ ] := brackets for grouping expressions
+; operators (binary)
+; * / + - := arithmetic operators
+; | & ^ := bitwise operators
+; commands
+; .byte := byte of data (placed directly)
+; .org := from which addr onwards bytes are placed (origin; value of program counter)
+; .word := word of data, 2 bytes, values as big endian (1 2 3 4)
+; .dword := double word of data, 4 bytes, value as ?
+;
+; .advance := fill everything with 0, until the given address
+;
+; .scope := open scope
+; .scend := end scope
+; .alias := takes a name and an address, sets name to the address
+;
+; .include := inserts file at the given line
+; .incbin := include binary data
+; .require := include file if it hasn't been already
+;
+; .text := place bytes in the text "space" of the output (default)
+; .data := place bytes in the data "space" of the output
+; you use .org (right afterwards) to specify start of area
+; .space := for variable name, "reserve" given bytes of data; used for label and address calculations
+; .checkpc := assert program counter isn't at given address
+; macros
+; Instead of being "textual replacement", the arguments are evaulated before passed to macro.
+; ONLY temporary labels, macros define their own scope on substitution.
+; Arguments accessible via _1, _2, _3, ...; must be bytes or words
+; .macro := starts a macro definition with the given name
+; .macend := ends the macro definition
+; .invoke := calls a macro by the given name and arguments
+; synonymous with `macro_name
+
+.outfile "hello.prg"
+
+;== Preloader ==;
+
+; The starting address is defined by the value (label or alias) "main".
+; Since in basic we need to provide a decimal string, but we store a decimal value, we need
+; to do this complicated convertion.
+.alias main_addr_ch1 '0 + main / 10000 ; 65535 -> 6
+.alias main_addr_ch2 '0 + [main - main / 10000 * 10000] / 1000 ; 65535 -> 65535 - 60000 = 5535 / 1000 = 5
+.alias main_addr_ch3 '0 + [main - main / 1000 * 1000] / 100 ; 65535 -> 65535 - 65000 = 535 / 100 = 5
+.alias main_addr_ch4 '0 + [main - main / 100 * 100] / 10 ; 65535 -> 65535 - 65500 = 35 / 10 = 3
+.alias main_addr_ch5 '0 + main - main / 10 * 10 ; 65535 -> 65535 - 65530 = 5
+
+.word $0801 ; Header -- addr 2049
+.org $0801 ; Program Counter
+
+.scope
+ .word _eop ; Next line addr (I guess it is used to tell how long the line is?)
+ .word 10 ; 10 (line of BASIC program)
+ .byte $9e," " ; SYS 2064 (opcode for SYS and null terminated string)
+ .byte main_addr_ch1,main_addr_ch2,main_addr_ch3,main_addr_ch4,main_addr_ch5,0
+_eop: .word 0 ; End of program
+.scend
+
+;== Program ==;
+
+; KERNAL
+.alias putchar $FFD2
+
+; PROGRAM
+.macro print
+ ldx #0 ; x = 0
+ _loop: lda _1, x ; while (_1[x] != 0) {
+ beq _done
+ jsr putchar ; putchar(_1[x])
+ inx ; ++x
+ jmp _loop ; }
+ _done:
+.macend
+
+main: `print hello
+ rts
+
+hello: .byte "HELLO, WORLD!", 0