Here is a simple "Hello world!" written in my N language:
func main () int n = 1; int ret = 0; int x; print "Hello world!"; printn n; x = 2 * 2 << 2; x = ++; print x; printn n; exit ret; funcend
The math expression is calculated from left to right. And x = ++; would be x++; in C. Here is the assembly output of the N compiler:
ston; lab main; int n@main; push_i 1, L0; pull_i L0, n@main; int ret@main; push_i 0, L1; pull_i L1, ret@main; int x@main; push_s "Hello world!", S0; print_s S0; print_n L0; push_i 2, L2; mul_l L2, L2, L2; smul_l L2, L2, L2; pull_i L2, x@main; inc_l L2; pull_i L2, x@main; print_l L2; print_n L0; exit L1;The first line ston; switches the stack on. It's the function call stack. The push opcodes "push" constants or variables into registers. A pull opcode "pulls" register content into variables. In this example every variable of the function main gets the @main tacked on, to mark them as the main function variables.
The next example shows a simple for loop:
func main () int x; int f; int max = 10; int null = 0; int one = 1; #NESTED_CODE_GLOBAL_OFF; x = 1; for; print "Hello world!"; printn one; x = ++; f = x <= max; next f; exit null; funcend
The index range check is done by "f = x <= max;". The next checks if the expression in f is true, as long as it's true the loop continues.
If we take a look at the assembly code generated by the N compiler. Then we see that the generated code (-O2 optimization on) looks like this:
ston; lab main; int x@main; int f@main; int max@main; push_i 10, L0; pull_i L0, max@main; int null@main; push_i 0, L1; pull_i L1, null@main; int one@main; push_i 1, L2; pull_i L2, one@main; push_i 1, L3; pull_i L3, x@main; lab for_0; push_s "Hello world!", S0; print_s S0; print_n L2; inc_lseq_jmp_l L3, L0, for_0; exit L1;
The three for loop control lines are now a simple "inc_lseq_jmp_l L3, L5, for_0;". The L3 register is the "x" counter variable and the L0 register the "max" variable. So one opcode makes the work of three.