Everything from SyrPython and icktoofay, plus ...
Four instructions:
mov ah, 0x0e ;
mov al, [si] ;
cmp al, 0 ; check for null terminator
jz print_end ; stop printing
I would suggest:
mov ax,0x0e00 ; clears al
or al,[si] ; sets Z accordingly (if [si] is 0)
jz print_end ; stop printing
If I understand correctly (I used another assembler back in the days. Borland I think)
newinput: mov bx, mem ; set register b to memory start
add bx, word 2 ; increment by size of memory ptr
You move the offset of mem in bx. Then why not do
newinput: mov bx, mem + 2
Like you do elsewhere ?
It seems you never use DX nor DI (and you still will not if you use the well suggested lodsb, that you may or may not use with rep, depending on your future direction)
You have 8 registers Ax Cx Dx Bx SP BP SI DI SP, you will mostly not play with SI and DI are usually used for offsets (what you use BX for) AX is your main register CX is mostly used for counting (and shifts) DX is for other data BP is mostly used for stack frame, which you don't care about so, another free one.
For using xor reg,reg, this is indeed a preferred way, with the only downside that it affects the flags so you cannot very well use it while pipelining your code (putting the operations in such order they are executed in parallel)
ie:
cmp bx, 2 <- U
mov ax, 1 <- V
je label
cmp will be executed in pipeline U, and the mov in pipeline V because it does not affect the cmp operation.
But this may be a bit over the top for a loader.