% latex generated by the script '/home/project-web/bumble/cgi-bin/text2latex.cgi' 
% with help from the sed script 'text2latex.sed' 
% query string: 'books/osdev/osdev-book' 
% sFileName= 'osdev-book' 
% sDirName = 'books/osdev' 
% sName = '' -->
% latex by http://bumble.sourceforge.net/scripts/text2latex.sed 
% this script is a work in progress 
\documentclass[11pt]{article} 
\usepackage[hmargin=2cm, vmargin=2cm]{geometry} 
\usepackage{url} 
\usepackage{hyperref} 
\begin{document}
 
 
 \\&\\& Developing an Operating System 

This is a booklet about the process of developing an
operating system. First a 512 byte bootloader is created
which performs a simple function such as printing a message
or getting a key from the keyboard, then that bootloader is
used to load and execute a larger os 'kernel'. Really, the
snippets here, just contain information about writing simple
'bootable' programs for x86 computers. The source code is in
x86 assembler and was inspired by the Mikeos code


\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents main bios interrupt functions
 .. int 13h, disk access (via sectors, cylinders etc)
 ..

GETTING HELP

 wiki.osdev.org is a good site
 
THE BOOT PROCESS

 The computer powers on and starts executing the bios. The
 bios then looks for a bootable sector (512 bytes) on a
 suitable 'boot medium'. That could be a old floppy disk (in
 days gone by) or now a USB memory stick, CD, or hard-drive.
 The boot medium is supposed to contain a 'boot signature'
 which is just a couple of bytes with specific numbers in
 them. This is to ensure that the computer doesnt attempt to
 boot something which is not supposed to be booted.

 \url{http://board.flatassembler.net/topic.php?p=124387}
 interesting information about booting from usb by mike gonta


* boot code with disk geometry etc
-----------------

 ; Original code by Mike Gonta, Public Domain 2011

 LOAD\\_ADDRESS equ 600h

 use16
 org 7C00h
 jmp start
 nop
 db ' '
 dw 512 ; bytes per sector
 db 1 ; sectors per cluster
 dw 36 ; reserved sector count
 db 2 ; number of FATs
 dw 16*14 ; root directory entries
 dw 18*2*80 ; sector count
 db 0F0h ; media byte
 dw 9 ; sectors per fat
 dw 18 ; sectors per track
 dw 2 ; number of heads
 dd 0 ; hidden sector count
 dd 0 ; number of sectors huge
 db 0 ; drive number
 db 0 ; reserved
 db 29h ; signature
 dd 0 ; volume ID
 db ' ' ; volume label
 db 'FAT12 ' ; file system type
 
 start:
 xor ax, ax
 mov ds, ax
 mov es, ax
 mov ss, ax
 mov sp, 7C00h

 mov bx, LOAD\\_ADDRESS
 mov cx, 2 ; track 0, sector 2
 xor dh, dh ; side 0
 mov ax, 223h ; read 35 sectors
 test dl, dl
 jne .1
 mov ax, 211h ; read 17 sectors
 call read
 jc exit
 add bx, 512*17
 mov cx, 1 ; track 0, sector 1
 mov dh, 1 ; side 1
 mov ax, 212h ; read 18 sectors

 .1:
 call read
 jc exit

 jmp 0:LOAD\\_ADDRESS
 read:
 mov bp, 2 ; 3 tries
 .1:
 push ax
 int 13h
 jnc .2
 sub bp, 1
 jc .2
 xor ax, ax
 int 13h
 pop ax
 jmp .1
 .2:
 pop bp
 ret

 print:
 mov ah, 0Eh
 xor bh, bh
 .1:
 mov al, [si]
 lea si, [si+1]
 test al, al
 je .2
 int 10h
 jmp .1
 .2:
 ret

 exit:
 mov si, boot\\_drive\\_read\\_error
 call print
 mov ah, 10h
 int 16h
 int 19h

 boot\\_drive\\_read\\_error:
 db 'Boot drive read error!', 13, 10
 db 'Press any key to restart.', 13, 10, 0
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h

 use16
 org LOAD\\_ADDRESS
 stage2:
 times 512*34 db 90h
 mov si, message
 call stage2\\_print
 mov ah, 10h
 int 16h
 int 19h

 stage2\\_print:
 mov ah, 0Eh
 xor bh, bh
 .1:
 mov al, [si]
 lea si, [si+1]
 test al, al
 je .2
 int 10h
 jmp .1
 .2:
 ret

 message:
 db 'USB booting is easy', 13, 10
 db 'Press any key to restart.', 13, 10, 0 
 times (512*35)-(\\$-\\$\\$) db 0

 fat1: ; empty FAT12 file system
 db 0F0h, 0FFh, 0FFh
 times 512*9-(\\$-fat1) db 0
 fat2:
 db 0F0h, 0FFh, 0FFh
 times 512*9-(\\$-fat2) db 0
 root:
 times 512*14-(\\$-root) db 0

,,,

STEP BY STEP

 This section describes, step by step how to create a bootable
 x86 usb key

 * install the correct programs
 >> sudo apt-get install qemu-system-i386 nasm mkdosfs ... 

 * make a new floppy image called 'os.flp'
 >> mkdosfs -C os.flp 1440

 The above line will not overwrite and existing file.
 Another way is to copy an existing floppy disk image.

 * compile the assembler source into a flat binary executable
 >> nasm -f bin -o first.bin first.asm
 >> nasm -o first.bin first.asm \\#\\#(probably the same)

 The 'bin' format is the default for the nasm assembler.

 * insert the compiled kernel 'first.bin' into the floppy image
 >> dd status=noxfer conv=notrunc if=first.bin of=os.flp

 * boot the operating system in the qemu virtual machine
 >> qemu -fda os.flp
 >> qemu-system-i386 -fda os.flp
 
 * create an iso file which can be burnt to a cd in the 'cdiso' folder
 >> mkisofs -o myfirst.iso -b myfirst.flp cdiso/

 Use 'df' or 'dmesg' to find out the device name of a usb key which
 you have inserted eg '/dev/sdc'

 * unmount the usb key
 >> umount \texttt{/dev/sdc}

 WARNING: the following command will delete all previous data on
 the usb memory stick.

 When you execute the command below, the little light on the 
 usb memory stick should flash a few times, indicating that 
 data is being written to the stick.

 * write the new operating system to the boot sector of the usb key
 >> sudo dd if=os.flp of=/dev/sdc
 >> su; dd if=os.flp of=/dev/sdc \\#\\#(on a non debian system)

 Be Very, Very careful where you write the floppy image
 file to. If you write it to your hard-disk (for example \texttt{/dev/hda)} that is
 more or less the end of the data and operating system on that
 hard-disk.

 If the usb memory stick dev has a number, dont use the number, just
 the letters of the device name eg 'sdc1' becomes 'sdc' (remove 
 the number 1 from the name).

 The usb memory stick can now be used to boot the new operating system
 by changing the computer boot order in the bios. Eg press <esc> on an
 asus eee pc or the ibm key on a thinkpad

MEMORY ADDRESSES IN X86 READ MODE

 real mode addresses are 20 bits but are made up of a 16bit segment
 address, with a 16bit offset. Its not that complicated. The 
 segment address, which needs to be loaded into ds or another
 segment register with something
 like
 mov ax 07C0h
 mov ds ax
 is really the address 07C00h. That means that it is a 20 bit
 address that can only address 1 megabyte of memory, no more. Hence
 the one of the needs for protected mode...

SIMPLE BOOT PROGRAM 

 Apparently the boot sector from a floppy (or usb) or hard-disk
 is always loaded to the physical memory location 07C00h which
 corresponds to the 'segment' (in real mode) 07C0h. The code 
 below seems to have a problem with register indirect jumps
 (code segment not set properly)

 * a simple example of a bootable program with stack and a function
 -----------------
 BITS 16

start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax ; this creates a 4K gap between stack and code 
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov si, text\\_string ; Put string position into SI
 call print\\_string ; Call our string-printing routine

 jmp \\$ ; Jump here - infinite loop!

 text\\_string db 'This is my cool new OS!!!', 0

 print\\_string: ; Routine: output string in SI to screen
 mov ah, 0Eh ; int 10h 'print char' function

 .repeat:
 lodsb ; Get character from string
 cmp al, 0
 je .done ; If char is zero, end of string
 int 10h ; Otherwise, print it
 jmp .repeat

 .done:
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

GOTCHAS ....

 Some bioses require a short jump (+/-128 bytes) followed
 by a 'nop' no operation instruction in order to execute 
 even though there isnt really a logical reason for this.

 * start the boot sector like this
 -------
 jmp short start
 nop
 start:
 ,,,

 * ax cant be used as an index register
 -------
 mov bx, [ax]
 ; error !! you cant get some value out of memory by using
 ; an index stored in ax, use bx or si or di instead
 mov ax, [bx] ; OK this works
 ,,,

BOOTLOADERS 

 https://github.com/cirosantilli/x86-bare-metal-examples
 good examples

 \url{http://stackoverflow.com/questions/22054578/how-to-run-a-program-without-an-operating-system/32483545\\#32483545}
 good knowledgable stuff about bootloading and io

 The initial bootable program may only be 512 bytes long since
 it must fit into 1 sector of the 'floppy'. This is limiting.
 The answer is to use these 512 bytes to load a bigger program
 into memory and jump to it. The code below shows how.
 
 Sector 2, head 0, cylinder 0, is the sector (512 bytes) 
 immediately following the sector occupied by the boot 
 program, which contains the code we want to execute.

 
 After Booting the DL register may contain the number of 
 the boot media. For example for a usb memory stick on my
 asus eee pc DL=128. this number
 should be saved for use with the read write functions of INT 13h

 * a simple working bootloader 
 -----------------
 BITS 16

 jmp start
 drive db 0 ; a variable to hold boot drive number
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov [drive], dl ; save the boot drive number
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax ; with a 4K gap between stack and code
 mov sp, 4096

 ; save the DL register or else dont modify it
 ; it contains the number of the boot medium (hard disk,
 ; usb memory stick etc)
 ; The 'floppy' Drive is NOT necesarily 0!!!

 reset: ; Reset the floppy drive
 mov ax, 0 ; 
 mov dl, [drive] ; the boot drive number (eg for usb 128)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ; es:bx determines where data loaded to
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 5 ; Load 5 sectors (only 1 used here)
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2 (sector 1 is the boot sector)
 mov dh, 0 ; Head=0
 mov dl, [drive] ; 
 int 13h ; Read!
 jc read ; ERROR => Try again

 jmp 1000h:0000 ; Jump to the loaded code 

 times 510-(\\$-\\$\\$) db 0 ; pad out the boot sector (512 bytes)
 dw 0AA55h ; end with standard boot signature

 ; this is important for memory offset calculations
 ; or compile next stage separately
 section stage2 vstart=0

 ; the code to be loaded and executed
 mov ah, 0x0A 
 mov al, '!'
 mov cx, 10
 int 10h

 hang: jmp hang
 ,,,
 

 The only difference in the code below is that the loaded
 program is contained in a separate file, which is handy
 for organisational reasons.

 
 * another way of writing the bootloader, almost identical 
 -----------------------------
 ; 3.ASM
 ; Load a program off the disk and jump to it

 ; Tells the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 start:
 push dx ; save the boot medium drive number
 ; Update the segment registers
 mov ax, cs
 mov ds, ax
 mov es, ax

 reset: ; Reset the floppy drive
 ; drive number in DL, unmodified since boot 
 mov ax, 0 ;
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ;
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 5 ; Load 5 sectors
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2
 mov dh, 0 ; Head=0
 ; drive number in DL, unmodified since boot 
 int 13h ; Read!

 jc read ; ERROR => Try again
 jmp 1000h:0000 ; Jump to the program

 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h

 This is a small loadable program.

 ; PROG.ASM
 mov ah, 9
 mov al, '='
 mov bx, 7
 mov cx, 10
 int 10h

 hang: jmp hang

 This program creates a disk image file that contains both
 the bootstrap and the small loadable program.

 ; IMAGE.ASM
 ; Disk image

 \\%include '3.asm'
 \\%include 'prog.asm' 
 ,,,

 The code below doesnt modify the DL register which contains
 the drive number of the boot medium immediately after boot.
 (the bios places it there). It would be better and safer
 to save DL for use with the int 13h read/write functions

 * a boot loader which shows what its up to
 -----------------
 BITS 16
 jmp start
 \\%include 'prints.asm'
 \\%include 'printi8.asm'
 m.reset db 'resetting floppy',13,10,0
 m.read db 'reading sector 2 of floppy',13,10,0
 m.dlstate db 'dl is ',0
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov si, m.dlstate
 call prints
 mov bl, 10
 mov al, dl
 call printi8

 mov cx, 4 ; try to reset drive 4 times
 .reset: ; Reset the floppy drive
 mov si, m.reset
 call prints
 mov ax, 0 ;
 ;mov dl, 0 ; Drive=0 (=A), no! use the DL value after boot
 int 13h 
 jnc .startread
 loop .reset ; on error (carry flag) reset again 3 times
 .startread:
 mov cx, 4 ; try to read 4 times
 .read:
 mov si, m.read
 call prints
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ; es:bx determines where data loaded to
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 5 ; Load 5 sectors (only 1 used here)
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2 (sector 1 is the boot sector)
 mov dh, 0 ; Head=0
 ;mov dl, 0 ; Drive=0, 'floppy' (or usb memory stick)
 int 13h ; Read!
 jnc .done
 loop .read ; on error (carry flag) try again 3 times
 .done: 
 jmp 1000h:0000 ; Jump to the loaded code 

 jmp \\$
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h

 ; the code to be loaded and executed
 jmp start2
 m.loaded db 'loaded data!',13,10,0
 start2:
 mov ah, 0x0A 
 mov al, '!'
 mov cx, 10
 int 10h

 hang: jmp hang

 ,,,


 ; boot1.asm stand alone program for floppy boot sector
 ; Compiled using nasm -f bin boot1.asm
 ; Written to floppy with dd if=boot1 of=/dev/fd0

MULTIBOOT AND GRUB ....

 Multiboot is a file format invented by grub to overcome
 limitations of master boot record mbr format. It allows
 booting a file from the file system, and booting several
 oses on the one computer.

 This is worth investigating to allow the toy.os to co-exist
 peacefully with other systems.

REBOOTING ....

 * reboot the computer by jumping to FFFF:0
 ------------------

 ; Boot record is loaded at 0000:7C00 ie CS==0 \\& IP==7c00
 org 7c00h
 
 lea si,[msg] ; load message address into SI register:
 mov ah,0eh
 print: 
 mov al,[si] 
 cmp al,0 
 jz done ; zero byte at end of string
 int 10h ; write character to screen. 
 inc si 
 jmp print

 done: 
 mov ah,0 ; wait for any key:
 int 16h ; waits for key press

 ; store magic value at 0040h:0072h to reboot:
 ; 0000h - cold boot.
 ; 1234h - warm boot.
 mov ax,0040h
 mov ds,ax
 mov word[0072h],0000h ; cold boot.
 jmp 0ffffh:0000h ; reboot!

 msg db 'welcome, i have control of the computer.',13,10
 db 'press any key to reboot.',13,10
 db '(after removing the floppy)',13,10,0

 ,,,

 * reboot the computer, but this may lock up the computer.
 >> int 19h

 * reboot the computer after a user keypress with int 19h, may lock!
 -------------
 mov ah, 0 ; x86 bios wait for keypress function
 int 16h
 mov ah, 0eH ; echo the key just pressed 
 int 10H
 int 19h ; reboot the computer

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; PC boot signature
 ,,,

SEGMENTS

 data in segments can be accessed with the syntax ss:bx ss:cx etc
 The if no segment is specified then all mov, lodsb etc are relative
 to ds - the data segment.

 * move the low byte on top of stack into al 
 ------------
 mov bx, sp
 mov al, [ss:bx]
 ,,,
 
STACK SEGMENT ....

 The stack segment register is used to calculate offsets
 into the stack used for PUSH and POP instructions. It
 appears to be automatically initialized, but we can 
 initialize it explicitly if we need a big stack etc.


 * access data in the ss stack segment
 ------------
 BITS 16
 [ORG 0]

 cr equ 13 ; carriage return
 lf equ 10 ; form feed 

 jmp 07C0h:start ; Goto segment 07C0
 
 ; print low byte character on top of stack
 ; not including the fn return pointer
 test:
 dw 0
 db 4, 'test'
 test.x:
 mov bx, sp
 mov al, [ss:bx+2]
 mov ah, 0eH 
 int 10H
 ret

 start:

 mov ax, cs ; make data segment and es same as code segment
 mov ds, ax
 mov es, ax
 
 push '*' 
 call test.x 
 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of MBR boot sector with 0s
 dw 0xAA55 ; The standard MBR boot signature
 ,,,
 ,,,,

DATA SEGMENT ....

 The DS or data segment register needs to be initialized
 before accessing variable with [var] since the offset of 
 these variables are calculated relative to value in the 
 DS register.

 The following 2 lines are sufficient. But I am not sure why
 or if the magic number 0x07C0 always works.

 * Initialize the data segment register DS
 ----------
 mov ax, 07C0h 
 mov ds, ax ; load DS with correct value 
 ,,,

 * Error!! prints rubbish not 1st and 2nd char of 'message' 
 ------------
 jmp start
 message db 'hello!'
 start:
 mov ah, 0eh
 mov al, [message] ;! DS hasnt been initialized
 int 10h ; will display garbage
 mov al, [message+1] ; same...
 int 10h
 hang: jmp hang
 ,,,

 * Correct! print the first two characters of a string
 ------------
 jmp start
 message db 'hello!'
 start:
 mov ax, 07C0h ; Initialize data segment DS register
 mov ds, ax ; load DS with correct value 
 mov ah, 0eh ; bios teletype function
 mov al, [message] ; first char of 'message' 
 int 10h ; invoke bios 
 mov al, [message+1] ; 2nd char of 'message'
 int 10h
 hang: jmp hang
 ,,,

 * also works in qemu with no boot signature 
 ------------
 mov ax, 07C0h ; Initialize data segment DS register
 mov ds, ax ; load DS with correct value 
 mov ah, 0eh ; bios teletype function
 mov al, [message] ; first char of 'message' 
 int 10h ; invoke bios 
 mov al, [message+1] ; 2nd char of 'message'
 int 10h 

 hang: jmp hang
 message db 'hello!'
 ,,,

EXTENDED READ WRITE FUNCTIONS

 For memory addresses outside of the range use extended
 functions with a dap data structure.

 \url{http://forum.osdev.org/viewtopic.php?f=13\\&t=27510}
 good posts about this topic

 Use INT 13h with AH=42h (read) AH=43h (write), extended functions
 use with a DAP, a datastructure

MOVING DATA


\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents data moving instructions
 .. mov - mov data around
 .. xchg - exchange the contents of 2 registers/memory
 ..

POINTERS AND DATA ....

 We can use the BX register as a pointer to data or and index
 Also, SI and DI

 * get data from a pointer
 >> mov bx, [bx]

 This is like 
 int i = *p; in the C language

MOV ...

 The 'mov' x86 instruction is perhaps the simplest and
 most fundamental instructions

 The syntax for moving a register into memory doesnt seem logical
 to me, ... it is "mov [buffer], ax"

 * when moving to and from memory specify a data size
 >> mov byte al, [foo]

 * when moving to and from memory specify a data size
 >> mov word [buffer], 0x0000 
 
 * mov from register to memory buffer
 --------
 result dw 0x0000 ; assign memory
 sub dx, dx ; set dx = 0
 add dx, 10
 mov [result], dx ; store result from register DX
 ,,,

XCHG ...

 This instruction is 1 clock cycle and fewer bytes than
 mov so more desirable in some circumstances.

STRINGS AND TEXT

 A 'string' in this context is just a series of bytes, words
 or double words which exist is contiguous memory locations.
 The bytes may represent characters in some human language, or
 they may not. Its up to you.

 x86 Assembly language has special instructions for dealing with
 strings such as movs, movsb etc. But each instruction only deals
 with one byte, word etc at a time (unless you combine these
 instructions with a 'rep' instruction)

STRING INSTRUCTIONS ....
 

\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents summary
 .. cmpsb - compare bytes from 2 strings (in DS:SI and ES:DI)
 .. cmpsw - compare double bytes from 2 strings (DS:SI and ES:DI)
 .. lodsb - load a byte from a string in AL
 .. lodsw - load 2 bytes from a string in to AX
 .. lodsd - load 4 bytes from a string into EAX
 ..


 * load a byte character from a string in AL and update SI
 >> lodsb

STOS ....

 This is the "store a string" instruction and includes 
 stosb, store a byte, stosw, store a word etc. Need to 
 initializefk

 * initialise an array with -1
 -----------
 jmp start
 array resw 100
 start:
 mov ax, cs
 mov es, ax ; stosw uses es extended segment
 mov ecx, 100
 mov edi, array
 mov ax, -1
 cld ; clear direction flag, ie go forward not backward
 rep stosw
 here: jmp here
 ,,,

 
 * convert a string to lowercase without changing blank characters
 -----------
 jmp start
 string.a: db 'HeLLo'
 string.b: db ' '
 stringlength: dw 4 
 start:
 mov ax, cs
 mov ds, ax ; must initialise ds no?
 mov es, ax ; must initialise es no?
 mov ecx, stringlength 
 mov esi, string.a 
 mov edi, string.b 
 cld ; clear direction flag, ie go forward not backward
 .again
 lodsb
 or al, 20h
 stosb
 loop .again
 here: jmp here
 ,,,

PRINTING STRINGS ....

 Normally strings are 'printed' or displayed by loading the
 address of the first byte (or word) of a string into
 the SI register and then using 'lodsb' the load string byte
 instruction to get successive characters into the AL or AX
 register while incrementing the pointer in the SI register
 (lodsb does these things automatically).
 
 Another way to print a string is to write the string directly to 
 video memory (instead of using x86 bios int 0x10 functions)
 
 Does LODSB decrement the CX counter? No

 * print the first two characters of a string
 ------------
 jmp start
 message db 'hello!'
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov ah, 0eh ; print character function
 mov al, [message] ; first char of 'message' 
 int 10h
 mov al, [message+1]
 int 10h
 here: jmp here 
 ,,,

 * print the 1st three characters with lodsb 
 ------------
 jmp start
 message db 'hello!'
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax 
 cld ; set dir flag to forwards
 lea si, [message]
 mov cx, 3 ; loop count 3
 mov ah, 0eh ; print character function
 .again:
 lodsb ; get next char from message 
 int 10h
 loop .again
 here: jmp here 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The code below is working and demonstrates a number of important
 forth style ideas; A set of functions which are set within a 
 data structure (a linked list) with each function having its own
 header with function name and a link to previous function.
 Also, all parameters for the functions are passed on the stack.

 * forth function style printing 
 ------------
 jmp start
 message dw 9, 'AbcXyz\{*\}'
 ; a linked list of functions (forth style)

 ; -- dup just duplicates the top item on the stack
 dup: dw 0 ; 1st word has a zero link 
 db 3, 'dup' ; strings are 'counted' 
 dup.x:
 pop bx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push bx ; restore fn return address
 ret
 
 ; print takes its arguments on the stack (buffer address, char count)
 print:
 dw dup ; link to previous dictionary entry 
 db 5, 'print' 
 print.x:
 cld ; set dir flag to forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop ax ; address of buffer to print
 push bx ; restore return function call
 mov si, ax ; maybe should use "lea si, ax" but how?? 
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message 
 int 10h
 loop .again
 ret
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax 
 ;mov sp, ? ; what about the stack pointer?
 push message+2 ; address of string buffer (1st word is count)
 push 9 ; how many characters to print
 call print.x
 here: jmp here 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * print a zero terminated string with address in the SI register 
 -----------------
 BITS 16

 jmp start
 message db 'A function to print',13,10,0
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov si, message ; Put string position into SI
 call prints ; Call our string-printing routine

 hang: jmp hang ; Jump here - infinite loop!

 ;\\# prints
 ; output zero terminated string in SI to screen
 prints: 
 mov ah, 0Eh ; int 10h 'print char' function

 .again:
 lodsb ; Get next character from string
 cmp al, 0 ; Char == 0 ? 
 je .done ; If char is zero, end of string
 int 10h ; Otherwise, print it
 jmp .again

 .done:
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
VARIABLES OF STRING BUFFERS ....

 Another approach to strings is for a string to push
 the address of its buffer onto the stack. This can then
 be manipulated and displayed by other functions....

 * a string buffer which places its address on the stack 
 -----------------
 BITS 16

 jmp start
 message.s db 'A function to print',13,10,0 
 message: 
 pop bx ; get return address off stack
 push message.s ; put the address of the message on the stack
 push bx ; restore return address
 ret
 puts:
 pop bx ; the 'puts' procedure return address
 pop si ; the address of zero ended string to print 
 push bx ; restore the return address to the stack
 mov ah, 0eH ; bios teletype
 .again:
 lodsb ; Get character from string
 cmp al, 0
 je .done ; If char is zero, end of string
 int 10h ; Otherwise, print it
 jmp .again
 .done:
 ret

 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 call message ; Put string address onto stack
 call puts ; print the message on the stack 
 call message ; Put string address onto stack
 call puts ; print the message on the stack 

 hang: jmp hang ; Jump here - an infinite loop

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 


ZERO TERMINATED STRINGS ....

 A zero terminated string simple has a byte with 0 zero in
 it at the end of the characters stored in memory. This is
 the system used by the C language.

COUNTED STRINGS ....

 One method of storing a string is to include the count of 
 the number of characters in a string next to the string where
 it is stored in memory. This system is used in the old 
 'forth' language and in modern languages where strings are 
 stored as objects.

 * store the length (in byte characters) after the string
 --------
 message db 'abcdefghijklmnop'
 count dw \\$-message
 ,,,

 * print a counted string with lodsb 
 -----------------
 BITS 16
 jmp start
 message db 'Counted String'
 count dw 14 
 start:
 mov ax, 07C0h ; set the data segment 
 mov ds, ax 
 mov si, message ; Put string position into SI
 mov cx, [count] ; how many chars to print
 mov ah, 0Eh ; int 10h 'print char' function
 .again:
 lodsb ; Get character from string into AL
 int 10h ; 
 loop .again ; loop while CX > 0

 hang: jmp hang ; Jump here - infinite loop!

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The code below is very similar by only uses 1 byte for the 
 count (thus limiting the string length to 255 characters) and
 also has the count preceding the string. These type of 
 counted strings are what are used in the Forth language

 * print a preceding counted string with lodsb 
 -----------------
 BITS 16
 jmp start
 message db 16,'Counted String!!'
 start:
 mov ax, 07C0h ; set the data segment 
 mov ds, ax 

 cld ; move forward through message
 sub cx, cx ; set CX = 0
 mov si, message ; Put start of string position into SI
 lodsb ; get [SI] into AL; increment SI
 mov cl, al ; cl now contains the count
 mov ah, 0Eh ; bios int 10h 'print char' function
 .again:
 lodsb ; Get character from string into AL
 int 10h ; invoke bios 
 loop .again ; loop while CX > 0

 here: jmp here ; Jump here - infinite loop!

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * print a string with an automatic preceding count
 -----------------
 BITS 16
 jmp start
 count dw message.end - message
 message db 'abcdefghijklmnopqrstuvwxyz'
 message.end 

 start:
 mov ax, 07C0h ; set the data segment 
 mov ds, ax 
 mov si, message ; Put string position into SI
 mov cx, [count] ; how many chars to print
 mov ah, 0Eh ; int 10h 'print char' function
 .again:
 lodsb ; Get character from string into AL
 int 10h ; 
 loop .again ; loop while CX > 0

 hang: jmp hang ; Jump here - infinite loop!

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


 The code below prints a counted string, where the count
 is calculated by the assembler and is located in the 2 bytes
 just before the string itself in memory. This system allows
 the use of only one label (instead of one for the message and
 one for the count)

 * a counted string with only one label 
 -----------------
 BITS 16
 jmp start
 message dw message.end-\\$-2
 db 'abcdefghijklmnopqrstuvwxyz'
 message.end 

 start:
 mov ax, 07C0h ; set the data segment 
 mov ds, ax 

 mov si, message+2 ; Put string position into SI (after count)
 mov cx, [message] ; how many chars to print (message length)
 mov ah, 0Eh ; int 10h 'print char' function
 .again:
 lodsb ; Get character from string into AL
 int 10h ; x86 bios interrupt, do it! 
 loop .again ; loop while CX > 0

 hang: jmp hang ; Jump here - infinite loop!

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


CMPS COMPARE STRING ....

 This includes cmpsb, cmpsw, cmpsw. These instructions 
 compare [ds:si] == [es:di] and set a flag if true or false
 They also advance the 2 pointers by one byte or word etc.
 This means the cmps instructions can be used in a loop or 
 with rep to compare an entire string.

 The instructions can be used with repe repne etc 
 The std, set direction flag instruction and cld, clear direction
 flag determine which way the ds:si and es:di pointers advance
 after the compare instruction

 * initialise ds and es registers and si and di
 ------
 aaa db 'x'
 bbb db 'y'
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov es, ax ; ! must initialise for cmpsb
 lea si, [a]
 ,,,

 We can also initialise ds and es with lds and les

SCAS SCAN STRING ....

 The scan string instructions are used to locate a particular
 'character' (or value) with a string. It uses the ES:DI 
 register pair (not the DS:SI pair).

 This instruction has the variants scasb, scasw, scasd
 
 * scan a string for a particular character
 -----------
 BITS 16

 jmp start
 message db 'abcdefghijklmnop'
 count dw \\$-message
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h 
 mov ds, ax ; data segment = code segment 
 mov es, ax ; extended segment = code segment

 mov di, message
 ; or as below
 ;les di, [message]
 cld ; search forward (set direction flag = 0) 
 mov al, 'p' ; the character to scan for 
 mov cx, [count] ; search within string length 
 repne scasb
 je .found
 .notfound:
 mov al, 'N'
 mov ah, 0eH ; bios 'teletype' function
 int 10H ; bios output interrupt 
 jmp hang 
 .found:
 dec di ; If found, DI points 1 byte further, as with 'cmps'
 mov ax, [di] ; print the character found
 mov ah, 0eH ; teletype AL bios function
 int 10H
 mov al, 'Y'
 int 10H

 hang: jmp hang ; loop foever 
 times 510-(\\$-\\$\\$) db 0 ; padding
 dw 0AA55h ; boot signature
 ,,,

 
 The code below should be modified to skip all leading 
 white space. This could be done with 'repe scasb' with al=' '
 
 * find the length of the 1st word of a sentence
 -----------
 BITS 16

 jmp start
 message db 'tree one 2 three'
 count dw \\$-message
 start:
 mov ax, 07C0h 
 mov ds, ax ; data segment = code segment 
 mov es, ax ; extended segment = code segment

 mov di, message ; or ... les di, [message]
 cld ; search forward (set direction flag = 0) 
 mov al, ' ' ; scan for next space 
 mov cx, [count] ; search within string length 
 repne scasb
 je .found
 .notfound:
 mov al, 'N'
 mov ah, 0eH ; bios 'teletype' function
 int 10H ; bios output interrupt 
 jmp hang 
 .found:
 dec di ; DI points 1 byte further, as with 'cmps'
 mov ax, [count] ; how many characters scanned
 sub ax, cx ; or do DI - message
 dec ax ; cx is 1 too small
 add ax, '0' ; convert count digit to ascii
 mov ah, 0eH ; teletype AL bios function
 int 10H
 mov al, 'Y'
 int 10H

 hang: jmp hang ; loop foever 
 times 510-(\\$-\\$\\$) db 0 ; padding
 dw 0AA55h ; boot signature
 ,,,


NULL TERMINATED STRINGS ....

 The 'null' or zero terminated string is a series of (usually)
 ascii characters (traditionally bytes) with the last bytes
 being the value zero 0. This is the standard C programming language
 string representation and therefore is pretty common. The 
 advantage is that string manipulation functions dont have to 
 know how long the strings are before doing something with them.

 * define a string with a unix newline
 >> prompt db "ENTER OPERAND:", 13, 0

 * define a string with a dos newline
 >> prompt db "ENTER OPERAND:", 13, 10, 0

 * define a null terminated string
 >> message db 'This is my cool new OS!!!', 0

COMPARING STRINGS ....
 
 Below we use 'dw' for the count of the two words because
 the count is loaded into the CX loop register (and so
 has to be a word, not a byte)
 
 The code below could be better written with 'cmpsb'
 ie "compare string byte". 

 Use 'std' set direction flag to scan through a string backwards

 * eg with cmpsb
 ------
 les edi, string.b ; loads edi and es segment (??)
 lds esi, string.a ; loads esi and ds data segment (??)
 mov ecx, stringlength(string.a)
 cld ; clear direction flag, search forward
 repe cmpsb
 je .same
 ja .above ; if string.a is greater than string.b
 ,,, 

 Must use 'lea' when initialising si and di registers. Also 
 must initialise es register, since 'cmpsb' compares 
 ds:si with es:di

 * compare with cmpsb 
 ---------
 jmp start
 word.a db 'an elephantaa',0
 length dw \\$-word.a
 word.b db 'an elephanTaa',0
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov es, ax ; ! must initialise for cmpsb
 mov cx, length ; this length includes the null termination
 lea si, [word.a]
 lea di, [word.b] 
 cld ; search forwards (clear direction flag)
 repe cmpsb
 ; below is the more verbose version of repe cmpsb
 ;.again:
 ; cmpsb 
 ; loope .again

 dec si ; point to last different letter
 dec di
 mov ax, [si] ; get character into al register
 mov ah, 0eH ; print al
 int 10H
 here: jmp here
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,


 A forth style compare string function. The function is in a 
 linked list dictionary. Parameters are passed on the stack.
 (addr of 1st counted string, addr of 2nd counted string - returns 0/1/2)

 * compare 2 counted strings, push 0 on stack if equal 
 ---------
 jmp start
 ; below seems better if indirect calls needed, eg call bx, call [bx]
 ; jmp 07C0h:start ; Goto segment 07C0

 word.a dw 8, 'abcABCaB'
 word.b dw 8, 'abcABCaB'

 ; print T if the top element of stack is 0
 ; print F if top element is <> 0
 truefalse:
 dw 0
 dw 9, 'truefalse'
 truefalse.x:
 pop dx ; juggle return fn ip
 pop ax ; get parameter
 push dx ; restore return fn ip
 cmp ax, 0
 je .true
 mov ah, 0Eh ; int 10h 'print char' function
 mov al, 'F'
 int 10h ; x86 bios interrupt, do it! 
 ret
 .true:
 mov ah, 0Eh ; int 10h 'print char' function
 mov al, 'T'
 int 10h ; x86 bios interrupt, do it! 
 ret

 compare:
 dw 0
 dw 8, 'compare'
 compare.x:
 pop dx ; juggle fn return ip
 pop ax ; 1st buffer address
 pop bx ; 2nd buffer address
 push dx ; restore fn ip
 sub cx, cx ; set cx:=0, not necessary here
 mov cx, [bx] ; how many characters to compare
 add cx, 2 ; we also have to compare the count bytes
 mov si, ax 
 mov di, bx
 cld ; search forwards (clear direction flag)
 repe cmpsb
 je .same
 ; ja .above ;
 pop dx ; juggle return ip
 push 1 ; return result on stack
 push dx
 ret
 .same: 
 pop dx ; juggle return ip
 push 0 ; return result on stack 
 push dx
 ret

 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov es, ax ; ! must initialise for cmpsb
 push word.a
 push word.b
 call compare.x
 call truefalse.x

 here: jmp here
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,


 The following code modifies the 'compare' function to traverse
 a linked list dictionary searching for an entry. If the entry is found 
 the function returns a pointer to the execution token of the word
 on the stack. If it is not found it returns 0 on top of the stack.
 This is a fairly standard forth word, and can be used to implement
 a very simple interactive command interpreter. The function 
 receives on the stack a pointer to a counted string with the name
 to search for and also a pointer to the top of the dictionary
 (last term)

 (stack: searchterm, where to start search -> 0 or *function)

 This is working... now we need to actually execute the found
 word. So we will write one that just prints a star

 !!! Gotcha, we have to use jmp 07C0h:start if we want indirect
 function calls (eg mov bx, fn; call bx) to work. I think this
 is because it sets up the code segment properly...

 * find an entry in a linked list dictionary, forth-style 
 ---------
 ; these dont seem needed in qemu
 BITS 16
 [ORG 0]

 ; !! jmp start, no doesnt work, why?

 jmp 07C0h:start ; Goto segment 07C0

 searchterm dw 4, 'star'
 last dw find
 buffer dw 0, ' '

 ; just print a newline
 crlf:
 dw 0
 dw 4, 'crlf'
 crlf.x:
 mov ah, 0eh ; bios type char function 
 mov al, 13 ; cr lf
 int 10h
 mov al, 10
 int 10h
 ret

 ; just to test
 hash:
 dw crlf,
 dw 4, 'hash'
 hash.x:
 mov ah, 0Eh ; just print a star with bios
 mov al, '\\#'
 int 10h ; x86 bios interrupt
 ret
 ; another fn for testing
 star:
 dw hash,
 dw 4, 'star'
 star.x:
 mov ah, 0Eh ; just print a star with bios
 mov al, '*'
 int 10h ; x86 bios interrupt
 ret

 ; get rid of top stack element
 drop:
 dw star,
 dw 4, 'drop'
 drop.x:
 pop ax
 pop bx
 push ax
 ret

 ; just print some message about word found or else '?'
 found:
 dw drop 
 dw 5, 'found'
 found.x:
 pop dx ; juggle return fn ip
 pop bx ; get parameter (0 or pointer to function)
 push dx ; restore return fn ip
 cmp bx, 0
 jne .foundit
 mov ah, 0Eh ; int 10h 'print char' function
 mov al, '?'
 int 10h ; x86 bios interrupt, do it! 
 ret
 .foundit:
 mov ah, 0Eh ; int 10h 'print char' function
 mov al, '!'
 int 10h ; x86 bios interrupt, do it! 
 add bx, 2 ; point bx to the function header count
 mov cx, [bx] ; get the function name count 
 mov al, cl ; print the count (for feedback)
 add al, '0' ; convert to ascii
 int 10h ; x86 bios interrupt, do it! 
 mov al, [bx+2] ; first letter of func name
 int 10h ; x86 bios interrupt, do it! 
 ret

 ; should rewrite this, use si for current word point and 
 ; di for search term pointer. Use byte word counts, not 2 bytes
 ; 
 find:
 dw found 
 dw 4, 'find'
 find.x:
 pop dx ; juggle fn return ip
 ; no, just pop into si and di, easier
 pop bx ; where to start searching (eg last entry in dict)
 pop ax ; counted string buffer to search for 
 push dx ; restore fn ip
 .again:
 sub cx, cx ; set cx:=0, not necessary here
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 mov cx, [si] ; the count of the search term
 add cx, 2 ; we also have to compare the count bytes
 mov di, ax ; the search term pointer
 cld ; search forwards (clear direction flag)
 repe cmpsb ; compare all characters for equality
 je .found
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 je .notfound ; no more to words search, so exit
 push ax ; save ax, the search term pointer
 mov ah, 0Eh ; print a dot on each unsuccessful search
 mov al, '.' ; for debugging
 ;int 10h ; x86 bios interrupt
 pop ax ; restore the search term pointer 
 jmp .again 
 .notfound:
 pop dx
 push 0 ; not found so return 0
 push dx
 ret
 .found: 
 pop dx ; juggle return ip
 push bx ; return result on stack 
 push dx
 ret
 
 ; execute a function given a pointer to its header on the stack
 exec:
 dw find
 dw 4, 'exec'
 exec.x:
 pop ax
 pop bx ; get pointer to function
 push ax ; preserve fn return pointer
 add bx, 2 ; point to name count
 mov cx, [bx] ; get the count
 add bx, 2 ; skip over count
 add bx, cx ; advance the pointer to the function

 ; !! not call [bx] thats a pointer to jumptable
 ; !!! call bx may change the stack (probably will) so we need 
 ; !!! to preserve the call return ip 
 pop word [returnexec] ; save return ip
 call bx ; call the fn pointed to by bx
 push word [returnexec] ; restore fn return ip
 ret
 ; a dodgy solution, but any register my
 returnexec dw 0


 ; get a line of input from the user 
 ; stack parameters: buffer address, max characters
 ; returns: buffer address. This would be called 'accept' in 
 ; a traditional forth system. We can leave the buffer address
 ; on the stack, which can be used by the calling proceedure.
 line: 
 dw exec ; link to next fn in dictionary
 dw 4, 'line' ; forth-style function header 
 line.x:
 pop bx ; juggle return pointer
 pop cx ; how many chars maximum to get 
 ; use pop di instead of pop ax, since stosb comes later
 pop ax ; where to copy chars
 push bx ; restore return pointer
 mov bx, ax ; save buffer address, unnecessary, leave on stack
 add ax, 2 ; first word of buffer is char count
 mov di, ax ; where stosb will put characters 
 ; could use 'lea di, [bx]' as well
 sub dx, dx ; simple char counter set dx:=0
 .again:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 cmp al, 13 ; was the key press an 'enter' 
 je .exit ; exit if enter pressed
 mov ah, 0eh ; echo the character
 int 10h
 stosb ; put the char into the buffer
 inc dx ; increment char counter
 loop .again
 .exit:
 ; pop di, then mov [di], dx
 mov [bx], dx ; store char count in buffer
 pop ax ; preserve fn return ip
 push bx ; return buffer address on stack
 ; Not Necessary !!! just leave it there
 push ax
 ret

 ; just the forth count word
 count:
 dw line
 db 5, 'count'
 count.h:
 pop dx ; preserve return fn pointer
 pop bx ; buffer address
 push dx ; leave buffer addr on stack
 xor ax, ax ; ax := 0
 mov al, [bx] ; get count into al
 inc bx
 pop dx ; juggle return ip
 push bx ; new buffer address
 push ax ; char count
 push dx ; fn return ip
 ret

 ; type takes its arguments on the stack (buffer address, char count)
 ; modify this to display char count and chars. So it gets
 ; a pointer to a counted string and displays both
 type:
 dw line ; link to previous dictionary entry 
 dw 5, 'type' 
 type.x:
 cld ; make lodsb step forward through chars
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop si ; address of buffer to print
 push bx ; restore return function call
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message in al
 int 10h ; print char with bios
 loop .again
 ret
 
 start:
 mov ax, cs ; initialize the data segment register DS
 mov ds, ax
 mov es, ax ; ! must initialise for cmpsb
 ;push searchterm ; what word to search for

 .again:
 push buffer ; where to store input from 'line'
 push 10 ; max number of chars
 call line.x
 ;push buffer+2
 ;push 6
 ;call type.x ; change how this type works (counted string)
 push buffer ; the search term
 push type ; top of dictionary
 call find.x ; find the name entered by user
 ;call found.x
 call crlf.x
 pop ax
 push ax 
 cmp ax, 0 ; if not found top of stack is 0
 je .again ; dont execute if zero, put this in exec
 call exec.x ; execute it!
 call crlf.x
 jmp .again

 here: jmp here
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,


 The code below is unnecessarily long. It should use cmpsb etc

 * compare 2 counted strings for equality
 ----------
 BITS 16
 jmp start
 word.a dw 18 
 db 'five hundred and 2'
 word.b dw 18 
 db 'five hundred and 1'

 start:
 mov ax, 07C0h ; set the data segment 
 mov ds, ax 

 mov ah, 0Eh ; int 10h 'print char' function
 mov si, word.a ; Put address of 1st byte of word.a into SI 
 mov di, word.b ; same, but for word.b in to DI 
 mov ax, [si] ; get word.a count into AL 
 mov bx, [di]
 cmp ax, bx ; see if 2 words have the same count
 jne different ; print message and terminate
 mov cx, ax ; put the loop count into cx (ch == 0)
 inc si ; point to first char of word.a
 inc di ; point to first char of word.b
 .again:
 lodsb ; Get character from word.a into AL
 mov bl, [di] ; get next char from word.b into BL
 inc di
 cmp al, bl ; see if the character is the same 
 jne different ; print message and terminate
 loop .again ; loop while CX > 0

 same: ; the words must be the same
 mov ah, 0Eh ; int 10h 'print char' function
 mov al, 'S'
 int 10h ; x86 bios interrupt, do it! 

 hang: jmp hang ; Jump here - infinite loop!

 different:
 mov ah, 0Eh ; int 10h 'print char' function
 mov al, 'D'
 int 10h ; x86 bios interrupt, do it! 
 jmp hang

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 Todo!
 * enter a string and check if it is in a dictionary
 -------------
 receive characters, count them, store them in a buffer
 then compare them to words in a dictionary 
 ,,,

COPYING STRINGS ....

 what is the difference between 'lea' and 'lds', mov etc?

 * copy a number of bytes from one destination to another
 -----------------
 mov cx,(number of bytes to move)
 lea di,(destination address)
 lea si,(source address)
 cld ; clear direction flag, copy forwards
 rep movsb
 ,,,

 * a complete copy example
 ---------
 jmp start
 word db 'an elephant',0
 length EQU \\$-word
 buffer resb 80
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov cx, length ; this length includes the null termination
 mov si, word
 mov di, buffer
 cld ; copy forwards (clear direction flag)
 rep movsb
 here: jmp here
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,

CONVERTING TO AND FROM STRINGS ....

 * convert from a digit to an ascii character by adding '0' or 48
 ------
 mov al, 9 
 add al, '0' 
 ,,,

 * convert a digit to hexadecimal using xlatb
 -----------
 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 hextable db "0123456789ABCDEF"
 start:
 ; Update the segment registers
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov al, 15
 mov ebx, hextable ; translation table
 xlatb ; replace al with hex digit
 mov ah, 0eH ; print al
 int 10H
 hang: jmp hang
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,

 The code below may be the most concise possible way to 
 print a number in assembly language.

 * print a 2 byte number in hexadecimal 
 -----------
 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0
 hextable db "0123456789ABCDEF"
 start:
 mov ax, cs ; cs is 07C0 after the far jump
 mov ds, ax ; point data segment -> code segment
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov dx, 0xFEDC ; the number to print
 mov cx, 4
 .again
 rol dx, 4
 mov al, dl
 and al, 0x0F
 xlatb ; replace al with hex digit
 int 10H
 loop .again
 hang: jmp hang
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,

 The code below is just a variation on the code above
 where the stack is used to pass the number to the function
 or proceedure. Hopefully this will allow us to reuse this
 proceedure in other code.

 We have to juggle the stack to get the parameter off without
 hurting the return address

 * print a 2 byte number but use the stack to pass number 
 -----------
 [ORG 0]
 jmp 07C0h:start ; Goto code segment 07C0
 start:
 mov ax, cs ; cs is 07C0 after the far jump
 mov ds, ax ; point data segment -> code segment

 ; doesnt seem necessary to initialize the stack
 ;add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 ;mov ss, ax ; initialise stack pointers
 ;mov sp, 4096 

 push 0xABCD ; the number to print
 call hexprint ; print the number 
 here: jmp here ; and the rest is silence

 hexprint.data:
 hextable db "0123456789ABCDEF"
 hexprint:
 pop bx ; get off the return call address
 pop dx ; retrieve the number to print
 push bx ; save the return call address
 mov ah, 0x0E ; x86 bios print char function
 mov bx, hextable ; translation table
 mov cx, 4
 .again:
 rol dx, 4
 mov al, dl
 and al, 0x0F
 xlatb ; replace al with hex digit
 int 10H
 loop .again
 ret
 times 510-(\\$-\\$\\$) db 0 ; pad to 512 bytes total
 dw 0AA55h ; standard x86 bootloader signature
 ,,,

 conversion to decimal display... divide by base (10) 
 convert remainder to ascii using xlatb. push to stack. 
 divide again by base, convert to ascii ... and so on
 until quotient is 0. Then pop the stack and display each 
 character.

 The code below seems to be working. To adapt for 16 bit 
 unsigned ints we need to use DX AX as dividend and BX 
 as base or divisor

 * convert an unsigned 8 bit number to ascii in any base 0 to 16
 -----------
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 number db 255 
 base db 16 

 start:
 ; Update the segment registers
 mov ax, cs
 mov ds, ax
 mov es, ax
 ;mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 
 mov al, [number] ; number/base
 mov bl, [base] ; bl is the divisor
 call printi8

 hang: jmp hang
 
 ;proc printi8 
 ; expects the 8 bit number to display in AL and 
 ; the base in BL register
 hextable db "0123456789ABCDEF"
 printi8:
 push cx
 push bx
 push ax
 sub cx, cx ; set counter = 0
 .again:
 sub ah, ah ; ah = 0, ax is the dividend
 div bl ; does ax/bl. remainder  ah
 push ax ; save remainder:quotient on the stack 
 inc cx ; increment the digit counter
 cmp al, 0 ; if the quotient != 0 do the next digit 
 jne .again ; loop while quotient > 0

 .print:
 pop ax ; get digit from the stack
 mov al, ah ; convert digit to ascii
 mov ebx, hextable ; translation table
 xlatb ; replace al with hex digit from table
 mov ah, 0eH ; print digit in al
 int 10H
 loop .print ; using cx the digit counter to loop 
 pop ax
 pop bx
 pop cx
 ret

 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,

LOGICAL AND BIT OPERATIONS

 and, or, not, xor, test

 Use the OR instruction to turn on 1 or more bits of a register
 Use the AND instruction to turn off 1 or more bits of a register 

OR INSTRUCTION ....

 * turn on the high bit of the bl register
 -----------
 mov bl, color
 or bl, 10000000b
 ,,,

 * cut and paste bits with 'or'
 -------
 and AL, 55H ; cut odd bits
 and BL, 0AAH ; cut even bits
 or AL, BL ; paste the registers together
 ,,,

XOR INSTRUCTION ....

 Toggles one or more bits. etc

 * initialize the AX register to zero
 >> xor AX, AX

 * toggle the last bit of the AX register
 >> xor AX, 1

 In the code the value 0A6h is the encryption 'key' (any 
 key may be used, or chosen by the user). The data can be
 unencrypted using the same function.

 * encrypt data with xor
 ---
 input db 'unencrypted'
 output db ' '
 ...
 cld 
 lea si, [input]
 lea di, [output]
 lodsb ; read a data byte (or character) into AL
 xor AL, 0A6H
 stosb ; write data byte from AL to output buffer
 ,,,

AND INSTRUCTION ....

TEST INSTRUCTION ....

 The test instruction can be used to test the value of 
 one or more bits of a register. This is similar to the AND
 instruction but the register value is not changed.

 The TEST instruction sets the Zero flag if the test is true.
 So we can use jz, je, loopz, loope for the true case
 and jnz, jne, loopnz, and loopne for the false case.

 TEST sets the flag register is an identical way to the 
 AND instruction. So, if the result of the AND instruction
 would be zero, then TEST will set the zero flag to 1.
 This can be a bit confusing!! Use jz or jnz with test

 * check if AL == 0
 >> test al, al 

 * jump if AL is odd 
 -------
 test AL, 1
 jz .odd
 ,,,

 * jump if the least OR most significant bits of AX are set
 -------
 test AL, 10000001b
 jz .exit
 ,,,

 We can only test if one of several bits are set, not
 if they are all set.

 * test if the most significant bit of bl is set 
 -----------
 jmp start
 start:
 mov bl, 0b10101011 ; pattern to display
 mov dl, 0b00000000 ; variable test pattern
 test bl, 0b10000000
 jz .unset 
 .set:
 mov ah, 0eH ; bios teletype function
 mov al, 'y' 
 int 10H ; do it
 jmp end
 .unset:
 mov ah, 0eH ; bios teletype function
 mov al, 'n' 
 int 10H ; do it
 end: jmp end
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 We can use the TEST instruction to see if a number is
 divisible by a power of 2 (eg 2,4,8,16...). 
 Use 2\\^n - 1 as the operand to TEST (1,3,7,15...) .

 No, I think this is wrong !!!

 * print a "!" if CX is divisible by 4 
 ---------------------------------
 start:
 mov cx, 9 
 .again:
 mov al, cl 
 add al, '0' ; convert digit to ascii
 mov ah, 0eH ; bios teletype function
 int 10H ; invoke bios
 test cl, 3 
 jnz .here
 mov al, '!' 
 mov ah, 0eH ; bios teletype function
 int 10H
 .here:
 loop .again
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * print a "!" if CX is divisible by 8
 ---------------------------------
 start:
 mov cx, 31 
 .again:
 mov al, 'o' ; the character to print
 mov ah, 0eH ; bios teletype function
 int 10H ; invoke bios
 test cx, 0x0007 
 jne .here
 mov al, '!' 
 mov ah, 0eH ; bios teletype function
 int 10H
 .here
 loop .again
 jmp \\$ ; an infinite loop
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

XOR INSTRUCTION ....

NOT INSTRUCTION ....

 * reverse every bit of a register
 >> not ESI

SHIFT INSTRUCTIONS ....

 Shift operations are useful for multiplying and (integer)
 dividing by powers of 2. eg 2, 4, 8, 16 etc This should
 be faster than using the MUL and DIV instructions

 The code below is more easily done with the ROR or ROL
 instructions.
 
 * encrypt data by swapping nibbles with shl and shr
 -----------
 ; al contains the byte to be encrypted
 mov AH, AL
 shl AL, 4 
 shr AH, 4
 or AL, AH
 ; al has encrypted byte
 ,,,

SHL ....

 shift left
 
 what does shift fill the empty bits with 0, 1 or indeterminate

SHR ....

 shift right

ROTATE INSTRUCTIONS ....


\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents rotate sumary
 rol - rotate left
 ror - rotate right
 rcl - rotate left through carry
 rcr - rotate right through carry.
 ,,,

 * encrypt a byte by swapping nibbles (4 bits in a byte)
 ----
 mov CL, 4
 ror AL, CL ; or rol AL, Cl (no difference)
 ,,,

DISPLAYING BIT PATTERNS ....

 
 strangely the following 3 lines are not 
 equivalent
 -------
 ;test bl, dl ; see if bit is set
 and bl, dl ; see if bit is set
 cmp bl, dl
 ,,,

 * display a bit pattern
 -----------
 block equ 0xFE ; ascii code for small block
 alpha equ 224 ; Greek letter alpha 
 jmp start
 start:
 mov ax, 07C0h ; Initialize data segment DS register
 mov ds, ax ; load DS with correct value 
 mov cx, 8 ; number of bits to display 
 mov dl, 0b10000000 ; test pattern
 mov bl, 0b10101010 ; pattern to display
 .again:
 mov ah, 0eH ; bios teletype
 test bl, dl ; see if bit is set
 jnz .fill
 mov al, '0' ; print zero
 int 10H ; do it
 jmp .ll
 .fill:
 mov al, block ; char to print 
 int 10H ; do it
 .ll:
 ror dl, 1 ; move the bit pattern
 loop .again ; go again
 here: jmp here 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,

PROCEEDURES

 Procedures are similar to jumps in that the IP instruction pointer
 is modified implicitly by the instruction. The 'call' and 'ret'
 instructions implement proceedures in x86 assembler. 

INDIRECT PROCEEDURES ....

 There are some gotchas with this syntax, read carefully!

 * simple indirect function call 
 ----
 jmp start
 hash:
 ; print a hash
 ret
 start:
 mov bx, start
 call bx

 ,,,

 * a pointer to a pointer 
 ----
 jmp start
 jumptable dw hash, bang
 hash:
 ; print a hash
 ret
 bang:
 ; print a bang
 ret
 start:
 mov bx, jumptable+2 
 call [bx]
 ; this calls the 'bang' function !!!
 ,,,

 * call proceedure located by a pointer 
 >> call [BX]

 Gotchas!!! If the code segment is not initialised
 call [bx] doesnt work!!!
 So do: jmp 07C0h:start ; Goto segment 07C0
 Or do: mov ax, 07c0h and mov cs, ax etc

 The following is a simple command interpreter. The next 
 step is to create a linked list dictionary with a function
 which searches through and executes. Another step is to 
 have some kind of self-referentialism, that is, so that the 
 user can look up what functions are available. 

 This self referentialism can be provided with a 'command name'
 which is just a counted string before the code to be executed.

 * a simple indirect procedure call with jump-table
 ---------
 BITS 16
 [ORG 0]
 alpha equ 224 ; Greek letter alpha 
 beta equ 225 ; Greek letter beta
 gamma equ 226 ; Greek letter gamma

 jmp 07C0h:start ; Goto segment 07C0
 jumptable dw aa,bb,cc
 aa:
 mov al, alpha ; letter to print 
 mov ah, 0eH ; bios teletype
 int 10H ; do it
 ret
 bb:
 mov al, beta 
 mov ah, 0eH ; bios teletype
 int 10H ; invoke bios 
 ret ; return from 'call'
 cc:
 mov al, gamma 
 mov ah, 0eH 
 int 10H
 ret
 start:
 mov ax, cs ; initialize the data segment register DS
 mov ds, ax
 .again:
 mov al, '>' ; print a prompt
 mov ah, 0eH ; bios teletype 
 int 10H
 mov ah, 0 ; bios wait for keypress function
 int 16h ; invoke bios
 cmp al, 'a' ; check for valid command (a-c)
 jb .again ; just print prompt if invalid
 cmp al, 'c'
 ja .again
 sub al, 'a' ; convert letter to a index into jump table
 sub bx, bx ; set bx:=0
 mov bl, al ; ax cant be used in effective addresses
 shl bl, 1 ; do bl:=bl*2 (since its a double-byte array)
 
 ; this below also would work
 ;mov bx, jumptable+2
 ;call [bx]
 ; and this too, its a gotcha
 ;mov bx, bb 
 ;call bx

 call [jumptable+bx] ; jump-table is word (2 byte) cells
 
 jmp .again ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

PARAMETERS FOR PROCEDURES ....

 We can pass parameters to procedures in x86 assembly in various ways.
 One is on the stack, another is in registers, or even in memory.
 If we pass parameters on the stack then we can 'juggle' the stack,
 since inside the procedure, the top item is the return address for 
 the procedure. Or we can use the stack pointer SP register to 
 get the value

 * basic stack juggling to pass a parameter
 -------
 mov ax, 0xABCD
 push ax
 call puts
 puts:
 pop bx ; the 'puts' procedure return address
 pop ax ; the parameter we want to use
 push bx ; restore the return address to the stack
 ; ... code to do something with parameter
 ret
 ,,,

 * return a parameter via the stack 
 -------
 call puts
 pop ax ; the parameter that was returned
 ...
 puts:
 ; ... code 
 pop bx ; the return address 
 push ax ; the parameter we want to return
 push bx ; restore the return address to the stack
 ret
 ,,,

 We can modify the code below to include a header in the procedure
 eg
 puts:
 db 5, 'print'
 puts.x:
 ... code

 This allows us to use the procedure interactively as well
 as programatically, for example, we can look up the function

 * print string, using stack to pass address of buffer 
 ---------
 BITS 16
 [ORG 0]
 cr equ 13 ; carriage return
 lf equ 13 ; carriage return

 jmp 07C0h:start ; Goto segment 07C0
 buffer db 'String in Buffer',13,10,0
 puts:
 pop bx ; the 'puts' procedure return address
 pop si ; the address of zero ended string to print 
 push bx ; restore the return address to the stack
 mov ah, 0eH ; bios teletype
 .again:
 lodsb ; Get character from string
 cmp al, 0
 je .done ; If char is zero, end of string
 int 10h ; Otherwise, print it
 jmp .again
 .done
 ret

 start:
 mov ax, cs ; initialize the data segment register DS
 mov ds, ax
 .again:
 ; "push buffer" also seems valid
 mov ax, buffer ; address of string
 push ax ; pass address to function
 call puts ; jump-table is word (2 byte) cells
 mov ax, buffer ; address of string
 push ax ; pass address to function
 call puts ; jump-table is word (2 byte) cells
 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 Arguments below are passed and returned by juggling the stack.
 Command 'c' waits for a keypress and puts it value on the stack.
 Command 'a' prints the value of the key on the stack. Command 'b'
 just prints a greek beta

 * an example of stack passing and returning arguments/parameters
 ---------
 BITS 16
 [ORG 0]
 cr equ 13 ; carriage return
 lf equ 13 ; carriage return
 alpha equ 224 ; Greek letter alpha
 beta equ 225 ; Greek letter beta

 jmp 07C0h:start ; Goto segment 07C0
 jumptable dw putc,bb,cc
 putc:
 pop bx ; the 'putc' procedure return address
 pop ax ; the parameter we want to use
 push bx ; restore the return address to the stack
 mov ah, 0eH ; bios teletype
 int 10H ; do it
 ret
 bb:
 mov al, beta
 mov ah, 0eH ; bios teletype
 int 10H ; invoke bios 
 ret ; return from 'call'
 cc:
 mov ah, 0 ; bios wait for keypress function
 int 16h ; invoke bios
 pop bx ; get return address off stack
 push ax ; put char in AL onto stack
 push bx ; restore return address
 ret ; return from 'call'
 start:
 mov ax, cs ; initialize the data segment register DS
 mov ds, ax
 .again:
 mov al, '>' ; print a prompt
 mov ah, 0eH ; bios teletype 
 int 10H
 mov ah, 0 ; bios wait for keypress function
 int 16h ; invoke bios
 mov ah, 0eH ; echo char just typed (command name)
 int 10H
 cmp al, 'a' ; check for valid command (a-c)
 jb .again ; just print prompt if invalid
 cmp al, 'c'
 ja .again
 sub al, 'a' ; convert letter to a index into jump table
 sub bx, bx ; set bx:=0
 mov bl, al ; ax cant be used in effective addresses
 shl bl, 1 ; do bl:=bl*2 (since its a double-byte array)
 call [jumptable+bx] ; jump-table is word (2 byte) cells
 jmp .again ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

JUMPS 

 direct, indirect, short long etc, conditional, unconditional

CONDITIONAL JUMPS ....

 jne/jnz, ja/jnbe, je/jz, jae/jnb, jb/jnae, jbe/jna
 je jz - jump if ZF=1

 * signed jumps
 ----
 jg, jge, jl, jle are signed jumps
 js, jump if signed
 jns jump if not signed
 ,,,

 * example with lots of jumps
 -------
 mov AX, 10
 mov BX, 9
 cmp AX, BX
 je .equal ; if ax=bx jump 
 jz. equal ; same
 jne .unequal ; if ax!=bx jump
 jnz .unequal ; same
 ja .above ; ?if ax>bx jump
 jae .greaterequal ; ? if ax=>bx jump
 jnb ... ; same

 ,,,


\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents summary of jump instructions
 .. jecxz - jump if ecx is 0
 .. jc - jump if carry
 .. jnc - jump if no carry
 .. jo - jump if overflow
 .. jno - jump if no overflow
 .. js - jump if negative sign
 .. jns - jump if not negative sign
 .. jp - jump if parity
 .. jpe - jump if even parity
 .. jnp - jump if no parity
 .. jpo - jump if odd parity
 ,,,

INDIRECT JUMPS ....

 Indirect jumps may be used to simulate 'switch' or 'case'
 language syntax from higher level languages.

 See also indirect call statements. The techniques of indirect
 jumps and calls all a very simple command interpreter to 
 be written (in the style of a forth system).

 Code below is hanging why??? some code is now working
 eg jmp di

 Many hours of frustration later, it seems the problem was two-fold.
 Firstly register and register indirect jumps us the CS code segment
 implicitly (the offset is calculated from the start of the CS
 segment). The technique used in the MikeOs primer doesnt seem
 to set the code segment properly... Also there are 2 forms ...

 * jump to a memory location contained in register di
 >> jmp di

 * and jump to location specified by register pointer
 >> jmp [di]

 The version above can be used with jump-tables for example
 
 * perhaps the simplest register jump
 ---------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Go to (code?) segment 07C0
 nip:
 mov al, '!' ; print something
 mov ah, 0eH 
 int 10H
 jmp \\$ 
 start:
 mov ax, cs
 mov ds, ax ; may not be necessary?
 .again:
 mov bx, nip 
 jmp bx 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The code below is a good template for possibly the simplest 
 possible command interpreter possible within an assembly
 program. The program prompts the user for a letter (command)
 and then executes some code based on the letter entered. 
 The code is selected with a jump-table and an indirect
 jump. It may be more sensible to use procedure 'calls' in
 this case rather than jumps.

 * a simple register-indirect jump with jump-table
 ---------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0
 jumptable dw aa,bb,cc
 aa:
 mov al, 'A' ; print A
 mov ah, 0eH 
 int 10H
 jmp start.again 
 bb:
 mov al, 'B' ; print B
 mov ah, 0eH 
 int 10H
 jmp start.again 
 cc:
 mov al, 'C' ; print C
 mov ah, 0eH 
 int 10H
 jmp start.again 
 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 .again:
 mov al, '?' ; print a prompt
 mov ah, 0eH 
 int 10H
 mov ah, 0 ; wait for keypress function
 int 16h
 cmp al, 'a'
 jb .again 
 cmp al, 'c'
 ja .again
 sub al, 'a' ; convert letter to a digit
 sub bx, bx ; set bx:=0
 mov bl, al ; ax cant be used in effective addresses
 shl bl, 1 ; do bl:=bl*2
 jmp [jumptable+bx] ; jump-table is word (2 byte) cells
 jmp .again ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 Add a code name to each function, this provides some
 self referentialism.

 * a simple register-indirect jump with jump-table
 ---------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0
 jumptable dw aa,bb,cc
 aa:
 mov al, 'A' ; print A
 mov ah, 0eH 
 int 10H
 jmp start.again 
 bb:
 mov al, 'B' ; print B
 mov ah, 0eH 
 int 10H
 jmp start.again 
 cc:
 mov al, 'C' ; print C
 mov ah, 0eH 
 int 10H
 jmp start.again 
 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 .again:
 mov al, '?' ; print a prompt
 mov ah, 0eH 
 int 10H
 mov ah, 0 ; wait for keypress function
 int 16h
 cmp al, 'a'
 jb .again 
 cmp al, 'c'
 ja .again
 sub al, 'a' ; convert letter to a digit
 sub bx, bx ; set bx:=0
 mov bl, al ; ax cant be used in effective addresses
 shl bl, 1 ; do bl:=bl*2
 jmp [jumptable+bx] ; jump-table is word (2 byte) cells
 jmp .again ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 In the examples below we jump to the third item in
 a word cell jump table

 * jumptable examples, 
 ---------
 mov di, [jumptable+4] 
 call di
 ; is equivalent to
 mov di, jumptable+4 
 call [di]
 ; which is equivalent to
 call [jumptable+4]
 ,,,

 code below is not working because of code segment issues
 but we can do jmp [table+esi*4] which is good!

 * indirect jump example
 -----
 [org 0]
 jmp start
 jumptable dd apple
 dd orange
 dd pear
 dd lemon
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov cs, ax
 mov ds, ax
 ; get a digit (0-3) into AX
 sub eax, eax ; set eax := 0
 mov ah, 0 ; wait for keypress function
 int 16h
 mov ah, 0eH ; echo the keypress
 int 10H
 cmp al, '0' ; check if digit is 0,1,2 or 3
 jb start ;
 cmp al, '3' ; 
 ja start ;
 sub ah, ah ; set ah := 0
 sub al, '0' ; convert from ascii to a digit 0-3
 mov esi, eax
 jmp [jumptable+ESI*4] ; indirect jump
 apple:
 mov al, 'A' ; print A
 mov ah, 0eH 
 int 10H
 jmp start
 orange:
 mov al, 'B' ; print B
 mov ah, 0eH 
 int 10H
 jmp start
 pear:
 jmp start
 lemon:
 jmp start
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

LOOPS

 * print digits 0-9 in ascending order 
 ---------------------------------
 start:
 mov cx, 10 
 .again:
 mov al, 10 
 sub al, cl ;
 add al, '0' ; convert digit to ascii
 mov ah, 0eH ; bios teletype function
 int 10H ; invoke bios
 loop .again
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * attempt to read from a disk 3 times into a data buffer
 -----------------

 mov cx, 3 ; countdown of read attempts
 read\\_loop:
 xor ah, ah ; set ah to zero - reset drive function
 int 0x13 ; call drive reset 

 mov ax, ds
 mov es, ax ; es == ds
 mov bx, BlahBlah ; set BX to the address (not the value) of BlahBlah 
 mov dl, DriveNumber
 mov dh, HeadNumber
 mov al, NumSectors 
 mov ch, CylNumLow
 mov cl, CylNumHigh ; set the high part of the cylinder number, bits 6 and 7 
 and cl, Sector ; set the sector number, bits 0-5
 mov ah, 0x2 ; set function 2h

 int 0x13 ; call the interrupt
 jnc exit ; if the carry flag is clear, it worked
 loop read\\_data ; try three times, then give up - leave error msg in al

 exit:
 ;;; whatever other code you need

 [segment data]
 BlahBlah resb 512 
 ,,,

 The CX register with the loop command, counts down. So we 
 need some extra logic to make it count up

 * print extended ascii characters in ascending order
 ---------------------------------------------------------
 start:
 mov cx, 0x00FF 
 .again:
 mov al, 0xFF
 sub al, cl ; the character to print goes in AL
 mov ah, 0eH ; bios teletype function
 int 10H ; invoke bios function
 loop .again
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * print all ascii characters in descending order
 ---------------------------------------------------------
 start:
 mov cx, 0x00FF 
 .again:
 mov al, cl 
 mov ah, 0eH
 int 10H
 loop .again
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

LOOP INSTRUCTIONS ....
 
 loop, loopz, loope, loopnz, loopne

GOTCHAS FOR LOOPS ....

 If the CX register somehow goes < 0 then the loop will
 probably continue forever (since -1 == 0xFFFF) !! or at least
 a long time. Long enough to cause havoc

REP INSTRUCTIONS ....

 The rep instruction is similar to the LOOP instructions except
 that only one instruction is repeated (multiple in the case
 of loop).

 The REP instrucion and its cousins is used in conjunction 
 with another instruction which is repeated while CX is not
 0 (CX is decremented on each repetition of the instruction)

 * rep instructions
 >> rep, repe, repz, repne, repnz

 * increment AX 4 times
 -------
 mov CX, 4
 rep inc AX
 ,,,

 * move 8 bytes of data from DS:SI to ES:DI
 -------
 mov CX, 8
 rep movsb
 ,,,

 * move 8 double words (32bits) of data from DS:SI to ES:DI
 -------
 mov CX, 8
 rep movsd
 ,,,

STACK 

 The stack is a useful thing but you may have to allocate space
 for it. It grows 'down', towards low memory, and toward SS.
 The stack contains either word (16 bit) or double word (32 bit)
 data items (but never 8 bit). When the stack is full then
 SS == SP. Normally we put the stack after the runtime code
 and it grows back towards the code. x86 is little endian
 so bigger memory address means more significant byte.


 Apparently an x86 bios automatically initialises a 512 (one sector)
 stack immediately after the boot code sector. If you need
 more than this you have to initialise SS (stack segment register)
 and SP (the stack pointer register) to something sensible
 and useful.

INITIALIZING THE STACK ....

 * set up a stack after a bootloader
 -------------------
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 ,,,

POP INSTRUCTIONS ....

 pops a 16 or 32 bit data item from the stack (depending on 
 the 'address size attribute', e.g. BITS 16 in the assembler...)

 * pop straight into the destination index reg
 >> pop di

 * pop the 2 bytes at top of stack and place in CX register 
 >> pop cx (flags: none)

 * pop 2 bytes into memory pointed to by bx
 >> pop [bx]

 * pop a saved flags register into the flags register
 >> popf

 * pop all registers 
 >> popa
 >> popad

PUSH 
 
 * push into memory
 >> push [bx]

TWO STACKS ....

 The x86 architecture includes one built in stack (accessed
 with the 'push' and 'pop' instructions). But it would be nice 
 to have another stack. For example to pass parameters to functions
 without having to worry about stack frames etc. This is 
 another forth idea. 


 * create a second stack
 ----------

 ; The push instructions
 sub edx, 4 ; Decrement the stack pointer one position (4 bytes)
 mov dword [edx], eax ; Store the value at the new location

 ; The pop instructions
 ;Popping 3 steps: Getting value, incrementing the stack, 
 ; and returning the value. We will return the value simply by leaving it in eax.

 mov eax, dword [edx] ; Load the value off of the stack
 add edx, 4 ; Increment the stack pointer one position (4 bytes)
 ; Leave the result in eax to return it
 ,,,

DATA STRUCTURES 

CODE WITH HEADER ....

 Code blocks (proceedures) can be given a header to describe
 the following code. This header can be used to provide a 
 description of the code. Compared to a forth style linked list
 dictionary, we see the extra maintenance involved in maintaining
 a 'jump-table' of pointers to the beginning of each function

 * code with some header text 
 ---------
 BITS 16
 [ORG 0]
 cr equ 13 ; carriage return
 lf equ 10 ; line feed
 bell equ 7 ; bell (sort of)

 jmp 07C0h:start ; Goto segment 07C0
 jumptable dw asc,beep,reboot,colours,help

 asc:
 db 11, 'ascii chars' ; function name in text 
 mov cx, 0x00FF
 asc.again:
 mov al, cl ; print ascii char in CL register 
 mov ah, 0eH 
 int 10H
 and al, 0x0F ; using 'AND' with 'CMP' to 
 cmp al, 0x0F ; create a simple modulus test
 jne asc.ll
 mov al, cr ; print chars 16 to a line
 int 10H
 mov al, lf 
 int 10H
 asc.ll:
 loop asc.again
 nop
 ret
 beep:
 db 4, 'beep'
 mov al, bell ; beep
 mov ah, 0eH 
 int 10H
 ret
 reboot:
 db 6, 'reboot'
 int 19h ; a dodgy way to reboot the computer
 colours:
 db 7, 'colours'
 mov al, 'C' ; print Colours ...
 mov ah, 0eH 
 int 10H
 ret
 help:
 ; the code below has a problem with the CX index used
 ; is only printing 4 valid counts
 db 4, 'help'
 mov al, 'H' ; print help ...
 mov ah, 0eH 
 int 10H
 mov cx, 5 ; loop through 5 functions
 help.again:
 mov di, jumptable
 add di, cx ; do si:=si+cx*2 (jumptable is word cell)
 add di, cx
 mov si, [di] ; di now points to start of function
 mov al, byte [si] ; get the text count
 mov ah, 0eH
 add al, '0' ; convert to ascii digit
 int 10H
 loop help.again 
 ret
 start:
 mov ax, cs ; the code segment is magically correct
 mov ds, ax ; establish data segment
 mov es, ax ; do we need ES extended segment?
 .again:
 mov ah, 0eH 
 mov al, cr ; print a prompt on a newline 
 int 10H
 mov al, lf 
 int 10H
 mov al, '?' 
 int 10H
 mov ah, 0 ; wait for keypress function
 int 16h
 cmp al, 'a' ; check for valid command (a-c)
 jb .again ; just print prompt again if invalid
 cmp al, 'e'
 ja .again
 sub al, 'a' ; convert letter to a index into jump table
 mov bx, jumptable
 add bl, al ; set bx:=bx+al*2
 add bl, al ; set the pointer to point to code
 mov si, [bx] ; si -> start of proceedure
 mov bl, byte [si] ; jump over name of proceedure
 ; or we could print the function name here
 inc bl ; the first code byte
 sub bh, bh ; set bh := 0
 add si, bx
 call si 
 jmp .again ; loop back to prompt
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

LINKED LISTS ....

 Linked lists can be implemented easily using the syntax of 
 the assembler itself (nasm style syntax)

 * a linked list in assembler
 -----------
 liststart dw '0' 
 w1 dw liststart
 db 3, 'egg'
 w2 dw w1
 db 5, 'water' 
 w3 dw w2 
 db 4, 'tree'
 ,,,

 * another layout 
 -----------------
 nip dw 0 ; 1st word has a zero link
 db 3, 'nip' ; strings are 'counted' 
 egg dw nip ; link to previous dictionary entry 
 db 3, 'egg' ; 
 bat dw egg ; link to previous dictionary entry 
 db 3, 'bat' ; 
 last dw bat ; 
 ,,,


 Nasm can also handle forward references to labels, so the 
 dictionary could be written the with the reverse order.

NUMBERS 

DISPLAYING NUMBERS ....

 Even the task of displaying a number in assembler is 
 a non-trivial task. The basic idea is to divide repeatedly 
 by the base in which one is displaying the number (eg 10 for 
 decimal), and use the remainders or the division. But the 
 remainders must be reversed for printing...

 * display one byte number in binary format
 -----------
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0
 dotbin.doc: 
 db 'displays a 1 byte number in binary format', 13, 10
 db 'eg: 3 .bin displays 00000101 '
 dw \\$-dotbin.doc
 ; stack: n --
 dotbin:
 dw 0 ; link to previous word or null at top of dict
 db 4, '.bin' ; counted function name
 dotbin.x:
 pop dx ; balance return fn pointer
 pop bx ; bin number to print
 xor bh, bh ; only 8 bits
 push dx ; restore return IP
 mov cx, 8 ; number of bits to display 
 mov dl, 0b10000000 ; scan bit pattern
 .again:
 mov ah, 0eH ; bios teletype
 test bl, dl ; see if bit is set
 jnz .one
 .zero:
 mov al, '0' ; print zero
 jmp .print
 .one:
 mov al, '1' ; char to print 
 .print:
 int 10H ; x86 bios, ah=0eH type char
 ror dl, 1 ; move the bit pattern 1 to right 
 loop .again ; go again
 ret

 start:

 mov ax, cs ; make ds and es the same as cs code segment 
 mov ds, ax ; data segment
 mov es, ax 
 push 0x0003
 call dotbin.x

 here: jmp here 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,

 * print a 1 byte number in hexadecimal 
 -----------
 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0
 hextable db "0123456789ABCDEF"
 start:
 mov ax, cs ; cs is 07C0 after the far jump
 mov ds, ax ; point data segment -> code segment
 mov ah, 0x0E ; bios int 10H teletype function
 mov bx, hextable ; translation table
 mov dl, 0x1F ; the number to print
 mov cx, 2 ; number of digits to print
 .again
 rol dl, 4
 mov al, dl
 and al, 0x0F ; use only low 4 bits (hex digit)
 xlatb ; replace al with hex digit
 int 10H ; invoke bios to print
 loop .again
 hang: jmp hang
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,

 * print a 2 byte number in hexadecimal 
 -----------
 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0
 hextable db "0123456789ABCDEF"
 start:
 mov ax, cs ; cs is 07C0 after the far jump
 mov ds, ax ; point data segment -> code segment
 mov ah, 0x0E 
 mov bx, hextable ; translation table
 mov dx, 0xABCD ; the number to print
 mov cx, 4
 .again
 rol dx, 4
 mov al, dl
 and al, 0x0F
 xlatb ; replace al with hex digit
 int 10H
 loop .again
 hang: jmp hang
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 ,,,


CONVERTING NUMBERS FROM ASCII ....


 The code below acts as an interactive decimal to hex converter.
 The user enters a number in decimal and the program displays it
 in hex. The program prints '!' if the number is too big to store
 in 2 bytes.

 * convert positive decimal number entered by user to integer 
 ---------
 BITS 16
 [ORG 0]
 cr equ 13 ; carriage return
 lf equ 13 ; carriage return

 jmp 07C0h:start ; Goto segment 07C0
 result dw 0x0000 ; somewhere to store converted number
 newline.h db 'prints a newline',13,10,0
 newline:
 mov ah, 0eH ; bios teletype function 
 mov al, 13 
 int 10H ; invoke bios
 mov al, 10 
 int 10H ; invoke bios
 ret
 hextable db "0123456789ABCDEF" ; translation table
 puthex.h db 'puthex: displays a 2 byte number in hex format',13,10,0
 puthex:
 pop bx ; return address
 pop dx ; the number to print (parameter on stack)
 push bx ; restore return address
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 4 ; number of digits to print
 .again:
 rol dx, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again
 ret

 start:
 mov ax, cs ; initialize the data segment register DS
 mov ds, ax
 mov al, '>' ; print a prompt
 mov ah, 0eH ; bios teletype 
 int 10H
 .again:
 mov ah, 0 ; bios wait for keypress function
 int 16h ; invoke bios
 mov ah, 0eH ; echo the keypress
 int 10H
 cmp al, '0' ; check for valid digit (a-c)
 jb .display ; if ascii value is less than '0' not digit
 cmp al, '9'
 ja .display ; if ascii value greater than '9' not digit
 sub ah, ah ; set ah = 0
 sub al, '0' ; convert digit to ascii
 push ax ; store digit on stack
 mov ax, [result]
 mov bx, 10 ; multiply by 10 (for decimal numbers)
 mul bx ; do AX x BX and store in DX:AX 
 pop bx ; get digit from stack
 jo .toobig ; result too big to store in AX 
 add ax, bx
 mov [result], ax
 jmp .again ; loop- get more digits
 .display:
 call newline
 mov ax, [result] ; result to print 
 push ax ; pass number to function
 call puthex ; display number as hex
 ; also display ascii character here ....
 mov al, 'H' ; print H to indicate hex output 
 mov ah, 0eH ; bios teletype function 
 int 10H ; invoke bios
 call newline
 mov word [result], 0x0000 ; set result = 0

 mov al, '>' ; print a prompt
 mov ah, 0eH ; bios teletype 
 int 10H
 jmp .again ; loop- get a new number
 .toobig: 
 mov al, '!' ; print ! if integer is too big for 2 bytes
 mov ah, 0eH ; bios teletype function 
 int 10H ; invoke bios
 mov word [result], 0x0000 ; set result = 0
 call newline
 jmp .again
 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

ARITHMETIC

 Simple mathemetical operations require special care in assembly
 language because of the need to check for 'overflow' or 
 'carry' conditions (where the result is to large to fit into
 the target register

NEGATIVE NUMBERS ....

 The 2s complement format is used for negative numbers and the msb or 
 most significant bit is set to 1 if negative.

 see "printvectors" for a routine to print +/- 2 digit numbers
 
 "cbw" converts a signed byte to a signed word. This is important

 * check if a number is negative
 ----
 cmp eax, 0
 jl isNegative
 ,,,

 * another way to check if negative (msb is set)
 ------
 test eax, 0x80000000
 jne is\\_signed
 ,,,


 * check that msb is set 
 --------
 test eax, eax
 js signed
 ,,,

 * negate a register
 >> neg ax
 >> xor eax,eax; sub eax,edx ; another worse way to negate

 * display a negative number
 ---------
 print '-'
 neg ax
 print ax
 ,,,

 jg, jge, jl, jle are signed jumps
 js, jump if signed
 jns jump if not signed

GOTCHAS FOR NEGATIVE NUMBERS ....

 if dl == -99 and dh == 0 then dx will NOT be == to -99
 That is because the sign bit (msb) on dx is not set.
 The solution is, just use cbw to convert the signed byte
 to a signed word

ADD INSTRUCTION ....

 It is legal to add directly to the destination index register [di]
 which is hand for writing pixels to memory, strings etc

 * increment di by 4
 >> add di, 4
 
 * negative increments are legal and good and handy
 >> add di, -1 (same as 'sub di, 1')

 * add an indexed memory location
 >> add di, [table + bx]

 * we can use add to multiple
 ---------
 mov cx, 4
 .again:
 add ax, 5 
 loop .again
 ,,,,

DIGITS ....

 * check if a number entered is an ascii digit
 -------
 start:
 .again:
 sub ax, ax
 mov ah,0 ; wait for a key press 
 int 16h ; bios interrupt service 
 cmp al, '0' ;
 jb .notdigit ; if ascii value is less than '0' not digit
 cmp al, '9'
 ja .notdigit ; if ascii value greater than '9' not digit
 mov ah, 0eh ; print the digit if it is one
 int 10h ; bios print routine
 jmp .again 
 .notdigit:
 mov ah, 0eh ; print 'N' if its not a digit
 mov al, 'N' 
 int 10h
 jmp .again
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 
 ,,,

MULTIPLICATION ....

 The MUL instruction is used to multiply. 
 8 or 16 bit: 11 clock cycles
 32 bit: 10 clock cycles

 * multiply AL x BL and store result in AX 
 >> mul BL (flags: CF, OF cleared if AH zero, otherwise set)

 * multiply AX x DX and store result in DX:AX
 >> mul DX (flags: CF, OF cleared if DX zero, otherwise set)

 * multiply EAX x ECX and store result in EDX:EAX
 >> mul CX (flags: CF, OF cleared if DX zero, otherwise set)

DIVISION ....
 
 the x86 instruction set has a special 'div' instruction
 for performing division. Another method is to perform
 repeated subtraction.

 AX/[8 bit register] -> quotient in AL, remainder in AH

 DX AX/[16 bit register] -> quotient in AX, remainder in DX

 EDX EAX/[32 bit register] -> quotient in AX, remainder in DX

 * divide 23/10 and print quotient and remainder 
 ---------------------------------------------------------
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov ax, 23
 mov bl, 10
 div bl ; quotient -> al, remainder -> ah
 add al, '0' ; convert digit in al to ascii 
 call printc ; print the quotient in AL
 mov al, 'r'
 call printc ; print a separator character
 mov al, ah 
 add al, '0' ; convert digit to ascii 
 call printc ; print the remainder (from AH)
 jmp \\$ ; keep looping! 
 
 ; routine to output character in AL to screen
 printc: 
 push ax
 mov ah, 0Eh ; int 10h 'print char' function
 cmp al, 32 ; could modify to check for ascii range 
 int 10h ; call bios function 
 pop ax
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 note that the dividend (AX) must be 16 bits for an 8 bit 
 divisor, so check that the correct data type is loaded.

 The code below only works if the quotient and remainder are
 single digits.

 * divide a number by another and print quotient and remainder 
 ---------------------------------------------------------
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov AX, [dividend] 
 mov BL, [divisor] 
 div BL
 push AX ; save AX so we can get the remainder later
 add AL, '0' ; convert to ascii, but only one digit!
 mov AH, 0Eh ; print quotient
 int 10h
 mov AL, 'r' ; print separator character
 mov ah, 0eh
 int 10h
 pop AX
 mov AL, AH 
 add AL, '0' ; convert to ascii 
 mov ah, 0eh ; print remainder
 int 10h
 jmp \\$ ; keep looping! 
 
 dividend dw 79 
 divisor dw 11 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,

GOTCHAS FOR DIVISION ....

 * a strange bug, if I dont xor ah,ah the whole program crashes in qemu
 -------
 mov bl, 10 ; divide by 10
 xor ah, ah ; this is necessary !! not sure why
 div bl ; do ax/bl, ah=remainder, al=quotient
 ,,,

DIVISION INSTRUCTIONS ....


\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents summary of division instructions
 .. div - unsigned division
 .. idiv - signed division
 .. shr - integer division by powers of 2
 ,,,

 div cx - seems to divide ax by cx and leave the remainder/quotient
 in dx (???)

MODULUS ....

 The modulus operation can be performed by used the 
 'div' instruction, and then taking the value in the AH
 register which is the 'remainder' from a division operation.

 If the modulus is of a number which is a power of 2 (2,4,8,16 ...)
 we can obtain the modulus by ANDing the right number of 
 high bits in the number. This should be faster than using 
 the DIV instruction

 If we perform modulus with test, or and/cmp then the modulus needs
 to be a 2\\^n I think.
 
 * modulus performed with the 'test' instruction
 -----------------
 jmp start
 start:
 mov cx, 0x00FF
 mov ah, 0x0E 
 .again:
 mov al, cl ; print ascii char in CL register 
 int 10H
 test al, 0x0F
 jne .asc.ll ; or jz .asc.ll
 mov al, 13 ; print chars 16 to a line
 int 10H
 mov al, 10 
 int 10H
 .asc.ll: 
 loop .again
 jmp \\$ ; loop forever

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The modulus can also be performed with AND and CMP instructions

 * modulus performed with 'and' and 'cmp'
 -----------------
 jmp start
 start:
 mov cx, 0x00FF
 .again:
 mov al, cl ; print ascii char in CL register 
 mov ah, 0x0E 
 int 10H
 and al, 0x0F ; using 'AND' with 'CMP' to 
 cmp al, 0x0F ; create a simple modulus test
 ; or test al, 0x0F ???
 jne .asc.ll
 mov al, 13 ; print chars 16 to a line
 int 10H
 mov al, 10 
 int 10H
 .asc.ll: 
 loop .again
 jmp \\$ ; loop forever

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
RANDOM NUMBERS ....

 * how to generate a sort of random number from clock ticks
 ------------
 RANDGEN: ; generate a rand no using the system time
 RANDSTART:

 mov ah, 00h ; interrupts to get system time 
 int 1Ah ; cx:dx now holds number of clock ticks since midnight 
 mov ax, dx
 xor dx, dx
 mov cx, 10 
 div cx ; here dx contains the remainder of the division - from 0 to 9

 add dl, '0' ; to ascii from '0' to '9'
 mov ah, 2h ; call interrupt to display a value in DL
 int 21h 
 RET 
 ,,,

 Multiply by big prime,then add other big prime, then
 div to reduce to given number range.

 * a complete random number example 
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 rand.doc:
 db 'Gives a kind of random number between 1 and 10 generated'
 db 'from the number of clock ticks since midnight. Unfortunately'
 db 'this isnt useful for generating a sequence of random numbers'
 dw \\$-rand.doc
 rand:
 dw 0 ; link to previous dict word or null
 db 4, 'rand' ; forth counted name
 rand.x:
 mov ah, 00h ; interrupts to get system time 
 int 1Ah ; cx:dx now holds number of clock ticks since midnight 
 mov ax, dx
 xor dx, dx
 mov cx, 10 
 div cx ; here dx contains the remainder of the division - from 0 to 9
 pop bx ; balance return pointer
 push dx ; push result 0-9 on stack
 push bx ; restore return pointer
 .exit:
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax ; print with stosw


 mov cx, 10
 .again:
 push cx
 call rand.x
 pop ax ; the result 0-9
 mov ah, 0x0e ; print char func
 add al, '0' ; convert to asci digit
 int 10h
 pop cx
 loop .again

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
 * generate pseudo random number
 >> RNG = (69069*RNG + 69069) MOD 2\\^32

 * xor shift pseudo random number generator
 -------------
 mov ax, num
 mov bx, ax
 shl bx, 3
 xor ax, bx ; ax is now next pseudo random number 
 ,,,

 * try to generate some pseudo random numbers 
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 ; **
 rgen.doc:
 db 'generate a pseudo random number between 0 and 9 '
 db ' using the xorshift technique'
 dw \\$-rgen.doc
 rgen:
 dw 0 ; link to previous dict word or null
 db 4, 'rgen' ; forth counted name
 rgen.x:
 pop dx
 pop ax ; previous random
 push dx ; restore fn pointer
 ;mov ah, 00h ; interrupts to get system time 
 ;int 1Ah ; cx:dx now holds number of clock ticks since midnight 

 mov bx, ax
 shl bx, 3
 xor ax, bx

 ;mov ax, dx
 xor dx, dx
 mov cx, 10 
 div cx ; here dx contains the remainder of the division - from 0 to 9
 pop bx ; balance return pointer
 push dx ; push result 0-9 on stack
 push bx ; restore return pointer
 .exit:
 ret
 ; *

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax ; print with stosw


 push 123
 call rgen.x
 mov cx, 10
 .again:
 push cx
 
 call rgen.x
 pop ax ; the result 0-9
 mov ah, 0x0e ; print char func
 add al, '0' ; convert to asci digit
 int 10h
 pop cx
 loop .again

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
MATHEMATICS 

SQUARE ROOT ....

 The square root is approximated using newtons method.
 Another method is to observe that all square numbers are
 the sum of odd consecutive odd numbers eg:
 1 + 3 + 5 = 3\\^2

 * time consuming way to find floor of square root 
 --------
 mov ax, num
 mov cx, 1
 .again:
 sub ax, cx
 cmp ax, 0
 jb .exit ; need to debug this logic
 add cx, 2
 jmp .again
 .exit:
 add cx, 1
 shr cx, 1 ; do cx := (cx+1)/2
 ; cx is now floor of square root
 ,,,,

 But for large numbers this is going to be very slow. Newtons
 method is much faster

POWERS OF TWO ....

 We can get powers of 2 relatively easily and quickly by
 bit shifting left

 * print in decimal powers of 2
 -----------
 
 ,,,

GEOMETRY


 * get absolute value of difference
 ---------
 sub eax, edx
 cdq
 xor eax, edx
 sub eax, edx
 ,,,

 print pixels in a circle. square the distance then check.

 * show distance between 2 graphics points (x1,y2) (x2,y2)
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 dist.doc:
 db 'Gives the distance between 2 points using the formula '
 db ' sqrt((x1-x2)\\^2 + (y1-y2)\\^2) '
 db ' [stack: x1, y1, x2, y2 :tos -- returns: |distance| ] '
 dw \\$-dist.doc
 dist:
 dw 0 ; link to previous dict word or null
 db 4, 'dist' ; forth counted name
 dist.x:

 pop dx ; balance return ip
 pop ax ; x1 coord
 pop bx ; y1 coordinate
 pop cx ; x2 coord 
 sub ax, cx ; x1-x2 
 pop cx ; y2 coord
 sub bx, cx ; y1-y2 
 push dx ; restore fn pointer

 mov dx, ax
 mul dx ; ax\\^2
 mov ax, dx ; save ax*ax to dx
 mov ax, bx
 mul bx ; ax:=bx\\^2
 add ax, dx ; |x|\\^2 + |y|\\^2 
 
 pop dx ; juggle return fn*
 push ax ; save result on stack
 push dx ; restore return fn*
 .exit:
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov ax, 0xB800 ; for printing to screen 
 mov es, ax ; print with stosw

 push 0 ; x1
 push 0 ; y1
 push 3 ; x2
 push 4 ; y2, 3,4,5 triangle
 call dist.x
 
 pop ax ; result
 
 ; look for dothex for printing here: use direct memory 
 ; printing
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
 
CONFIGURING VIDEO DISPLAY

 The video appears to have several 'display modes' which need to
 be set or configured.

 int 10h Get current video mode AH=0Fh 
 returns: AL = Video Mode, AH = number of character columns, 
 BH = active page

 * display the video mode number 
 -----------------------------------------------------
 jmp start
 start:
 mov ah, 0Fh 
 int 10h
 add al, '0' 
 mov ah, 0Eh
 int 10h
 jmp \\$ ; loop forever

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The code below prints '80' which is probably the standard
 screen character width in text mode.

 * display the number of character columns 
 -----------------------------------------------------
 jmp start
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov ah, 0Fh ; video mode info function
 int 10h ; load al with character width
 mov al, ah ; printi8 prints number in al register 
 mov bl, 10 ; printi8 uses bl register for base
 call printi8
 jmp \\$ ; loop forever

 \\%include 'printi8.asm'
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

VIDEO MODES

 \url{http://brokenthorn.com/Resources/OSDevVid2.html}
 good info

 vga interface = low resolution, usually <= 16 colours (except 13h)
 vesa vbe interface = higher resolutions

 Note that mode 13h is the only one in standard vga which
 can display 256 colours.

 * set a video mode
 >> INT 10h, AH=0, AL=<video-mode>
 

\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents Standard vga colour modes
 Mode Resolution Color depth 
 AL=0h 40x25 Text 16 Color
 AL=1h 40x25 Text 16 Color
 AL=2h 80x25 Text 16 Color 
 3h 80x25 Text 16 Color (default text mode on boot-up)
 4h 320x200 4 Color
 5h 320x200 4 Gray 
 7h 80x25 Text 2 Color 
 Dh 320x200 16 Color
 Eh 640x200 16 Color
 Fh 640x350 2 Color
 10h 640x350 16 Color 
 11h 640x480 2 Color
 12h 640x480 16 Color
 13h 320x200 256 Color (a common simple graphics mode)
 6Ah 800x600 16 color (higher resolution) 
 ,,,

 Basically in text mode its seems impossible to draw pixels
 and visa versa.

 * set the mode to 0 for bigger text 
 --------------------
 start:
 mov ax, 07C0h
 mov ds, ax
 .setmode:
 mov ah, 0 ; set graphics display mode function.
 mov al, 0h ; mode 0h = text 40x25 
 int 10h ; set it!
 .text:
 mov ah, 0eh
 mov bh, 0 ; page 0
 mov bl, 0x2F ; colour 2 = background, F = forground
 mov al, 'Q'
 int 10h
 hang: jmp hang

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 Set the high bit of AL to not clear screen when changing
 video mode.

 * set video mode to 3 (colour text) without clearing screen
 -------
 start:
 mov ah, 0eh ; teletype 'P'
 mov al, 'P'
 int 10h
 mov ah, 0
 int 16h ; wait for a key press
 .setmode:
 mov ah, 0
 mov al, 10000000b ; 0 + high bit set to not clear screen
 int 10h
 .print:
 mov ah, 0eh ; teletype 'Q'
 mov al, 'Q' ; the 'Q' gets printed in cursor position 0,0
 int 10h ; and overwrites whatever was there
 hang: jmp hang

 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 

 Garbage is displayed on the screen with the code below

 * switch to a graphics mode without clearing screen, not useful
 -------
 start:
 mov ah, 0eh ; teletype 'P'
 mov al, 'P'
 int 10h
 mov ah, 0
 int 16h ; wait for a key press
 .setmode:
 mov ah, 0
 mov al, 13h ; graphics mode 320x200
 or al, 10000000b ; set the high bit to 1 
 int 10h
 .print:
 hang: jmp hang

 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
GRAPHICS VIDEO MODES ....

 \url{http://wiki.osdev.org/Drawing\\_In\\_Protected\\_Mode}
 see this

 mode 12h has resolution 640x320 which is not bad, but
 only 16 colours, where as
 mod 13h is 320x200, 256 colours

 Text *can* be printed in graphics video modes !!!

 ; set video to big text 40x25
 vid:
 dw asc
 db 3, 'vid'
 vid.x: 
 mov ah, 0 ; set graphics display mode function.
 mov al, 1h ; mode 0h = text 40x25 
 int 10h ; set it!
 ret

WRITING OUTPUT TO THE SCREEN

 Write a character at the current cursor position 
 int 10h, ah=0ah al=character, bh=page number, 
 cx=number of times to print the character


\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents int 10h character display functions (value in register ah)
 .. 0eh - teletype, the cursor is advanced after printing
 .. 0ah - print character at x,y position with colour
 ..

 * print a character using the 'teletype' function (0eh)
 ---------------------------------------------------------
 start:
 mov al, '*'
 mov ah, 0eH
 int 10H
 jmp \\$ ; keep looping! 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 For a great deal of time I assumed that it wasnt possible
 to print text in graphic video modes... not true!! But 
 the BL register must be set to the required colour other
 wise it seems to default to black, meaning that the character
 cannot be seen.

 * print a coloured character in graphic mode 13h
 ---------------------------------------------------------
 start:
 mov ah, 0 ; clear the screen
 mov al, 13h 
 int 10H
 mov ah, 0eH
 mov al, '*'
 mov bl, 0x18 ; the colour of the character = 8, background=3
 int 10H
 mov bl, 7 ; the colour of the character
 int 10H
 mov bl, 6 ; the colour of the character
 int 10H
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The newline (13) goes to start of current line.
 linefeed (10) goes to a new line. So 13, 10 works as expected

 * print a character with 'teletype' and a newline 
 ---------------------------------------------------------
 start:
 mov al, '*'
 mov ah, 0eH
 int 10H
 mov al, 13 
 mov ah, 0eH
 int 10H
 mov al, 10 
 mov ah, 0eH
 int 10H
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * print a 10 stars down the screen 
 ---------------------------------------------------------
 start:
 mov cx, 30
 .again:
 mov al, '*'
 mov ah, 0eH
 int 10H
 mov al, 13 
 ;mov ah, 0eH
 int 10H
 mov al, 10 
 ;mov ah, 0eH
 int 10H
 loop .again
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * print "!!!" at the current cursor position
 -----------------------------------------------------
 start:
 mov ah, 0aH 
 mov al, '!' ;
 mov cx, 3 ;
 int 10h ;
 jmp \\$ ; Jump here - infinite loop!

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,


 * notes for how to clear the screen
 --------------------------

 start:
 mov ah,06h ; ah=function number for int10 (06)
 mov al,00h ; al=number of lines to scroll (00=clear screen)
 mov bx,700h ; bh=color attribute for new lines
 xor cx,cx ; ch=upper left hand line number of window (dec)
 ; cl=upper left hand column number of window (dec)
 mov dx,184fh ; dh=low right hand line number of window (dec)
 ; dl=low right hand column number of window (dec)
 int 10h
 jmp \\$ ; Jump here - infinite loop!

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,

OUTPUT TEXT WITHOUT BIOS FUNCTIONS ....

 It is not difficult to print text without using the bios int 10h functions.
 You just write the ascii char to the memory location starting at 0xB8000 and
 colour attribute afterwards. But is this possible in all video modes? or
 only text modes?

 So 0xB8000 is column 0, row 0 
 0xB8001 is column 0, row 0, colour byte |IRGB IRGB| (background forground)
 0xB8002 is colomn 1, row 0 
 0xB8001 is column 1, row 0, colour byte |IRGB IRGB| (background forground)
 etc
 0xB8000+160d is column 0, row 1
 0xB8000+161d is column 0, row 1, colour byte

 * print a character to the screen using video memory 
 ------------------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 char.doc:
 db 'Prints one character to screen using video memory'
 dw \\$-char.doc
 char:
 char.x:
 mov ax, 0xB800
 mov fs, ax ; fs -> start of video memory 
 mov [fs:4], byte '\\#' ; the char to print
 mov [fs:5], byte 0b00101001 ; blue on green 
 ret
 start:
 mov ax, cs
 mov ds, ax
 mov es, ax 

 mov ah, 0 ; clear the screen
 mov al, 13h 
 ;int 10H

 call char.x

 jmp \\$ ; loop forever or hlt ?
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,


 We cannot copy byte character strings directly to video memory because
 each character in video memory has a colour bit

 * print a string by writing to video memory
 ------------------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 buffer db 33, 'abcdefghijklmnopqrstuvwxyz01234567890'
 string.doc:
 db 'Prints a string to screen using video memory'
 dw \\$-string.doc
 string:
 string.x:
 push es ; save es pointer
 mov ax, 0xB800
 mov es, ax ; es -> start of video memory 
 mov si, buffer+1 ; string to print 
 mov di, 100 ; where on screen to print

 ; rep movsb doesnt work because of colour bytes 
 ; [high byte= colour, low byte = character]
 ; but can do stosb char then stosb colour
 xor cx, cx ; count is only one byte
 mov cl, [buffer] ; how many chars to print (counted string)
 cld ; make lodsb and stosw step forwards 
 .nextchar
 lodsb ; al := ds:si++ 
 ; mov ah, 0b00101001 ; colour blue on green 
 mov ah, cl ; multi colour
 and ah, 0x0F ; only 16 forground colours 
 stosw ; es:di++ := ax, al==char, ah==colour
 loop .nextchar ; do it cx times

 ;mov [es:4], byte '\\#' ; the char to print
 ;mov [es:5], byte 0b00101001 ; blue on green 

 pop es ; restore es pointer
 ret

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax 

 mov ah, 0 ; clear the screen
 mov al, 13h 
 ;int 10H

 call string.x

 jmp \\$ ; loop forever or hlt ?
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,

 The code below relies on the trick that the ascii char '/' is
 also a colour code (white on light green). This code gets
 written to the high byte after the actual character to display

 * a trick to print a string directly to memory 
 ------------------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 buffer db start-\\$, 't/h/i/s/ \texttt{/a/} \texttt{/t/e/s/t/'}

 start:
 mov ax, cs
 mov ds, ax

 mov ax, 0xB800
 mov es, ax ; es -> start of video memory 
 mov si, buffer+1 ; string to print (skip count)
 mov di, 100 ; where on screen to print (50th char on screen)
 cld ; make movsb step forwards
 xor cx, cx ; string count is one byte: ie set ah := 0
 mov cl, [buffer] ; how many chars to print
 rep movsb ; copy string to video memory at 0xB8000 
 ; white on green

 jmp \\$ ; loop forever or hlt ?
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,

 * fill the screen with colours using memory writes 
 ------------------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 patch.doc:
 db 'Fills the screen with colours'
 dw \\$-patch.doc
 patch:
 patch.x:
 push es ; save es pointer
 mov ax, 0xB800
 mov es, ax ; es -> start of video memory 
 mov di, 0x00 ; start at top left corner 
 cld ; make stosw step forwards 

 ; rep movsb doesnt work because of colour bytes 
 ; [high byte= colour, low byte = character]
 mov cx, 0x0FFF ; print lots of background colours
 .nextchar
 mov ax, cx ; multi colour
 mov ah, al ; colour in high byte
 shl ah, 4 ; colour in high nibble
 xor al, al ; no character to print
 stosw ; es:di++ := ax 
 loop .nextchar ; do it cx times
 pop es ; restore es pointer
 ret

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax 

 call patch.x

 jmp \\$ ; loop forever or hlt ?
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,

 * c example of print to screen without bios int 10h
 ---------------------------------------------------
 // note this example will always write to the top
 // line of the screen
 void write\\_string( int colour, const char *string )
 \{
 volatile char *video = (volatile char*)0xB8000;
 while( *string != 0 ) \{
 *video++ = *string++;
 *video++ = colour;
 \}
 \}
 ,,,,

CURSOR

 Get Cursor position Bios function int 10H
 AH=03h,DL=Cursor-column,DH=Cursor-row 

 Set Cursor position Bios function int 10H
 AH=02h,DL=Cursor-column,DH=Cursor-row 

 * increment the cursor column position
 -------------------------------
 start:
 ; mov bh, 00h ; assume page 0
 mov ah, 03h ; bios function: get cursor position into dx 
 int 10h ; invoke bios
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dl ; increment cursor column by 1
 int 10h ; invoke bios
 jmp \\$ ; Jump here - infinite loop!
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


 * increment the cursor row and line position 10 times
 -------------------------------
 start:
 mov cx, 10 ; the loop counter
 .again:
 push cx
 mov al, '*'
 mov ah, 0eH
 int 10H
 ; why mov bh???
 ;mov bh, 00h ; get cursor position into dx (int 10h, ah=03h)
 mov ah, 03h ; bios get cursor row:col into DH:DL
 int 10h ; invoke bios
 mov bh, 00h ; not sure if necessary ??
 mov ah, 02h ; set cursor position specified in dx
 inc dl 
 inc dh 
 int 10h
 pop cx
 loop .again 

 jmp \\$ ; Jump here - infinite loop!
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,

MOVING THE CURSOR ....
 
 * a simple example
 ------------
 mov ah, 0 ; bios read key code function
 int 16H ; invoke bios
 cmp al, 0 ; is extended char (AL != 0) ?
 jne .printkey
 cmp ah, 75 ; left arrow
 je .leftarrow 
 cmp ah, 77 ; right arrow
 je .rightarrow 
 ,,,

 * move the cursor right if right arrow pressed
 -------------------------------
 jmp start
 start:
 .again:
 mov ah, 0 ; bios read key code function
 int 16H ; invoke bios
 cmp al, 0 ; is extended char (AL != 0) ?
 jne .again ; wait for next key if not ->
 cmp ah, 77 ; right arrow
 jne .again ; wait for next key if not -> arrow key
 mov ah, 03h ; get cursor position into dx (int 10h, ah=03h)
 int 10h ; invoke bios 
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dl ; increment column position
 int 10h ; invoke bios
 jmp .again 
 jmp \\$ ; program hangs here 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The code below could be greatly simplified by having a mov cursor
 procedure ?

 * move the cursor if arrow keys pressed
 -------------------------------
 jmp start
 start:
 .again:
 mov ah, 0 ; bios read key code function
 int 16H ; invoke bios
 cmp al, 0 ; is extended char (AL != 0) ?
 jne .again ; wait for next key if not extended char
 cmp ah, 75 ; left arrow
 je .moveleft ; 
 cmp ah, 77 ; right arrow
 je .moveright ; 
 cmp ah, 80 ; down arrow
 je .movedown 
 cmp ah, 72 ; up arrow
 je .moveup 
 jmp .again
 .moveleft:
 mov ah, 03h ; get cursor position into dx (int 10h, ah=03h)
 int 10h ; invoke bios 
 mov ah, 02h ; bios function: set cursor position specified in dx
 dec dl ; decrement column position
 int 10h ; invoke bios
 jmp .again 
 .moveright:
 mov ah, 03h ; get cursor position into dx (int 10h, ah=03h)
 int 10h ; invoke bios 
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dl ; increment column position
 int 10h ; invoke bios
 jmp .again 
 .movedown:
 mov ah, 03h ; get cursor position into dx (int 10h, ah=03h)
 int 10h ; invoke bios 
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dh ; increment row position
 int 10h ; invoke bios
 jmp .again 
 .moveup:
 mov ah, 03h ; get cursor position into dx (int 10h, ah=03h)
 int 10h ; invoke bios 
 mov ah, 02h ; bios function: set cursor position specified in dx
 dec dh ; decrement row position
 int 10h ; invoke bios
 jmp .again 
 jmp \\$ ; program hangs here 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 Check if this cursor positioning code works with a real bios
 (not just an emulator)!

 * echo chars and print ascii code in hexadecimal at bottom of screen 
 ---------------------------------------------------------
 BITS 16
 [ORG 0]

 sigma equ 228 ; greek capital sigma
 jmp 07C0h:start ; Goto segment 07C0

 hextable db "0123456789ABCDEF"
 key.al db 0 ; AL keycode left by int 0x16, al=0x00 
 key.ah db 0 ; AH keycode left by int 0x16, al=0x00 

 ; a structured document format, the last string in the 
 ; data structure is zero terminated to indicate end of documentation

 keycode.doc: ; one line summary for function
 db keycode.see-\\$ ; string count
 db 'Prints the ps/2 scancodes of keys pressed and released'
 keycode.see: ; related functions
 db keycode.eg-\\$ ; string count
 db 'scancode, colourcode', 0 ; zero terminated string
 dw \\$-keycode.doc
 keycode.eg:
 db keycode-\\$ ; string count
 db 'keycode /displays ascii codes interactively', 0
 dw \\$-keycode.doc ; a link to the top of the documentation
 keycode:
 dw 0 ; link to previous
 dw 7, 'keycode'
 keycode.x:

 .begin
 mov ah, 0 ; wait for keypress function
 int 16h
 cmp al, 'q' ; was the key press a 'q' 
 je .exit ; bail if quit pressed
 mov [key.al], al ; save AL key code in key buffer
 mov [key.ah], ah ; save AL key code in key buffer
 mov ah, 0eH ; bios teletype char function
 int 10H
 cmp al, 13 ; was the key press a 'enter' 
 jne .status
 mov al, 10 ; if a 'enter' is pressed add a newline
 int 10h
 ; save the cursor position, print something
 ; at the bottom of screen, then restore cursor
 .status: 
 mov ah, 03h ; bios function DH:DL <- cursor y:x
 int 10h ; invoke bios
 push dx ; save cursor Row:Col (DH:DL) on stack
 mov dx, 0x1700 ; Row 23, Column 0
 mov ah, 02h ; set cursor position specified in dx
 int 10h ; 

 ; print the key code in hex format
 mov ah, 0x0E ; x86 bios teletype function 

 mov al, 'A' ; 
 int 0x10 ; 
 mov al, 'L' ; 
 int 0x10 ; 
 mov al, '=' ; 
 int 0x10 ; invoke bios function
 mov al, '0' ; print prefix 0x to indicate hexadecimal number
 int 0x10 ; invoke bios function
 mov al, 'x' ;
 int 0x10
 mov cx, 2 ; print 2 nibbles ( 1 byte )
 mov bx, hextable ; pointer to digit translation table
 mov dl, [key.al] ; get key code from buffer
 .nextbyte:
 rol dl, 4 ; print first digit
 mov al, dl ; get key code 
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 ; or use stosw for memory printing
 ; with ah==colour
 int 10h ; invoke bios 
 loop .nextbyte
 
 ; how about direct memory printing here
 mov al, ' ' ; print a space to separate al and ah results 
 int 0x10 
 mov al, 'A' ; 
 int 0x10 
 mov al, 'H' ; 
 int 0x10 ; invoke bios function
 mov al, '=' ; print prefix 0x to indicate hexadecimal number
 int 0x10 ; invoke bios function
 mov al, '0' ; print prefix 0x to indicate hexadecimal number
 int 0x10 ; invoke bios function
 mov al, 'x' ;
 int 0x10

 mov cx, 2 ; print 2 bytes
 mov bx, hextable ; pointer to digit translation table
 mov dl, [key.ah] ; get key code from buffer
 .nextbyte.ah:
 rol dl, 4 ; print first digit
 mov al, dl ; get key code from buffer
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 int 10h ; invoke bios 
 loop .nextbyte.ah
 
 ; Also print the 'colour' of the ascii code. eg 0x7F is 
 ; intense white on grey (fg = low nibble, bg = high nibble)

 mov ah, 0x0E
 mov al, ' ' ; print a space to separate al and ah results 
 int 0x10 

 mov cx, 1 ; print 1 char only 
 mov ah, 0x09 ; bios function colour print 
 mov al, sigma ; print the 'colour' of current code 
 mov bl, [key.al] ; color bits: I back(R|G|B) I fore(R|G|B)
 int 0x10 

 ; If a special key is pressed (insert delete etc) then
 ; al will be 0. Escape is not a special key

 pop dx ; restore text cursor position
 mov ah, 02h ; set cursor position specified in dx
 int 10h ; invoke bios
 jmp .begin ; keep looping! 

 .exit:
 ret

 start:
 mov ax, cs 
 mov es, ax
 mov ds, ax
 call keycode.x
 jmp \\$

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * Code to show a hex byte is only 1 instruction longer than loop !!!
 --------------------------------------
 mov bx, hextable ; translation table
 mov ah, 0eH ; bios teletype function 
 mov al, [key] ; get key code from buffer
 rol al, 4 ; print first digit
 and al, 0x0F ; higher order digit
 xlatb ; replace al with hex digit
 int 10h ; invoke bios 
 mov al, [key] ; get key code from buffer
 and al, 0x0F ; lower order digit
 xlatb ; replace al with hex digit
 int 10h ; invoke bios 
 ,,,

KEYBOARD

PS2 ....

 understand \url{http://wiki.osdev.org/PS2\\_Keyboard}

 ps/2 Scancode tables: TODO: official specs?

 - \url{http://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/APNDXC.PDF}
 - https://en.wikipedia.org/wiki/Scancode

 One standard interface is the ps/2 keyboard

 probably need to use interrupts to do the following, otherwise
 this will block. Is a ps2 scancode only 8 bits? Key releases have the
 same code as corresponding keypress but highest bit is set (eg and 0x8000, key)

 * print hex scan codes every time key is pressed, released
 ------------------
 BITS 16
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 hextable db "0123456789ABCDEF" ; translation table
 scancode.doc:
 db 'Prints the ps/2 scancodes of keys pressed and released'
 dw \\$-scancode.doc
 scancode:
 scancode.x:
 ; cli ; necessary ??
 .loop:
 in al, 0x60 ; store ps/2 keyboard scancode to al
 cmp al, cl ; is this a new code? 
 je .loop ; 
 cmp al, 0x81 ; [escape] key release 
 je .exit ; exit if escape key pressed
 mov cl, al

 push cx ; save previous code to stack 

 ; print the hex code in al
 mov dl, al
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 2 ; number of digits to print
 .again:
 rol dl, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again

 mov ah, 0x0E ; 
 mov al, 0x0D ; a newline 
 int 10H
 mov al, 0x0A ; formfeed
 int 10H

 pop cx ; restore scan code
 jmp .loop

 .exit:
 ret

 start:

 mov ax, cs
 mov ds, ax
 mov es, ax

 call scancode.x

 here: jmp here ; loop forever or hlt ?
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,

 The code below is in GAS syntax.

 * print hex scan codes every time key is pressed, released
 ------------------
 BEGIN
 CLEAR
 /* TODO why CLI makes no difference? We are not using interrupts? */
 \texttt{/*cli*/}
 loop:
 /* Store the scancode to al. */
 in \\$0x60, \\%al
 cmp \\%al, \\%cl
 jz loop
 mov \\%al, \\%cl
 PRINT\\_HEX <\\%al>
 PRINT\\_NEWLINE
 jmp loop
 ,,,

INPUT FROM THE KEYBOARD ....
 
 * define ASCII code constant for the <Escape> key
 >> ESC equ 1bh ; should we not check al==0 as well

 INT 16h with AH=00h or 10h will block waiting for a keypress (returns ASCII result in AL); 
 
 use AH=01h or 11h to query whether a keypress is available first if you want to avoid blocking (returns immediately with ZF clear if a key is available, or set if not). See e.g. here, or here (or Google "INT 16h" for more).

 * press any key to exit a loop
 -------------------------
 .again 
 mov ah, 0x01 ; x86 bios check if keypress available
 int 0x16 
 jz .again ; loop forever if no keypress 

 ,,,

 In the code example below, space and backspace appear to work
 as expected on my machine, but [enter] returns the cursor to 
 the beginning of the line. 

 * read keys from the keyboard and print them to the screen
 ---------------------------------------------------------
 jmp start
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov ah, 0 ; wait for keypress function
 int 16h
 mov ah, 0eH ; echo the key pressed
 int 10H
 cmp al, 13 ; was the key press a 'enter' 
 jne start
 mov al, 10 ; if a 'enter' is pressed add a newline
 int 10h
 jmp start ; keep looping! 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


 Below is a forth style "key" and "emit" functions in a 
 linked list. Parameters are passed on the stack

 * read a key press and place value on the stack 
 ------------
 jmp start

 ; get one keystroke from user and place on stack
 key: 
 dw 0 ; 1st word has a zero link 
 db 3, 'key' ; forth-style function header 
 key.x:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 pop bx ; juggle function return pointer
 push ax ; save keypress value on stack
 push bx ; restore return pointer to stack
 ret
 emit:
 dw key ; link to previous dictionary entry 
 db 4, 'emit' 
 emit.x:
 pop bx ; juggle return address for call
 pop ax ; character to print (into al)
 push bx ; restore return function call
 mov ah, 0eh ; bios print character function
 int 10h
 ret

 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax 
 ;mov sp, ? ; what about the stack pointer?
 here:
 call key.x
 call emit.x
 jmp here 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * read keys and freeze if escape is pressed 
 ---------------------------------------------------------
 ESCP equ 1bh
 start:
 mov ah, 0
 int 16H
 cmp al, ESCP 
 je .done ; If escape pressed, freeze! 
 mov ah, 0eH ; print the character
 int 10H
 jmp start ; keep looping! 
 .done:
 jmp \\$

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 If a 'special' non-printing key is pressed, then register AL
 is zero and register AH contains the code for the key press

 * print '<' and '>' if arrow keys are pressed 
 ---------------------------------------------------------
 jmp start
 start:
 .again:
 mov ah, 0
 int 16H
 cmp al, 0 
 jne .printkey
 cmp ah, 75 ; left arrow
 je .leftarrow 
 cmp ah, 77 ; right arrow
 je .rightarrow 
 cmp ah, 82
 je .insertkey ; insert key?
 cmp ah, 83
 je .deletekey ; delete key?
 jmp .again ; read more keys
 
 .leftarrow
 mov al, '<'
 jmp .printkey 
 .rightarrow
 mov al, '>'
 jmp .printkey 
 .insertkey
 mov al, 'I'
 jmp .printkey 
 .deletekey
 mov al, 'D'
 jmp .printkey 
 .printkey
 mov ah, 0eH ; print the key pressed 
 int 10H
 jmp .again 
 .done:
 jmp \\$

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The following clears the screen but stops typing.

 * read keys and clear screen if escape is pressed 
 ---------------------------------------------------------
 ESC equ 1bh
 start:
 mov ah, 0 ; bios read char function
 int 16H ; invoke bios
 cmp al, ESC ; was key 'escape' ? 
 je .clear ; If escape pressed, cls! 
 mov ah, 0eH ; print the character
 int 10H
 jmp start ; keep looping! 

 .clear:
 mov ah, 0
 mov al, 13h
 int 10H
 jmp start 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


EDITING TEXT ....

 I think the idea is simple. One maintains a memory buffer of 
 text which is synchronised to what the user sees on the screen.
 When the user changes the text, the buffer gets updated. This
 buffer is then written periodically to storage.

 * a simple buffer editor with no save
 ------------
 BITS 16
 [ORG 0]
 cr equ 13 ; carriage return
 lf equ 13 ; carriage return

 jmp 07C0h:start ; Goto segment 07C0

 ; a small buffer made blank 
 buffer times 128 db ' ' ; we only have 512 bytes here

 edit.doc db 'Allows the user to edit a buffer of text'
 dw \\$-edit.doc
 edit:
 dw 0 ; top of dictionary
 db 4, 'edit'
 edit.x:
 pop bx ; return address
 pop di ; address of the buffer and insert pointer
 push bx ; restore return address

 mov ah, 0x0E ; bios teletype function 
 mov al, '*' ; an edit prompt
 int 10H ; invoke bios print function

 mov ah, 0 ; bios read key code function
 int 16H ; invoke bios
 ;cmp al, 0 ; is extended char (AL != 0) ?
 ;jne .again ; wait for next key if not ->
 ;cmp ah, 77 ; right arrow
 cmp ax, 77 ; right arrow
 ;jne .again ; wait for next key if not -> arrow key
 mov ah, 03h ; get cursor position into dx (int 10h, ah=03h)
 int 10h ; invoke bios 
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dl ; increment column position
 int 10h ; invoke bios

 ret

 start:
 mov ax, cs ; initialize the data segment register DS
 mov ds, ax
 mov es, ax
 push buffer
 call edit.x

 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

BYTECODE SYSTEM 512 BYTES

 Bytecode is an important goal for creating a forth which
 can run on a virtual machine on any architecture. While
 execution speed will be slower than other types of systems,
 it is useful for rapidly porting to a new architecture,
 since all that initially needs to be coded is the virtual
 machine

 The system below seems very powerful and very minimal. It
 is a basic virtual machine implemented in as little as 100
 bytes. Now to extend this, we need a way to compile code,
 and a virtual proceedure call for all high level functions.
 And we have to actually design the virtual machine.

 The name field uses a reverse count. So the count comes
 after the string. This allows exec.x to execute a list
 of pointers to code rather than having to adjust pointer
 after loading it.

 A 512 byte system is useful as a minimalist interactive
 bootloader and debugger.

 We can reduce the size of this, in a number of ways
 . relative backlinks to previous words in dictionary
 uses 1 byte vs 2 bytes per word. Means a maximum
 of 255 bytes per word (should be ample)
 . Dont have a count byte. Make all words 1 or 2 in length
 . keep top element of stack in ax register. Saves 
 quite a few bytes in the opcodes. 
 
 
 * forth stack machine bytecode example within 512 bytes
 ----------------------
 BITS 16
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0
 
 ;*** aliases for each bytecode
 ; these aliases are in the
 ; same order as the pointer table below

 ;*** opcodes 1,2,3,4 etc
 DUP equ 1
 DROP equ DUP+1
 SWAP equ DROP+1
 CSTORE equ SWAP+1
 CFETCHPLUS equ CSTORE+1
 COUNT equ CFETCHPLUS ; count is an alias for @+ 
 LIT equ COUNT+1
 LITW equ LIT+1
 EMIT equ LITW+1
 KEY equ EMIT+1
 PLUS equ KEY+1
 MINUS equ PLUS+1
 DIVMOD equ MINUS+1
 FCALL equ DIVMOD+1 
 EXIT equ FCALL+1 
 JUMP equ EXIT+1 
 JUMPZ equ JUMP+1 
 JUMPF equ JUMPZ ; jump false alias for jump zero 
 ; JUMPNZ equ JUMPZ+1 
 NOOP equ JUMPZ +1 ; nop is a good end-marker

 ;*** a table of code pointers. 
 ; The pointers have the same offset in
 ; table as the value of the opcode
 op.table:
 dw 0, dup.x, drop.x, swap.x, cstore.x, cfetchplus.x
 dw lit.x, litw.x, emit.x, key.x
 dw plus.x, minus.x
 dw divmod.x, fcall.x, exit.x
 dw jump.x, jumpz.x

 ; this is the function which executes the byte codes
 ; takes a pointer to the code. 
 ; Jumps are relative to the first
 ; byte of the jump instruction
 
 ; using relative backlinks to previous dictionary 
 ; entry to save 1 byte, could squash a count in there
 ; as well maybe
 exec:
 db 0 ; zero link means top of dictionary
 db 'ex', 2 ; this is not used interactively
 ; so probably doesnt need name field
 exec.x:
 ; save the return ip for 'exec' since the code
 ; below "call [op.table+bx]" changes the stack and 
 ; registers. Before this technique the same byte
 ; code would get executed over and over again because
 ; exec was not returning properly

 pop word [returnexec] ; save return ip
 pop si ; get pointer to code
 .nextopcode:
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++
 cmp al, 0 ; zero marks end of code
 je .exit

 .opcode:
 mov bx, ax ; get opcode (1-6 etc) into bx
 shl bx, 1 ; double bx because its a word pointer 
 call [op.table+bx] ; opcode is offset into code *p table
 jmp .nextopcode
 .exit:
 push word [returnexec] ; restore fn return ip
 ret

 returnexec dw 0

 plus.doc:
 ; db ' ( n1 n2 -- n1+n2 ) '
 ; db ' add the top 2 elements of the stack.'
 ; dw \\$-plus.doc
 plus:
 db \\$-exec.x-1 ; a relative link, saves 1 byte!
 db '+', 1
 plus.x:
 pop dx ; juggle return pointer
 pop bx
 pop ax
 add ax, bx
 push ax
 push dx ; restore return pointer
 ret

 minus.doc:
 ; db 'subtract the top element of stack from next top'
 ; db ' ( n1 n2 -- n1-n2 ) '
 ; dw \\$-minus.doc
 minus:
 db \\$-plus.x-1 ; relative backlink
 db '-', 1
 minus.x:
 pop dx ; juggle return pointer
 pop bx
 pop ax
 sub ax, bx
 push ax
 push dx ; restore return pointer
 ret

 divmod.doc:
 ; db '(n1 n2 - remainder quotient) '
 ; db ' divide n1 by n2 and provide remainder and quotient. '
 ; db ' n2 is the top item on the stack '
 ; dw \\$-divmod.doc
 divmod:
 db \\$-minus.x-1
 db '/mod', 4
 divmod.x:
 pop cx ; juggle return pointer
 xor dx, dx ; set dx := 0
 pop bx ; divisor is top element on stack
 pop ax ; dividend is next element
 div bx ; does dx:ax / bx remainder->dx; quotient->ax
 push dx ; put remainder on stack
 push ax ; put quotient on top of stack
 push cx ; restore return pointer
 ret

 fcall.doc:
 ; db 'Call a virtual proceedure on the bytecode '
 ; db 'stack machine'
 ; db 'The current code pointer (in the SI register)'
 ; db 'is saved - pushed '
 ; db 'onto the return stack and the address of the'
 ; db ' virtual proc '
 ; db ' to execute is loaded into SI. '
 ; dw \\$-fcall.doc
 fcall:
 db \\$-divmod.x-1
 db 'fcall', 5
 fcall.x:
 lodsw 
 mov [es:di], si
 add di, 2
 mov si, ax ; adjust the si code pointer 
 ret

 ; need to implement a return stack... maybe in DI
 ; destination ; index register.
 ; This will allow nested calls to procedures.
 
 exit.doc:
 ; db 'exit a virtual procedure by restoring si '
 ; db 'code pointer'
 ; dw \\$-exit.doc
 exit:
 ; not used interactively, so doesnt really need 
 ; name field
 db \\$-fcall.x-1
 db 'exit', 4
 exit.x:
 sub di, 2
 mov si, [es:di] ; restore si from rstack
 ret

 dup.doc:
 ; db 'Duplicates the top item on the stack.'
 ; dw \\$-dup.doc
 dup: 
 db \\$-exit.x-1
 db 'dup', 3 ; strings are 'counted' 
 dup.x:
 pop dx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push dx ; restore fn return address
 ret
 
 drop.doc:
 ; db 'removes the top item on the stack.'
 ; dw \\$-drop.doc
 drop: 
 db \\$-dup.x-1 ; relative back-link 
 db 'drop', 4 ; strings may be 'counted' 
 drop.x:
 pop dx ; juggle fn return address
 pop ax ; remove top element of stack
 push dx ; restore fn return address
 ret
 
 swap.doc:
 ; db 'swaps the top 2 items on the stack.'
 ; dw \\$-swap.doc
 swap: 
 db \\$-drop.x-1 ; link to previous word 
 db 'sw', 2
 swap.x:
 pop dx ; juggle fn return address
 pop ax ; get top stack item 
 pop bx ; get next stack item
 push ax ; put them back on in reverse order
 push bx
 push dx ; restore fn return address
 ret
 
 cstore.doc:
 ; db '( n addr -- ) '
 ; db 'store the byte value n at address adr.'
 ; db ' eg: 10 myvar c! '
 ; db ' puts the value 10 at the address specified 
 ; db ' by "myvar" '
 ; dw \\$-cstore.doc
 cstore: 
 db \\$-swap.x-1 ; link to previous word 
 db 'c!', 2
 cstore.x:
 pop dx ; juggle fn return address
 pop bx ; pointer to address
 pop ax ; value to store at address 
 mov [bx], al ; only the low value byte is stored
 push dx ; restore fn return address
 ret
 
 cfetchplus.doc:

 ; db '( adr -- adr+1 n ) '
 ; db ' Replace top element of the stack with value '
 ; db ' of the byte at given memory address and +1'
 ; db ' address . This is the same as "count"'
 ; dw \\$-cfetchplus.doc

 cfetchplus: 
 db \\$-cstore.x-1 ; link to previous word 
 db 'c@+', 3
 cfetchplus.x:
 pop dx ; juggle fn return address
 pop bx
 xor ax, ax ; set ax := 0
 mov al, byte [bx]
 inc bx ; increment address by 1
 push bx ; save address on stack
 push ax ; save value on top of stack
 push dx ; restore fn return address
 ret
 
 ; not used interactively so, probably doesnt
 ; need name field. Also probably doesnt need 
 ; link field either since it wont be looked up
 ; in the dictionary.
 lit.doc:
 ; db 'Pushes an 8 bit literal value onto the stack'
 ; db 'The literal value is encoded in the next byte '
 ; db 'after this instruction. This is similar to the '
 ; db 'forth "char" word. '
 ; dw \\$-lit.doc
 lit: 
 ;*** link field unnecessary ???
 ; db \\$-cfetchplus.x-1; relative backlink 
 ;*** name field unnecessary ???
 ; db 'lit', 3 
 lit.x:
 pop dx ; juggle fn return address
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get literal char into AL 
 ; cbw ; convert signed byte al to 
 ; signed word ax (neg offset)
 push ax ; put literal value on stack
 push dx ; restore fn return address
 ret
 
 litw.doc:
 ; db 'Pushes an 16 bit literal value onto the stack'
 ; dw \\$-litw.doc
 litw: 
 ;*** link field unnecessary ???
 ;db \\$-lit.x-1 ; link to previous word 
 ;*** name field unnecessary ???
 ;db 'litw', 4 
 litw.x:
 pop dx ; juggle fn return address
 lodsw ; ax := [si]++ get literal char into AX
 push ax ; put literal value on stack
 push dx ; restore fn return address
 ret
 
 emit.doc:
 ; db 'displays top item on stack as an ascii character.'
 ; db 'char is in the low byte of the stack item...'
 ; dw \\$-emit.doc
 emit:
 db \\$-cfetchplus.x-1 ; skip lit/litw (not interactive)
 db 'emit', 4
 emit.x:
 pop bx ; juggle return pointer
 pop ax ; char in al
 push bx
 mov ah, 0x0E ; bios teletype function 
 int 10h ; x86 bios 
 ret

 key.doc:
 ; db 'Get one keystroke from user and place on stack'
 ; db 'The key is represented as an ascii code 
 ; db 'in the low byte of the stack item.'
 ; db 'might as well use ekey instead.'
 ; dw \\$-key.doc
 key: 
 db \\$-emit.x-1 ; relative link to prev
 db 'key', 3 ; reverse counted string
 key.x:
 mov ah, 0 ; wait for keypress bios function
 int 16h ; ah := asci code and al := scan code
 pop bx ; juggle function return pointer
 mov ah, 0 ; set ah = 0
 push ax ; save asci code onto stack, high byte zero
 push bx ; restore return pointer to stack
 ret

 jump.doc:
 ; db ' ( -- ) stack is unchanged.
 ; db ' Jumps to a relative virtual instruction.'
 ; db ' The relative jump is given in the next byte.'
 ; db ' eg: JUMP, -2, jumps back 2 instructions 
 ; db ' in the bytecode' 
 ; db ' eg: LIT, '*', EMIT, JUMP, -3, ' 
 ; db ' prints a never-ending list of asterixes '
 ; dw \\$-jump.doc
 jump: 
 ;*** not interactive so no link field
 ;db \\$-key.x-1 
 ;*** not interactive so no name field
 ;db 'j', 1 
 jump.x:
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get relative jump target into AL
 cbw ; convert signed byte al to 
 ; signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 ret

 jumpz.doc:
 ; db ' ( n -- )
 ; db 'jumps to a relative virtual instruction if top '
 ; db 'stack element is zero. The flag value is removed '
 ; db 'from the stack
 ; db ' The relative jump is given in the next byte.'
 ; db ' eg: JUMPZ, -2, jumps back 2 instructions in the bytecode' 
 ; db ' eg: KEY, DUP, EMIT, LIT, '0', MINUS, JUMPNZ, -6 ' 
 ; db ' allows the user to type until zero is pressed. '
 ; dw \\$-jump.doc
 ; 
 ; jumps are not used interactively so probably 
 ; dont need a name field.
 jumpz: 
 ;*** not interactive, no link/name field
 ;db \\$-jump.x-1
 ;db 'jz', 2 
 jumpz.x:
 pop dx ; juggle return pointer
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ relative jump target into AL

 ;*** check stack for zero, 
 ; if not continue with next instruction 
 pop bx ; get top stack item into bx
 cmp bx, 0 ; if dx != 0 continue
 jne .exit
 cbw ; convert signed byte al to 
 ; signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 .exit:
 push dx ; restore call return
 ret

 read.doc:
 ; db ' ( sect n -- adr ) 
 ; db ' reads n sectors from disk starting at sect '
 ; db ' Return the address where sectors were loaded. '
 ; db ' In standard forths, load actually interprets '
 ; db ' source code. '
 ; dw \\$-read.doc
 read: 
 db \\$-key.x-1
 db 'r', 1
 read.x:
 ; sect n
 .reset: ; Reset the virtual floppy drive (usb)
 mov ax, 0 ; 
 mov dl, 0 ; assume drive is zero 
 ;mov dl, byte [drive.d]
 ;boot drive number (eg for usb 128)
 int 13h ;
 jc .reset ; ERROR => reset again
 .read:

 ;*** need to save es since rstack points with it!!
 mov [save.es], es

 ;*** dictionary is 512bytes, 
 ; Load just after stack segment 
 mov ax, ss ; cs is 07c0 hex ?! 
 mov es, ax ; es:bx determines where data loaded to

 ;*** or ax=1100h bx=0 is 4K after 64K segment
 mov bx, 0 ; ES:BX = ss:0

 pop dx ; juggle fn return
 pop ax ; n==number of sectors
 push dx ; restore fn return

 mov ah, 2 ; Load disk data to ES:BX
 ;mov al, 2 ; Load 2 sectors 512 bytes * 2 == 1K 

 ; try mov cx, 0x0002 ; cylinder 0, sector 2

 pop dx ; juggle fn return
 pop cx ; sect==start sector
 push dx ; restore fn return

 ;*** 2 is first sector after 512byte dictionary
 mov ch, 0 ; Cylinder=0
 ;mov cl, 10 ; Sector=10 (sector 1 = boot sector)

 mov dh, 0 ; Head=0
 ;mov dl, byte [drive.d] ; 
 mov dl, 0 ; 
 int 13h ; Read!
 jc .read ; ERROR => Try again
 
 ;*** restore es since rstack points with it!!
 mov es, [save.es]
 pop dx ; juggle fn return
 push ss ; return address where loaded 
 push dx ; restore fn return
 ret

 ;*** load alters es (used for rstack) 
 ;*** need to save
 save.es: dw 0
 
 ; *******************************
 ; end of byte codes, 512 byte system
 ; *******************************

 ; see ddot.p below for how to rewrite this
 ; but we need >r r>. Or just print 5 digits always
 udot.doc
 ; db ' ( n -- ) '
 ; db ' display top stack item as unsigned decimal. '
 udot:
 db \\$-read.x-1 ; skip jump/jumpz
 db 'u.', 2 
 udot.p:
 ; using 11 as a marker to know how many digits to print, but silly
 db LIT, 11, SWAP ; 11 n
 db LIT, 10 ; 11 n 10
 db DIVMOD ; 11 rem quotient
 db DUP, JUMPZ, 4
 db JUMP, -6
 ; 11 rem rem rem ... 0
 db DROP ; 11 rem rem ... 
 db LIT, '0', PLUS, EMIT ; 11 rem ... print remainder
 db DUP, LIT, 11, MINUS, JUMPZ, 4 
 db JUMP, -10
 db DROP
 db LIT, ' ', EMIT
 db EXIT 

 type.doc:
 ; db ' ( adr n -- ) '
 ; db ' Prints out n number of characters starting at address adr. '
 type:
 db \\$-udot.p-1 
 db 'type', 4
 type.p: 
 ; adr n
 .nextchar:
 db SWAP ; n adr 
 db CFETCHPLUS ; n adr+1 a
 db EMIT ; n adr+1
 db SWAP ; adr+1 n
 db LIT, 1 ; adr+1 n -1
 db MINUS ; adr+1 n-1
 db DUP ; adr+1 n-1 n-1
 db JUMPZ, 4 ; adr+1 n-1
 db JUMP, .nextchar-\\$
 db EXIT

 accept.doc:
 ; db ' ( buffer -- )
 ; db ' receive a line of input from the terminal '
 ; db ' and store it as a counted string in the buffer. '
 ; db ' This should be rewritten to discard excess chars.'
 ; also need to handle backspaces to backtrack over
 ; buffer
 accept:
 db \\$-type.p-1
 db 'ac', 2
 accept.p: 
 ; ( adr -- )
 db DUP, DUP ; a a a
 .nextchar:
 db LIT, 1, PLUS
 db DUP ; a a a+1 a+1 
 db KEY ; a a a+1 a+1 'x' 
 db DUP ; a a a+1 a+1 'x' 'x'
 db EMIT, DUP ; a a a+1 a+1 'x' 'x'
 db LIT, 13, MINUS, JUMPZ, 6 ; a a a+1 a+1 'x'

 db SWAP ; a a a+1 'x' a+1
 db CSTORE ; a a a+1 /put char in buffer
 db JUMP, .nextchar-\\$ ; not newline so get another char
 db LIT, 10, EMIT ; print newline if enter pressed
 ; a a a+n a+n 'x'
 db DROP, DROP ; a a a+n 
 db LIT, -1, PLUS ; a a a+n-1 
 db SWAP, MINUS ; a n-1
 db SWAP, CSTORE ; [a] := n-1
 db EXIT ; all virtual procedures end with 'exit'

 pad: db 9
 db '512b hey!'
 buff: db 0, ' '

 ; wow db KEY, EMIT, EXIT
 ; testing 512 one sector byte code

 code:

 db LITW
 dw pad
 db CFETCHPLUS 
 db FCALL
 dw type.p
 db 0

 .nextkey:
 db KEY, DUP, EMIT ; k
 db DUP, LIT, 'a' ; k k a
 db MINUS ; k k-a
 db JUMPZ, .accept-\\$ 
 ;db FCALL
 ;dw udot.p ; a+1 
 db EMIT, LIT, ' ', EMIT 
 db JUMP, .nextkey-\\$ 
 db 0
 .accept:
 db LIT, '!', EMIT
 db JUMP, .nextkey-\\$

 start:

 ;mov ax, 07C0h ; Set data segment to where we're loaded
 mov ax, cs ; cs already correct (07c0 hex ?!) 
 mov ds, ax ; data segment 

 ;*** set up rstack *p (es:di) just after code
 add ax, 32 ; 32 * 16 = 512byte = 1 sector
 mov es, ax ; using es:di as return stack pointer 
 mov di, 0

 ;*** cs/ss/ds/es * 16 gives absolute address
 add ax, 32 ; (4096+512+512)/16 
 mov ss, ax ; a 4K stack here
 mov sp, 1024 ; set up the stack pointer

 mov ah, 0eh ; print char func
 mov al, '\\&' ; 
 int 10h

 push code 
 call exec.x

 forever: jmp forever

 times 510-(\\$-\\$\\$) db 0 ; Pad boot rest of sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ;db 'Something to load in 2nd Sector'
 ,,,


BYTECODE 512 BYTES REGISTER TOP OF STACK

 This version puts the top of stack into the AX
 register. The next item of the stack is usually 
 buried just under the fn return pointer of the
 opcode that is executing.


 A 512 byte system is useful as a minimalist interactive
 bootloader and debugger.

 We can reduce the size of this, in a number of ways
 . relative backlinks to previous words in dictionary
 uses 1 byte vs 2 bytes per word. Means a maximum
 of 255 bytes per word (should be ample)
 . Dont have a count byte. Make all words 1 or 2 in length
 . keep top element of stack in ax register. Saves 
 quite a few bytes in the opcodes. 
 
 
 * forth stack machine bytecode example within 512 bytes
 ----------------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0
 
 ;*** aliases for each bytecode
 ; these aliases are in the
 ; same order as the pointer table below

 ;*** opcodes 1,2,3,4 etc
 DUP equ 1
 DROP equ DUP+1
 SWAP equ DROP+1
 CSTORE equ SWAP+1
 CFETCHPLUS equ CSTORE+1
 COUNT equ CFETCHPLUS ; count is an alias for @+ 
 LIT equ COUNT+1
 LITW equ LIT+1
 EMIT equ LITW+1
 KEY equ EMIT+1
 PLUS equ KEY+1
 MINUS equ PLUS+1
 DIVMOD equ MINUS+1
 FCALL equ DIVMOD+1 
 EXIT equ FCALL+1 
 JUMP equ EXIT+1 
 JUMPZ equ JUMP+1 
 JUMPF equ JUMPZ ; jump false alias for jump zero 
 SEGS equ JUMPZ+1
 READ equ SEGS+1
 NOOP equ READ+1 ; nop is a good end-marker

 ;*** a table of code pointers. 
 ; The pointers have the same offset in
 ; table as the value of the opcode
 op.table:
 dw 0, dup.x, drop.x, swap.x, cstore.x, cfetchplus.x
 dw lit.x, litw.x, emit.x, key.x
 dw plus.x, minus.x
 dw divmod.x, fcall.x, exit.x
 dw jump.x, jumpz.x, segs.x, read.x

 ; this is the function which executes the byte codes
 ; takes a pointer to the code. 
 ; Jumps are relative to the first
 ; byte of the jump instruction
 
 ; using relative backlinks to previous dictionary 
 ; entry to save 1 byte, could squash a count in there
 ; as well maybe
 exec:
 db 0 ; zero link means top of dictionary
 db 'ex', 2 ; this is not used interactively
 ; so probably doesnt need name field
 exec.x:
 ; save the return ip for 'exec' since the code
 ; below "call [op.table+bx]" changes the stack and 
 ; registers. Before this technique the same byte
 ; code would get executed over and over again because
 ; exec was not returning properly

 ;*** or pop fs ???
 pop word [returnexec] ; save return ip
 pop si ; get pointer to code
 .nextopcode:
 mov dx, ax ; ax is TOS, save in dx
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++
 cmp al, 0 ; zero marks end of code
 je .exit

 .opcode:
 mov bx, ax ; get opcode (1-6 etc) into bx
 shl bx, 1 ; double bx because its a word pointer 
 mov ax, dx ; restore ax TOS
 call [op.table+bx] ; opcode is offset into code *p table
 jmp .nextopcode
 .exit:
 push word [returnexec] ; restore fn return ip
 ret

 returnexec dw 0

 plus.doc:
 ; db ' ( n1 n2 -- n1+n2 ) '
 ; db ' add the top 2 elements of the stack.'
 ; dw \\$-plus.doc
 plus:
 db \\$-exec.x-1 ; a relative link, saves 1 byte!
 db '+', 1
 plus.x:
 pop dx ; juggle return pointer
 pop bx
 pop ax
 add ax, bx
 push ax
 push dx ; restore return pointer
 ret

 minus.doc:
 ; db 'subtract the top element of stack from next top'
 ; db ' ( n1 n2 -- n1-n2 ) '
 ; dw \\$-minus.doc
 minus:
 db \\$-plus.x-1 ; relative backlink
 db '-', 1
 minus.x:
 pop dx ; juggle return pointer
 pop bx
 pop ax
 sub ax, bx
 push ax
 push dx ; restore return pointer
 ret

 divmod.doc:
 ; db '(n1 n2 - remainder quotient) '
 ; db ' divide n1 by n2 and provide remainder and quotient. '
 ; db ' n2 is the top item on the stack '
 ; dw \\$-divmod.doc
 divmod:
 db \\$-minus.x-1
 db '/mod', 4
 divmod.x:
 pop cx ; juggle return pointer
 xor dx, dx ; set dx := 0
 pop bx ; divisor is top element on stack
 pop ax ; dividend is next element
 div bx ; does dx:ax / bx remainder->dx; quotient->ax
 push dx ; put remainder on stack
 push ax ; put quotient on top of stack
 push cx ; restore return pointer
 ret

 fcall.doc:
 ; db 'Call a virtual proceedure on the bytecode '
 ; db 'stack machine'
 ; db 'The current code pointer (in the SI register)'
 ; db 'is saved - pushed '
 ; db 'onto the return stack and the address of the'
 ; db ' virtual proc '
 ; db ' to execute is loaded into SI. '
 ; dw \\$-fcall.doc
 fcall:
 db \\$-divmod.x-1
 db 'fcall', 5
 fcall.x:
 lodsw 
 mov [es:di], si
 add di, 2
 mov si, ax ; adjust the si code pointer 
 ret

 ; need to implement a return stack... maybe in DI
 ; destination ; index register.
 ; This will allow nested calls to procedures.
 
 exit.doc:
 ; db 'exit a virtual procedure by restoring si '
 ; db 'code pointer'
 ; dw \\$-exit.doc
 exit:
 ; not used interactively, so doesnt really need 
 ; name field
 db \\$-fcall.x-1
 db 'exit', 4
 exit.x:
 sub di, 2
 mov si, [es:di] ; restore si from rstack
 ret

 dup.doc:
 ; db 'Duplicates the top item on the stack.'
 ; dw \\$-dup.doc
 dup: 
 db \\$-exit.x-1
 db 'dup', 3 ; strings are 'counted' 
 dup.x:
 pop dx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push dx ; restore fn return address
 ret
 
 drop.doc:
 ; db 'removes the top item on the stack.'
 ; dw \\$-drop.doc
 drop: 
 db \\$-dup.x-1 ; relative back-link 
 db 'drop', 4 ; strings may be 'counted' 
 drop.x:
 pop dx ; juggle fn return address
 pop ax ; remove top element of stack
 push dx ; restore fn return address
 ret
 
 swap.doc:
 ; db 'swaps the top 2 items on the stack.'
 ; dw \\$-swap.doc
 swap: 
 db \\$-drop.x-1 ; link to previous word 
 db 'sw', 2
 swap.x:
 pop dx ; juggle fn return address
 pop ax ; get top stack item 
 pop bx ; get next stack item
 push ax ; put them back on in reverse order
 push bx
 push dx ; restore fn return address
 ret
 
 cstore.doc:
 ; db '( n addr -- ) '
 ; db 'store the byte value n at address adr.'
 ; db ' eg: 10 myvar c! '
 ; db ' puts the value 10 at the address specified 
 ; db ' by "myvar" '
 ; dw \\$-cstore.doc
 cstore: 
 db \\$-swap.x-1 ; link to previous word 
 db 'c!', 2
 cstore.x:
 pop dx ; juggle fn return address
 pop bx ; pointer to address
 pop ax ; value to store at address 
 mov [bx], al ; only the low value byte is stored
 push dx ; restore fn return address
 ret
 
 cfetchplus.doc:

 ; db '( adr -- adr+1 n ) '
 ; db ' Replace top element of the stack with value '
 ; db ' of the byte at given memory address and +1'
 ; db ' address . This is the same as "count"'
 ; dw \\$-cfetchplus.doc

 cfetchplus: 
 db \\$-cstore.x-1 ; link to previous word 
 db 'c@+', 3
 cfetchplus.x:
 pop dx ; juggle fn return address
 pop bx
 xor ax, ax ; set ax := 0
 mov al, byte [bx]
 inc bx ; increment address by 1
 push bx ; save address on stack
 push ax ; save value on top of stack
 push dx ; restore fn return address
 ret
 
 ; not used interactively so, probably doesnt
 ; need name field. Also probably doesnt need 
 ; link field either since it wont be looked up
 ; in the dictionary.
 lit.doc:
 ; db 'Pushes an 8 bit literal value onto the stack'
 ; db 'The literal value is encoded in the next byte '
 ; db 'after this instruction. This is similar to the '
 ; db 'forth "char" word. '
 ; dw \\$-lit.doc
 lit: 
 ;*** link field unnecessary ???
 ; db \\$-cfetchplus.x-1; relative backlink 
 ;*** name field unnecessary ???
 ; db 'lit', 3 
 lit.x:
 pop dx ; juggle fn return address
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get literal char into AL 
 ; cbw ; convert signed byte al to 
 ; signed word ax (neg offset)
 push ax ; put literal value on stack
 push dx ; restore fn return address
 ret
 
 litw.doc:
 ; db 'Pushes an 16 bit literal value onto the stack'
 ; dw \\$-litw.doc
 litw: 
 ;*** link field unnecessary ???
 ;db \\$-lit.x-1 ; link to previous word 
 ;*** name field unnecessary ???
 ;db 'litw', 4 
 litw.x:
 pop dx ; juggle fn return address
 lodsw ; ax := [si]++ get literal char into AX
 push ax ; put literal value on stack
 push dx ; restore fn return address
 ret
 
 emit.doc:
 ; db 'displays top item on stack as an ascii character.'
 ; db 'char is in the low byte of the stack item...'
 ; dw \\$-emit.doc
 emit:
 db \\$-cfetchplus.x-1 ; skip lit/litw (not interactive)
 db 'emit', 4
 emit.x:
 pop bx ; juggle return pointer
 pop ax ; char in al
 push bx
 mov ah, 0x0E ; bios teletype function 
 int 10h ; x86 bios 
 ret

 key.doc:
 ; db 'Get one keystroke from user and place on stack'
 ; db 'The key is represented as an ascii code 
 ; db 'in the low byte of the stack item.'
 ; db 'might as well use ekey instead.'
 ; dw \\$-key.doc
 key: 
 db \\$-emit.x-1 ; relative link to prev
 db 'key', 3 ; reverse counted string
 key.x:
 mov ah, 0 ; wait for keypress bios function
 int 16h ; ah := asci code and al := scan code
 pop bx ; juggle function return pointer
 mov ah, 0 ; set ah = 0
 push ax ; save asci code onto stack, high byte zero
 push bx ; restore return pointer to stack
 ret

 jump.doc:
 ; db ' ( -- ) stack is unchanged.
 ; db ' Jumps to a relative virtual instruction.'
 ; db ' The relative jump is given in the next byte.'
 ; db ' eg: JUMP, -2, jumps back 2 instructions 
 ; db ' in the bytecode' 
 ; db ' eg: LIT, '*', EMIT, JUMP, -3, ' 
 ; db ' prints a never-ending list of asterixes '
 ; dw \\$-jump.doc
 jump: 
 ;*** not interactive so no link field
 ;db \\$-key.x-1 
 ;*** not interactive so no name field
 ;db 'j', 1 
 jump.x:
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get relative jump target into AL
 cbw ; convert signed byte al to 
 ; signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 ret

 jumpz.doc:
 ; db ' ( n -- )
 ; db 'jumps to a relative virtual instruction if top '
 ; db 'stack element is zero. The flag value is removed '
 ; db 'from the stack
 ; db ' The relative jump is given in the next byte.'
 ; db ' eg: JUMPZ, -2, jumps back 2 instructions in the bytecode' 
 ; db ' eg: KEY, DUP, EMIT, LIT, '0', MINUS, JUMPNZ, -6 ' 
 ; db ' allows the user to type until zero is pressed. '
 ; dw \\$-jump.doc
 ; 
 ; jumps are not used interactively so probably 
 ; dont need a name field.
 jumpz: 
 ;*** not interactive, no link/name field
 ;db \\$-jump.x-1
 ;db 'jz', 2 
 jumpz.x:
 pop dx ; juggle return pointer
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ relative jump target into AL

 ;*** check stack for zero, 
 ; if not continue with next instruction 
 pop bx ; get top stack item into bx
 cmp bx, 0 ; if dx != 0 continue
 jne .exit
 cbw ; convert signed byte al to 
 ; signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 .exit:
 push dx ; restore call return
 ret

 segs.doc:
 ; db ' ( -- cs ds es ss sp ) 
 ; db ' pushes segment register values and stack '
 ; db ' pointer onto the stack. Useful for debugging '
 ; db ' an x86 system. '
 ; dw \\$-segs.doc
 segs: 
 db \\$-key.x-1
 db 'seg', 3
 segs.x:
 pop dx ; ret *p
 push cs
 push ds
 push es
 push ss
 push sp
 push dx
 ret

 read.doc:
 ; db ' ( sect n -- adr ) 
 ; db ' reads n sectors from disk starting at sect '
 ; db ' Return the address where sectors were loaded. '
 ; db ' In standard forths, load actually interprets '
 ; db ' source code. '
 ; dw \\$-read.doc
 read: 
 db \\$-segs.x-1
 db 'r', 1
 read.x:

 ; sect n
 .reset: ; Reset the virtual floppy drive (usb)
 mov ax, 0 ; 
 mov dl, 0 ; assume drive is zero 
 ;mov dl, byte [drive.d]
 ;boot drive number (eg for usb 128)
 int 13h ;
 jc .reset ; ERROR => reset again
 .read:

 ;*** need to save es since rstack points with it!!
 mov [save.es], es

 ;*** dictionary is 512bytes, 
 ; Load just after stack segment 
 mov ax, ss ; cs is 07c0 hex ?! 
 mov es, ax ; es:bx determines where data loaded to

 ;*** or ax=1100h bx=0 is 4K after 64K segment
 mov bx, 0 ; ES:BX = ss:0

 pop dx ; juggle fn return
 pop ax ; n==number of sectors
 push dx ; restore fn return

 mov ah, 2 ; Load disk data to ES:BX
 ;mov al, 2 ; Load 2 sectors 512 bytes * 2 == 1K 

 ; try mov cx, 0x0002 ; cylinder 0, sector 2

 pop dx ; juggle fn return
 pop cx ; sect==start sector
 push dx ; restore fn return

 ;*** 2 is first sector after 512byte dictionary
 mov ch, 0 ; Cylinder=0
 ;mov cl, 10 ; Sector=10 (sector 1 = boot sector)

 mov dh, 0 ; Head=0
 ;mov dl, byte [drive.d] ; 
 mov dl, 0 ; 
 int 13h ; Read!
 jc .read ; ERROR => Try again
 
 ;*** restore es since rstack points with it!!
 mov es, [save.es]
 pop dx ; juggle fn return
 push ss ; return address where loaded 
 push dx ; restore fn return
 ret

 ;*** load alters es (used for rstack) 
 ;*** need to save
 save.es: dw 0
 
 ; *******************************
 ; end of byte codes, 512 byte system
 ; *******************************

 ; see ddot.p below for how to rewrite this
 ; but we need >r r>. Or just print 5 digits always
 udot.doc
 ; db ' ( n -- ) '
 ; db ' display top stack item as unsigned decimal. '
 udot:
 db \\$-read.x-1 ; skip jump/jumpz
 db 'u.', 2 
 udot.p:
 ; using 11 as a marker to know how many digits to print, but silly
 db LIT, 11, SWAP ; 11 n
 db LIT, 10 ; 11 n 10
 db DIVMOD ; 11 rem quotient
 db DUP, JUMPZ, 4
 db JUMP, -6
 ; 11 rem rem rem ... 0
 db DROP ; 11 rem rem ... 
 db LIT, '0', PLUS, EMIT ; 11 rem ... print remainder
 db DUP, LIT, 11, MINUS, JUMPZ, 4 
 db JUMP, -10
 db DROP
 db LIT, ' ', EMIT
 db EXIT 

 type.doc:
 ; db ' ( adr n -- ) '
 ; db ' Prints out n number of characters starting at address adr. '
 type:
 db \\$-udot.p-1 
 db 'type', 4
 type.p: 
 ; adr n
 .nextchar:
 db SWAP ; n adr 
 db CFETCHPLUS ; n adr+1 a
 db EMIT ; n adr+1
 db SWAP ; adr+1 n
 db LIT, 1 ; adr+1 n -1
 db MINUS ; adr+1 n-1
 db DUP ; adr+1 n-1 n-1
 db JUMPZ, 4 ; adr+1 n-1
 db JUMP, .nextchar-\\$
 db EXIT

 accept.doc:
 ; db ' ( buffer -- )
 ; db ' receive a line of input from the terminal '
 ; db ' and store it as a counted string in the buffer. '
 ; db ' This should be rewritten to discard excess chars.'
 ; also need to handle backspaces to backtrack over
 ; buffer
 accept:
 db \\$-type.p-1
 db 'ac', 2
 accept.p: 
 ; cant afford full accept, could just
 ; push key strokes onto stack and count

 db EXIT

 pad: db 9
 db '512b hi!'

 ; wow db KEY, EMIT, EXIT
 ; testing 512 one sector byte code

 code:

 db LIT, 3, LIT, 2, PLUS
 db FCALL
 dw udot.p
 db LITW
 dw pad
 db CFETCHPLUS 
 db FCALL
 dw type.p
 .nextkey:
 db KEY, DUP, EMIT ; k
 db DUP, LIT, 'r' ; k k a
 db MINUS ; k k-a
 db JUMPZ, .read-\\$ 
 db DUP, LIT, 's' ; k k a
 db MINUS ; k k-a
 db JUMPZ, .print-\\$ 
 db LIT, ' ', EMIT 
 db JUMP, .nextkey-\\$ 
 db 0
 .read:
 db LIT, ':', EMIT 
 db LIT, 2, LIT, 1, READ
 db JUMP, .nextkey-\\$
 .print:
 db SEGS 
 db FCALL
 dw udot.p
 db LIT, ' ', EMIT
 db FCALL
 dw udot.p
 db LIT, ' ', EMIT
 db JUMP, .nextkey-\\$

 start:

 ;mov ax, 07C0h ; Set data segment to where we're loaded
 mov ax, cs ; cs already correct (07c0 hex ?!) 
 mov ds, ax ; data segment 

 ;*** set up rstack *p (es:di) just after code
 add ax, 32 ; 32 * 16 = 512byte = 1 sector
 mov es, ax ; using es:di as return stack pointer 
 mov di, 0

 ;*** cs/ss/ds/es * 16 gives absolute address
 add ax, 320 ; (4096+512+512)/16 
 mov ss, ax ; a 4K stack here
 mov sp, 4096 ; set up the stack pointer

 push code 
 call exec.x

 forever: jmp forever

 times 510-(\\$-\\$\\$) db 0 ; Pad boot rest of sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ;db 'Something to load in 2nd Sector'
 ,,,


BYTECODE SYSTEM MULTIPLE SECTORS

 This chapter contains a working bootable readonly forth
 style system. But I will separate this into its own .asm file
 in order to develope it further.

 * bytecode system with multiple sectors
 ------------------------------
 ; history
 ; 10 june 2017 
 ; made a return stack with es:di and made fcall.x and exit.x
 ; use the return stack, apparently successfully which allows
 ; nested procedures.

 BITS 16
 [ORG 0]

 jmp 07C0h:bootload ; Goto segment 07C0
 drive: db 0 ; a variable to hold boot drive number
 db 'bootload...'
 bootload:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 mov [drive], dl ; save the boot drive number
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax ; with a 4K gap between stack and code
 mov sp, 4096

 ; save the DL register or else dont modify it
 ; it contains the number of the boot medium (hard disk,
 ; usb memory stick etc)
 ; The 'floppy' Drive is NOT necesarily 0!!!

 reset: ; Reset the virtual floppy drive (usb)
 mov ax, 0 ; 
 mov dl, [drive] ; the boot drive number (eg for usb 128)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ; es:bx determines where data loaded to
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 8 ; Load 8 sectors 512 bytes * 8 == 4K 
 ; try mov cx, 0x0002 ; cylinder 0, sector 2
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2 (sector 1 is the boot sector)
 mov dh, 0 ; Head=0
 mov dl, [drive] ; 
 int 13h ; Read!
 jc read ; ERROR => Try again

 jmp 1000h:0000 ; Jump to the loaded code 

 times 510-(\\$-\\$\\$) db 0 ; pad out the boot sector
 ; (512 bytes)
 dw 0AA55h ; end with standard boot signature

 ; ****
 ; this below is the magic line to make the new memory offsets
 ; work. Or compile the 2 files separately
 ; https://forum.nasm.us/index.php?topic=2160.0 

 section stage2 vstart=0

 jmp start

 ; using reverse name counts. 
 ; This allows us to decompile
 ; byte code by getting a list of pointers to 
 ; code and then looking up name
 ; eg 
 ; db 'minus', 5
 ; dw exec ; link to previous
 ; Another refinement is to hold the top element of the 
 ; stack in ax, which simplifies a lot of stack manipulation.
 ; eg 1+ becomes "inc ax" etc

 ; aliases for each bytecode, these aliases need to be in the
 ; same order as the pointer table below
 ; The nasm code below gives values of 1,2,3,4,5 etc to each 
 ; bytecode alias. A new opcode can be inserted without having
 ; to update all the following opcodes.

 ;**** to do:
 ; stack underflow check?
 ; read/write sector block to memory
 ; set video mode opcodes
 ; colour emit, type

 DUP equ 1
 DROP equ DUP+1
 SWAP equ DROP+1
 OVER equ SWAP+1
 TWODUP equ OVER+1
 DEPTH equ TWODUP+1
 RDEPTH equ DEPTH+1
 RON equ RDEPTH+1
 ROFF equ RON+1
 STORE equ ROFF+1
 STOREPLUS equ STORE+1
 FETCH equ STOREPLUS+1
 FETCHPLUS equ FETCH+1
 CSTORE equ FETCHPLUS+1
 CSTOREPLUS equ CSTORE+1
 CFETCH equ CSTOREPLUS+1
 CFETCHPLUS equ CFETCH+1
 COUNT equ CFETCHPLUS ; count is an alias for c@+
 EQUALS equ CFETCHPLUS+1
 NOTEQUALS equ EQUALS+1
 LESSTHAN equ NOTEQUALS+1
 ULESSTHAN equ LESSTHAN+1
 LIT equ ULESSTHAN+1
 LITW equ LIT+1
 EMIT equ LITW+1
 KEY equ EMIT+1
 EKEY equ KEY+1
 PLUS equ EKEY+1
 MINUS equ PLUS+1
 INCR equ MINUS+1
 DECR equ INCR+1 
 NEGATE equ DECR+1
 LOGOR equ NEGATE+1 ; logical or
 LOGXOR equ LOGOR+1
 LOGAND equ LOGXOR+1
 DIVMOD equ LOGAND+1
 TIMESTWO equ DIVMOD+1 
 MULT equ TIMESTWO+1 
 UMULT equ MULT+1 
 FCALL equ UMULT+1 
 PCALL equ FCALL+1
 EXIT equ PCALL+1 
 LJUMP equ EXIT+1 
 JUMP equ LJUMP+1 
 JUMPZ equ JUMP+1 
 JUMPF equ JUMPZ ; jumpf (false) is an aliase for jumpz 
 JUMPNZ equ JUMPZ+1 
 JUMPT equ JUMPNZ ; jump-true alias for jump-not-zero
 RLOOP equ JUMPNZ+1
 DIVTWO equ RLOOP+1
 LOAD equ DIVTWO+1 ; load a sector from disk
 SAVE equ LOAD+1
 NOOP equ SAVE+1 ; no operation \\& end marker 
 
 ;*** control bits and mask
 IMMEDIATE equ 0b10000000
 MASK equ 0b00011111

 ; a table of code pointers. The pointers have the 
 ; same offset in
 ; table as value of the opcode
 op.table:
 dw 0, dup.x, drop.x, swap.x, over.x, twodup.x
 dw depth.x, rdepth.x
 dw ron.x, roff.x
 dw store.x, storeplus.x, fetch.x, fetchplus.x
 dw cstore.x, cstoreplus.x
 dw cfetch.x, cfetchplus.x
 dw equals.x, notequals.x, 
 dw lessthan.x, ulessthan.x, lit.x, litw.x
 dw emit.x, key.x, ekey.x
 dw plus.x, minus.x, incr.x, decr.x, neg.x
 dw logor.x, logxor.x, logand.x
 dw divmod.x, timestwo.x, mult.x, umult.x
 dw fcall.x, pcall.x, exit.x
 dw ljump.x, jump.x, jumpz.x, jumpnz.x, rloop.x
 dw divtwo.x
 dw load.x, save.x
 dw noop.x, -1 


 ; this is the function which executes the byte codes
 ; takes a pointer to the code. Jumps are relative to the first
 ; byte of the jump instruction
 exec:
 dw 0 
 db 'exec', 4
 exec.x:
 ; save the return ip for 'exec' since the code
 ; below call [op.table+bx] changes the stack and 
 ; registers. Before this technique the same byte
 ; code would get executed over and over again because
 ; exec was not returning properly
 pop word [returnexec] ; save return ip
 pop si ; get pointer to code
 .nextopcode:
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++
 cmp al, 0 ; zero marks end of code
 je .exit

 .opcode:
 mov bx, ax ; get opcode (1-6 etc) into bx
 shl bx, 1 ; double bx because its a word pointer 
 call [op.table+bx] ; use opcode as offset into 
 ; code pointer table

 ;*** check for stack underflow here ???
 jmp .nextopcode
 .exit:
 push word [returnexec] ; restore fn return ip
 ret

 returnexec dw 0


 plus.doc:
 ; db 'add the top 2 elements of the stack.'
 ; db ' ( n1 n2 -- n1+n2 ) '
 ; db ' This opcode is agnostic about whether the two 16 bit '
 ; db ' numbers are signed or unsigned. What should happen in '
 ; db ' the case of an overflow ? '
 ; db ' eg: LIT, 4, LIT, '0', PLUS, EMIT '
 ; db ' displays the digit "4" '
 ; dw \\$-plus.doc
 plus:
 dw exec.x
 db '+', 1
 plus.x:
 pop dx ; juggle return pointer
 pop bx
 pop ax
 add ax, bx
 push ax
 push dx ; restore return pointer
 ret

 minus.doc:
 ; db 'subtract the top element of stack from next top'
 ; db ' ( n1 n2 -- n1-n2 ) '
 ; dw \\$-minus.doc
 minus:
 dw plus.x
 db '-', 1
 minus.x:
 pop dx ; juggle return pointer
 pop bx
 pop ax
 sub ax, bx
 push ax
 push dx ; restore return pointer
 ret

 incr.doc:
 ; db ' ( n -- n+1 )
 ; db 'Increment the top element of the data 
 ; db 'stack by one. '
 ; dw \\$-incr.doc
 incr:
 dw minus.x 
 db '1+', 2
 incr.x:
 pop dx ; juggle return pointer
 pop ax
 inc ax
 push ax
 push dx ; restore return pointer
 ret

 decr.doc:
 ; db ' ( n -- n-1 )
 ; db 'Decrement top element of the data stack by one. '
 ; dw \\$-decr.doc
 decr:
 dw incr.x 
 db '1-', 2
 decr.x:
 pop dx ; juggle return pointer
 pop ax
 dec ax
 push ax
 push dx ; restore return pointer
 ret

 neg.doc:
 ; db ' ( n -- -n )
 ; db 'Negates the top item of the stack'
 ; dw \\$-neg.doc
 neg:
 dw decr.x 
 db 'neg', 3
 neg.x:
 pop dx ; juggle return pointer
 pop ax
 neg ax
 push ax
 push dx ; restore return pointer
 ret

 logor.doc:
 ; db ' ( n1 n2 -- n1 V n2 )
 ; db 'the logical or of n1 and n2'
 ; dw \\$-logor.doc
 logor:
 dw neg.x 
 db 'or', 2
 logor.x:
 pop dx ; juggle return pointer
 pop ax
 pop bx
 or ax, bx
 push ax
 push dx ; restore return pointer
 ret

 logxor.doc:
 ; db ' ( n1 n2 -- n1 V n2 )
 ; db 'the logical or of n1 and n2'
 ; dw \\$-logxor.doc
 logxor:
 dw logor.x 
 db 'xor', 3
 logxor.x:
 pop dx ; juggle return pointer
 pop ax
 pop bx
 xor ax, bx
 push ax
 push dx ; restore return pointer
 ret

 logand.doc:
 ; db ' ( n1 n2 -- n1 \\&\\& n2 )
 ; db 'the logical and of n1 and n2'
 ; dw \\$-logand.doc
 logand:
 dw logxor.x 
 db 'and', 3
 logand.x:
 pop dx ; juggle return pointer
 pop ax
 pop bx
 and ax, bx
 push ax
 push dx ; restore return pointer
 ret

 divtwo.doc:
 ; db '(n1 - n1/2) '
 ; db ' divide n1 by 2 '
 ; dw \\$-divtwo.doc
 divtwo:
 dw logand.x 
 db '/2', 2
 divtwo.x:
 pop dx ; juggle return pointer
 pop ax ; dividend is next element
 shr ax, 1 ; do ax := (ax+1)/2
 push ax
 push dx ; restore return pointer
 ret

 divmod.doc:
 ; db '(n1 n2 - remainder quotient) '
 ; db ' divide n1 by n2 and provide remainder and quotient. '
 ; db ' n2 is the top item on the stack '
 ; dw \\$-divmod.doc
 divmod:
 dw divtwo.x 
 db '/mod', 4
 divmod.x:
 pop cx ; juggle return pointer
 xor dx, dx ; set dx := 0
 pop bx ; divisor is top element on stack
 pop ax ; dividend is next element
 div bx ; does dx:ax / bx remainder->dx; quotient->ax
 push dx ; put remainder on stack
 push ax ; put quotient on top of stack
 push cx ; restore return pointer
 ret

 timestwo.doc:
 ; db '(n1 -- n1*2 ) '
 ; db ' double n1. This basically performs a '
 ; db ' left shift on the bits in n1 '
 ; dw \\$-timestwo.doc
 timestwo:
 dw divmod.x 
 db '*2', 2
 timestwo.x:
 pop dx ; juggle return pointer
 pop ax ; dividend is next element
 shl ax, 1 ;
 push ax ; 
 push dx ; restore return pointer
 ret

 mult.doc:
 ; db '(n1 n2 -- n1*n2 ) '
 ; db ' signed multiplication '
 ; dw \\$-mult.doc
 mult:
 dw timestwo.x 
 db '*', 1
 mult.x:
 pop dx ; juggle return pointer
 pop ax 
 pop bx
 ; ax * bx ...
 push ax 
 push dx ; restore return pointer
 ret

 umult.doc:
 ; db '(n1 n2 -- n1*n2 ) '
 ; db ' unsigned multiplication '
 ; dw \\$-umult.doc
 umult:
 dw mult.x 
 db 'u*', 2
 umult.x:
 pop cx ; juggle return pointer
 xor dx, dx ; set dx := 0
 pop ax 
 pop bx
 ; ax * bx ...
 mul bx ; do dx:ax := ax*bx 
 push ax 
 push cx ; restore return pointer
 ret

 fcall.doc:
 ; db 'Call a virtual proceedure on the bytecode '
 ; db 'stack machine'
 ; db 'The current code pointer (in the SI register)'
 ; db 'is saved - pushed '
 ; db 'onto the return stack and the address of the'
 ; db ' virtual proc '
 ; db ' to execute is loaded into SI. '
 ; dw \\$-fcall.doc
 fcall:
 dw umult.x
 db 'fcall', 5
 fcall.x:
 lodsw 
 mov [es:di], si
 add di, 2
 mov si, ax ; adjust the si code pointer 
 ret

 pcall.doc:
 ; db ' ( xt -- ) '
 ; db ' Call a procedure using the top element on '
 ; db ' the data stack as the execution address. This '
 ; db ' allows the implementation of function pointers'
 ; db ' In standard forths this is called "execute". '
 ; dw \\$-pcall.doc
 pcall:
 dw fcall.x
 db 'pcall', 5 ; or call it exec/ex/execute
 pcall.x:
 pop dx ; juggle return
 mov [es:di], si ; save ip to return stack
 add di, 2
 pop si ; get proc exec address from stack
 push dx
 ret

 exit.doc:
 ; db 'exit a virtual procedure by restoring si '
 ; db 'code pointer'
 ; dw \\$-exit.doc
 exit:
 dw pcall.x 
 db 'exit', 4
 exit.x:
 sub di, 2
 mov si, [es:di] ; restore si from rstack
 ret

 dup.doc:
 ; db 'Duplicates the top item on the stack.'
 ; dw \\$-dup.doc
 dup: 
 dw exit.x ; link to previous word 
 db 'dup', 3 ; strings are 'counted' 
 dup.x:
 pop dx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push dx ; restore fn return address
 ret
 
 drop.doc:
 ; db 'removes the top item on the stack.'
 ; dw \\$-drop.doc
 drop: 
 dw dup.x ; link to previous word 
 db 'drop', 4 ; strings are 'counted' 
 drop.x:
 pop dx ; juggle fn return address
 pop ax ; remove top element of stack
 push dx ; restore fn return address
 ret
 
 swap.doc:
 ; db 'swaps the top 2 items on the stack.'
 ; dw \\$-swap.doc
 swap: 
 dw drop.x ; link to previous word 
 db 'swap', 4
 swap.x:
 pop dx ; juggle fn return address
 pop ax ; get top stack item 
 pop bx ; get next stack item
 push ax ; put them back on in reverse order
 push bx
 push dx ; restore fn return address
 ret
 
 over.doc:
 ; db ' ( n1 n2 -- n1 n2 n1 ) '
 ; db ' Puts a copy of 2nd stack item on top of stack. '
 ; db ' dont use this, will probably remove. '
 ; dw \\$-over.doc
 over: 
 dw swap.x ; link to previous word 
 db 'over', 4
 over.x:
 pop dx ; juggle fn return address
 pop ax ; get top stack item 
 pop bx ; get next stack item
 push bx ; 
 push ax ; 
 push bx ; add copy of 2nd item on top of stack
 push dx ; restore fn return address
 ret
 
 twodup.doc:
 ; db ' ( n1 n2 -- n1 n2 n1 n2 ) '
 ; db ' copies 2 stack items onto stack '
 ; dw \\$-twodup.doc
 twodup: 
 dw over.x 
 db '2dup', 4
 twodup.x:
 pop dx ; juggle fn return address
 pop ax ; get top stack item 
 pop bx ; get next stack item
 push bx ; 
 push ax ; 
 push bx ; 
 push ax ; n1 n2 n1 n2
 push dx ; restore fn return address
 ret
 
 depth.doc:
 ; db ' ( -- n ) '
 ; db ' Puts on the stack the number of stack items '
 ; db ' before this word was executed '
 ; dw \\$-depth.doc
 depth: 
 dw twodup.x ; link to previous word 
 db 'depth', 5
 depth.x:
 pop dx ; juggle fn return address
 mov bx, sp
 mov ax, 4096 ; 4K stack (but could change!)
 sub ax, bx ; 
 shr ax, 1 ; div by 2 (2 byte stack cell)
 ;causing problems ???
 ;dec ax ; the exec.x call doesnt count
 push ax
 push dx ; restore fn return address
 ret
 
 rdepth.doc:
 ; db ' ( -- n ) '
 ; db ' Puts on the stack the number of stack items '
 ; db ' on the return stack before this word '
 ; db ' was executed '
 ; dw \\$-rdepth.doc
 rdepth: 
 dw depth.x 
 db 'rdepth', 6
 rdepth.x:
 pop dx ; juggle fn return address
 mov ax, di 
 shl ax, 1
 push ax
 push dx ; restore fn return address
 ret
 
 ron.doc:
 ; db '( S: n -- )( R: -- n ) '
 ; db ' put the top item of the data stack onto the return stack.'
 ; dw \\$-ron.doc
 ron: 
 dw rdepth.x 
 db '>r', 2
 ron.x:
 pop dx ; juggle fn return address
 pop ax ; value to store at address 
 stosw ; [es:di] := ax, di+2
 push dx ; restore fn return address
 ret
 
 roff.doc:
 ; db '( S: -- n)( R: n -- ) '
 ; db ' put the top item of the return stack onto the data stack.'
 ; dw \\$-roff.doc
 roff: 
 dw ron.x 
 db 'r>', 2
 roff.x:
 pop dx ; juggle fn return address
 sub di, 2 ; 
 mov ax, [es:di] ; get top item off return stack
 push ax
 push dx ; restore fn return address
 ret
 
 store.doc:
 ; db '( w adr -- ) '
 ; db ' place 2 byte value w at address "adr" '
 ; dw \\$-store.doc
 store: 
 dw roff.x 
 db '!', 1
 store.x:
 pop dx ; juggle fn return address
 pop bx ; pointer to address
 pop ax ; value to store at address 
 mov [bx], ax ; 2 byte is stored
 push dx ; restore fn return address
 ret
 
 storeplus.doc:
 ; db '( w adr -- adr+2 ) '
 ; dw \\$-storeplus.doc
 storeplus: 
 dw store.x ; link to previous word 
 db '!+', 2
 storeplus.x:
 pop dx ; juggle fn return address
 pop bx ; pointer to address
 pop ax ; value to store at address 
 mov [bx], ax ; 2 byte value is stored
 inc bx ; advance address and put on stack
 inc bx ; advance address and put on stack
 push bx 
 push dx ; restore fn return address
 ret
 
 fetch.doc:
 ; db '( adr -- n ) '
 ; db ' Replace the top element of the stack with the '
 ; db ' value of the 16bites at the given memory address '
 ; dw \\$-fetch.doc
 fetch: 
 dw storeplus.x ; link to previous word 
 db '@', 1
 fetch.x:
 pop dx ; juggle fn return address
 pop bx
 mov ax, word [bx]
 push ax ; save value on top of stack
 push dx ; restore fn return address
 ret
 
 fetchplus.doc:
 ; db '( adr -- adr+2 n ) '
 ; db ' Replace the top element of the stack with the '
 ; db ' value of the 16bites at the given memory address '
 ; db ' and increment the address by 2 bytes. '
 ; dw \\$-fetchplus.doc
 fetchplus: 
 dw fetch.x ; link to previous word 
 db '@+', 2
 fetchplus.x:
 pop dx ; juggle fn return address
 pop bx
 mov ax, word [bx]
 add bx, 2 ; increment address by 1 word (2 bytes)
 push bx ; save address on stack
 push ax ; save value on top of stack
 push dx ; restore fn return address
 ret
 
 cstore.doc:
 ; db '( n adr -- ) store the byte value n at address adr.'
 ; db ' eg: 10 myvar ! '
 ; db ' puts the value 10 at the address specified by "myvar" '
 ; db ' The address is the top value on the stack. '
 ; dw \\$-cstore.doc
 cstore: 
 dw fetchplus.x ; link to previous word 
 db 'c!', 2
 cstore.x:
 pop dx ; juggle fn return address
 pop bx ; pointer to address
 pop ax ; value to store at address 
 mov [bx], al ; only the low value byte is stored
 push dx ; restore fn return address
 ret
 
 cstoreplus.doc:
 ; db '( n adr -- adr+1 ) store the byte value n at address adr.'
 ; db ' And increment the address '
 ; dw \\$-cstoreplus.doc
 cstoreplus: 
 dw cstore.x ; link to previous word 
 db 'c!+', 3
 cstoreplus.x:

 pop dx ; juggle fn return address
 pop bx ; pointer to address
 pop ax ; value to store at address 
 mov [bx], al ; only the low value byte is stored
 inc bx ; advance address and put on stack
 push bx 
 push dx ; restore fn return address
 ret
 
 cfetch.doc:
 ; db '( adr -- n ) Replace the top element of the stack with the value '
 ; db ' of the byte at the given memory address.'
 ; db ' eg: myvar @ . '
 ; db ' displays the value at the address given by "myvar" '
 ; dw \\$-cfetch.doc
 cfetch: 
 dw cstoreplus.x ; link to previous word 
 db 'c@', 2
 cfetch.x:
 pop dx ; juggle fn return address
 pop bx
 xor ax, ax ; set ax := 0
 mov al, byte [bx]
 push ax
 push dx ; restore fn return address
 ret
 
 cfetchplus.doc:
 ; db '( adr -- adr+1 n ) '
 ; db ' Replace the top element of the stack with the value '
 ; db ' of the byte at the given memory address and increment the '
 ; db ' address . This is exactly the same as "count"'
 ; dw \\$-fetchplus.doc
 cfetchplus: 
 dw cfetch.x ; link to previous word 
 db 'c@+', 3
 cfetchplus.x:
 pop dx ; juggle fn return address
 pop bx
 xor ax, ax ; set ax := 0
 mov al, byte [bx]
 inc bx ; increment address by 1
 push bx ; save address on stack
 push ax ; save value on top of stack
 push dx ; restore fn return address
 ret
 
 equals.doc:
 ; db ' ( n1 n2 -- flag ) '
 ; db 'Puts -1 (true) on the stack if n1==n2 '
 ; db 'otherwise puts zero (false) on the stack. '
 ; dw \\$-equals.doc
 equals: 
 dw cfetchplus.x ; link to previous word 
 db '=', 1 
 equals.x:
 pop dx ; juggle fn return address
 pop ax ; top stack item 
 pop bx ; 2nd stack item
 cmp ax, bx
 je .true
 .false:
 push 0
 jmp .exit
 .true:
 push -1
 .exit:
 push dx ; restore fn return address
 ret
 
 notequals.doc:
 ; db ' ( n1 n2 -- flag ) '
 ; db 'Puts 0 (false) on the stack if n1==n2 '
 ; db 'otherwise puts -1 (true) on the data stack'
 ; dw \\$-notequals.doc
 notequals: 
 dw equals.x ; link to previous word 
 db '<>', 2 
 notequals.x:
 pop dx ; juggle fn return address
 pop ax ; top stack item 
 pop bx ; 2nd stack item
 cmp ax, bx
 jne .true
 .false:
 push 0
 jmp .exit
 .true:
 push -1
 .exit:
 push dx ; restore fn return address
 ret
 
 lessthan.doc:
 ; db ' ( n1 n2 -- flag ) '
 ; db 'Puts 0 (false) on the stack if n1<n2 '
 ; db 'otherwise puts -1 (true) on the data stack'
 ; db 'this is a signed comparison'
 ; dw \\$-lessthan.doc
 lessthan: 
 dw notequals.x 
 db '<', 1 
 lessthan.x:
 pop dx ; juggle fn return address
 pop ax ; top stack item 
 pop bx ; 2nd stack item
 cmp ax, bx
 jg .true ; jg is a signed comparison 
 .false:
 push 0
 jmp .exit
 .true:
 push -1
 .exit:
 push dx ; restore fn return address
 ret
 
 ulessthan.doc:
 ; db ' ( n1 n2 -- flag ) '
 ; db 'Puts 0 (false) on the stack if n1<n2 '
 ; db 'otherwise puts -1 (true) on the data stack'
 ; db 'this is an unsigned comparison'
 ; dw \\$-ulessthan.doc
 ulessthan: 
 dw lessthan.x 
 db 'u<', 2 
 ulessthan.x:
 pop dx ; juggle fn return address
 pop ax ; top stack item 
 pop bx ; 2nd stack item
 cmp ax, bx
 ja .true ; jg is an unsigned comparison 
 .false:
 push 0
 jmp .exit
 .true:
 push -1
 .exit:
 push dx ; restore fn return address
 ret
 
 ; maybe call this "char"
 lit.doc:
 ; db 'Pushes an 8 bit literal value onto the stack'
 ; dw \\$-lit.doc
 lit: 
 dw ulessthan.x ; link to previous word 
 db 'lit', 3 
 lit.x:
 pop dx ; juggle fn return address
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get literal char into AL 
 cbw ; convert signed byte to signed word ax
 push ax ; put literal value on stack
 push dx ; restore fn return address
 ret
 
 litw.doc:
 ; db 'Pushes an 16 bit literal value onto the stack'
 ; dw \\$-litw.doc
 litw: 
 dw lit.x ; link to previous word 
 db 'litw', 4 
 litw.x:
 pop dx ; juggle fn return address
 lodsw ; ax := [si]++ get literal char into AX
 push ax ; put literal value on stack
 push dx ; restore fn return address
 ret
 
 emit.doc:
 ; db 'removes and displays top item on stack as an ascii character.'
 ; db 'I suppose the character is in the low byte of the stack item...'
 ; dw \\$-emit.doc
 emit:
 dw litw.x 
 db 'emit', 4
 emit.x:
 pop bx ; juggle return pointer
 pop ax ; char in al
 push bx
 mov ah, 0x0E ; bios teletype function 
 int 10h ; x86 bios 
 ret

 key.doc:
 ; db 'Get one keystroke from user and place on stack'
 ; db 'The key is represented as an ascii code in the low byte '
 ; db 'of the stack item.' 
 ; dw \\$-key.doc
 key: 
 dw emit.x ; link to prev
 db 'key', 3 ; reverse counted string
 key.x:
 mov ah, 0 ; wait for keypress bios function
 int 16h ; ah := asci code and al := scan code
 pop bx ; juggle function return pointer
 mov ah, 0 ; set ah = 0
 push ax ; save asci code onto stack, high byte zero
 push bx ; restore return pointer to stack
 ret

 ekey.doc:
 ; db 'Get one keyboard event and place on stack'
 ; db 'This includes arrow keys etc '
 ; dw \\$-ekey.doc
 ekey: 
 dw key.x ; link to prev
 db 'ekey', 4 ; reverse counted string
 ekey.x:
 ret

 ljump.doc:
 ; db 'jumps to a relative virtual instruction.'
 ; db ' The jump is given in the next 2 bytes'
 ; dw \\$-ljump.doc
 ljump: 
 dw ekey.x ; link to prev
 db 'ljump', 5 ; reverse count
 ljump.x:
 xor ax, ax ; set ax := 0
 lodsw ; al := [si]++ get relative jump target into AL
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by offset
 ret

 jump.doc:
 ; db 'jumps to a relative virtual instruction.'
 ; db ' The relative jump is given in the next byte.'
 ; db ' eg: JUMP, -2, jumps back 2 instructions in the bytecode' 
 ; db ' eg: LIT, '*', EMIT, JUMP, -3, ' 
 ; db ' prints a never-ending list of asterixes '
 ; dw \\$-jump.doc
 jump: 
 dw ljump.x ; link to prev
 db 'jump', 4 ; reverse count
 jump.x:
 ; jumps can be handled in the exec routine
 ; handle jumps by modifying virtual ip (in this case SI)
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get relative jump target into AL
 cbw ; convert signed byte al to signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 ; do we need to decrement si ?? yes, more logical
 ret

 jumpz.doc:
 ; db ' ( n -- )
 ; db 'jumps to a relative virtual instruction if top '
 ; db 'stack element is zero. The flag value is removed '
 ; db 'from the stack
 ; db ' The relative jump is given in the next byte.'
 ; db ' eg: JUMPZ, -2, jumps back 2 instructions in the bytecode' 
 ; db ' eg: KEY, DUP, EMIT, LIT, '0', MINUS, JUMPNZ, -6 ' 
 ; db ' allows the user to type until zero is pressed. '
 ; dw \\$-jump.doc
 ; handle jumps by modifying virtual ip (in this case SI)
 jumpz: 
 dw jump.x ; link to prev
 db 'jumpz', 5 ; reverse count
 jumpz.x:
 pop dx ; juggle return pointer
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get relative jump target into AL
 ; check stack for zero, if not continue with next instruction 
 pop bx ; get top stack item into bx
 cmp bx, 0 ; if dx != 0 continue
 jne .exit
 cbw ; convert signed byte al to signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 .exit:
 push dx ; restore call return
 ret

 ; should jumps take top stack element off ? yes
 jumpnz.doc:
 ; db 'jumps to a relative virtual instruction if top stack element '
 ; db ' is not zero.
 ; db ' The relative jump is given in the next byte.'
 ; db ' eg: JUMPNZ, -2, jumps back 2 instructions in the bytecode' 
 ; db ' eg: KEY, DUP, EMIT, LIT, 'q', MINUS, JUMPNZ, -6 ' 
 ; db ' allows the user to type until "q" is pressed. '
 ; dw \\$-jumpnz.doc
 jumpnz: 
 dw jumpz.x ; link to prev
 db 'jumpnz', 6 ; reverse count
 jumpnz.x:

 ; handle jumps by modifying virtual ip (in this case SI)
 pop dx ; juggle return pointer
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get relative jump target into AL
 ; check stack for zero, if so continue with next 
 ; instruction (dont jump)
 pop bx ; get top stack item into bx
 cmp bx, 0 ; if bx != 0 continue
 je .exit ; the only difference with jumpz !
 cbw ; convert signed byte al to signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 .exit:
 push dx ; restore call return
 ret

 rloop.doc:
 ; db ' ( R: n -- n-1 ) '
 ; db ' Decrements loop counter on return stack and jumps to '
 ; db ' target if counter > 0 '
 ; db ' like the x86 loop instruction this is a pre-decrement '
 ; db ' so a loop counter of 2 will loop twice. The disadvantage '
 ; db ' is that a loop counter of 0 will loop 2\\^16 times. '
 ; dw \\$-rloop.doc
 rloop: 
 dw jumpnz.x ; link to prev
 db 'loop', 4 ; reverse count
 rloop.x:
 ; handle loops by modifying virtual ip (in this case SI)
 pop dx ; juggle return pointer
 xor ax, ax ; set ax := 0
 lodsb ; al := [si]++ get relative loop target into AL
 ; check return stack for zero, if so continue with next 
 ; instruction (dont jump/loop)
 mov bx, [es:di-2] ; get top return stack item into bx
 dec bx ; decrement the loop counter on the return stack
 cmp bx, 0 ; if bx != 0 continue
 mov [es:di-2], bx ; update the counter
 je .exit ; the only difference with jumpz !
 cbw ; convert signed byte al to signed word ax (neg offset)
 sub si, 2 ; realign si to JUMP instruction, 
 add si, ax ; adjust the si code pointer by jump offset
 .exit:
 push dx ; restore call return
 ret

 load.doc:
 ; db ' ( sect n -- adr ) 
 ; db ' loads n sectors from disk starting a sect '
 ; db ' Return the adr where the sectors were loaded. '
 ; dw \\$-load.doc
 load: 
 dw rloop.x 
 db 'load', 4
 load.x:
 ; sect n
 .reset: ; Reset the virtual floppy drive (usb)
 mov ax, 0 ; 
 mov dl, byte [drive.d];boot drive number (eg for usb 128)
 int 13h ;
 jc .reset ; ERROR => reset again
 .read:

 ;*** need to save es since rstack points with it!!
 mov [save.es], es

 ;*** dictionary is currently about 4K, so load
 ; sectors after that
 mov ax, 1000h ; 
 mov es, ax ; es:bx determines where data loaded to

 ;*** or ax=1100h bx=0 is 4K after 64K segment
 mov bx, 4096 ; ES:BX = 1000:4096

 pop dx ; juggle fn return
 pop ax ; n==number of sectors
 push dx ; restore fn return

 mov ah, 2 ; Load disk data to ES:BX
 ;mov al, 2 ; Load 2 sectors 512 bytes * 2 == 1K 

 ; try mov cx, 0x0002 ; cylinder 0, sector 2

 pop dx ; juggle fn return
 pop cx ; sect==start sector
 push dx ; restore fn return

 ;*** 10 is first sector after 4K dictionary

 mov ch, 0 ; Cylinder=0
 ;mov cl, 10 ; Sector=10 (sector 1 = boot sector)

 mov dh, 0 ; Head=0
 mov dl, byte [drive.d] ; 
 int 13h ; Read!
 jc .read ; ERROR => Try again
 
 ;*** restore es since rstack points with it!!
 mov es, [save.es]
 pop dx ; juggle fn return
 push 4096 ; return address where loaded 
 push dx ; restore fn return
 ret

 ;*** load alters es (used for rstack) 
 ;*** need to save
 save.es: dw 0
 
 save.doc:
 ; db ' ( -- ) ... '
 ; db ' save sectors to disk '
 ; dw \\$-save.doc
 save: 
 dw load.x 
 db 'save', 4 
 save.x:
 ; to do
 ret

 noop.doc:
 ; db 'Does nothing. For some reason most machines '
 ; db 'include this instruction. Also it is a good '
 ; db 'end marker for the opcodes '
 ; dw \\$-noop.doc
 noop: 
 dw save.x 
 db 'nop', 3 
 noop.x:
 ret
 

 ; *******************************
 ; end of byte codes
 ; *******************************

 ; ************
 ; some immediate words
 ; ************

 hello.doc:
 ; db ' ( -- )
 ; db ' Just testing immediate procs '
 hello:
 dw noop.x
 db 'hello', IMMEDIATE | 5
 hello.p:
 db LIT, '!', EMIT
 db LIT, 'h', EMIT
 db LIT, 'i', EMIT
 db EXIT

 kay.doc:
 ; db ' ( n -- n*1024 )
 ; db ' returns n in K (kilo) quantity. '
 kay:
 dw hello.p
 db 'K', 1 
 kay.p:
 ; n
 db LITW
 dw 1024 ; n 1024
 db UMULT ; n * 1024
 db EXIT

 ; things to do in colon. Set here to next code space
 ; create a header. first link back using last.p
 ; and set last to new word
 ; then compile name, eg word, or wordcompile then
 ; compile count, then compile words until ;
 ; when ; compile "exit" set >code to here
 ;

 colon.doc:
 ; db ' ( -- )
 ; db ' the most important word. Creates a new '
 ; db ' word in the dictionary '
 colon:
 dw kay.p 
 db ':', IMMEDIATE | 1
 colon.p:
 db FCALL
 dw toin.p ; adr len
 db FCALL
 dw parse.p ; ad n / next word
 ; db FCALL
 ; dw tocode.p
 ; db FETCH
 ; db FCALL
 ; dw ishere.p
 ; insert link to last here
 ; db FCALL
 ; dw wordcompile.p
 db FCALL
 dw type.p
 db EXIT

 semicolon.doc:
 ; db ' ( -- )
 semicolon:
 dw colon.p 
 db ';', IMMEDIATE | 1
 semicolon.p:
 ; compile an exit
 ; set >code to here
 ; set ishere to anon buffer

 db EXIT

 begin.doc:
 ; db ' ( -- )
 ; db ' marks a jump back address for until/again etc'
 begin:
 dw semicolon.p 
 db 'begin', IMMEDIATE | 5
 begin.p:
 db FCALL
 dw here.p ; ad /current compilation adr
 db EXIT

 again.doc:
 ; db ' ( -- R: back-adr )
 ; db ' jumps back to begin'
 again:
 dw begin.p 
 db 'again', IMMEDIATE | 5
 again.p:
 db LIT, JUMP 
 db LIT, 2 ; op 2
 db FCALL
 dw compile.p ; compile to current position (here)
 db FCALL
 dw here.p ; jb here
 db DECR ; jb here-1 /align to jump
 db MINUS ; jb-here-1 
 db FCALL
 dw ccomp.p ; compile to current position (here)
 db EXIT

 until.doc:
 ; db ' ( n -- )
 ; db ' at runtime: jumps back to begin if n is true'
 ; db ' at compiletime: get begin address from data '
 ; db ' stack and compiles a conditional relative jump '
 ; db ' back to begin. '
 until:
 dw again.p 
 db 'until', IMMEDIATE | 5
 until.p:
 db LIT, JUMPF 
 db LIT, 2 ; jb op 2
 db FCALL
 dw compile.p ; compile to current position (here)
 db FCALL
 dw here.p ; jb here
 db DECR ; jb here-1 /align to jump
 db MINUS ; jb-here-1 
 db FCALL
 dw ccomp.p ; compile to current position (here)
 db EXIT

 if.doc:
 ; db ' ( n -- )
 ; db ' if the value n on the stack is zero '
 ; db ' skip statements after this until next "fi" '
 ; db ' compiles a jumpzero and puts the current '
 ; db ' address on the return stack '
 if:
 dw until.p 
 db 'if', IMMEDIATE | 2
 if.p:

 db LIT, JUMPF ; op
 db LIT, 2 ; op 2
 db FCALL
 dw compile.p ; compile to current position (here)

 db FCALL
 dw here.p ; ad /current compilation adr

 db LIT, 2 ; compile literal number (2) 
 db FCALL
 dw ccomp.p ;
 db EXIT

 else.doc:
 ; db ' compile.time: ( ad -- jad )
 ; db ' at compile time, get the "if" jump address '
 ; db ' from the data stack and completes the if jump '
 ; db ' using offset from here. Then it leaves the '
 ; db ' else jump address on stack for "fi" to deal with '
 else:
 dw if.p 
 db 'else', IMMEDIATE | 4
 else.p:
 ; ja
 db LIT, JUMP ; ja op
 db LIT, 2 ; ja op 2
 db FCALL
 dw compile.p ; compile to current position (here)

 db FCALL
 dw here.p ; ja ad /current compilation adr

 db LIT, 2 ; compile literal number (2) 
 db FCALL
 dw ccomp.p ;
 db SWAP ; ad ja
 db DUP ; ad ja ja
 db FCALL
 dw here.p ; ad ja ja target 
 db SWAP ; ad ja target ja
 db MINUS ; ad ja t-ja
 db INCR ; .... adjust jump target 
 db SWAP ; ad n ja
 db CSTORE ; ad 
 db EXIT


 ; better to use the data stack for compile time
 ; behaviors 
 fi.doc:
 ; db ' ( -- )
 ; db ' compile ( ad -- ) '
 ; db ' at compile time obtains the correct jump '
 ; db ' address from '
 ; db ' the data stack. This word is called "then" '
 ; db ' in traditional forths. '
 fi:
 dw else.p 
 db 'fi', IMMEDIATE | 2
 fi.p:
 ;*** the address on rstack is hidden under 2
 ; return addresses i.e (fi.p call and compile.p call)

 db DUP ; ja ja
 db FCALL
 dw here.p ; ja ja target 
 db SWAP ; ja target ja
 db MINUS ; ja t-ja
 db INCR ; .... adjust jump target 
 db SWAP ; a1 a2 n ja
 db CSTORE ; a1 a2
 db EXIT

 ; *******
 ntimes.doc:
 ; db ' ( n -- )
 ; db ' print a star n times. '
 ntimes:
 dw fi.p
 db 'ntimes', 6
 ntimes.p:
 db LIT, '*', EMIT, DECR, DUP, JUMPNZ, -5
 db FCALL
 dw nl.p
 db FCALL
 dw nl.p
 db EXIT

 dotstack.doc:
 ; db ' ( -- )
 ; db ' display the items on the data stack without '
 ; db ' altering it. The top (or most recent) item '
 ; db ' is printed rightmost '
 dotstack:
 dw fi.p
 db '.s', 2
 dotstack.p:
 ; below we need a copy of the stack depth
 ; because it gets decremented by the rloop
 ; need to sieve stack items onto rstack 
 ; with >r, swap, r>, swap, >r etc 
 db DEPTH, DUP ; n n
 db JUMPNZ, 4 ; n
 db DROP 
 db EXIT 
 ;*** put all items on return stack
 db DUP ; n n 
 db RON ; n r: n
 db SWAP, ROFF ; n a n-x 
 db SWAP, RON ; n n-x r: a
 db RON ; n r: a n-x
 db RLOOP, -5 ; n r: a n-x-1
 db ROFF ; n 0 r: a b c ...
 db DROP ; n r: a b c ...
 ;*** print all stack items
 db RON ; r: a b c ... n
 db ROFF ; n r: a b c ...
 db ROFF ; n c r: a b
 db DUP ; n c c r: a b
 db FCALL
 dw udot.p ; n c r: a b
 db LIT, ' ', EMIT ; n c r: a b 
 db SWAP, RON ; c r: a b n
 db RLOOP, -11 ; c r: a b n-1 
 db ROFF ; a b c ... 0
 db DROP ; a b c ...
 db EXIT
 
 dotrstack.doc:
 ; db ' ( -- )
 ; db ' display the top 2 items on the return stack ' 
 dotrstack:
 dw dotstack.p
 db '.r2', 3
 dotrstack.p:
 db LIT, ' ', EMIT
 db ROFF, ROFF ; n m
 db DUP ; n m m 
 db FCALL
 dw udot.p ; n m 
 db LIT, ' ', EMIT
 db RON ; n
 db DUP ; n n
 db FCALL
 dw udot.p ; n 
 db RON
 db EXIT

 rcount.doc:
 ; db ' ( adr -- adr-n n ) '
 ; db ' given a pointer to the count byte of a '
 ; db ' reverse counted byte, return the first byte '
 ; db ' of the string and its length. This proc '
 ; db ' may also handle the anding out of the immediate '
 ; db ' control bit which is stored in the msb of the '
 ; db ' count for execution tokens '
 ; dw \\$-rcount.doc
 rcount:
 dw dotrstack.p 
 db 'rcount', 6
 rcount.p:
 ; adr
 db DUP, CFETCH ; a n / get the count
 db LIT, 0b00011111 ; a n mask
 db LOGAND ; a n\\&mask
 db DUP ; a n n
 db RON, MINUS ; adr-n
 db ROFF ; adr-n n
 db EXIT

 dotxt.doc:
 ; db ' ( adr - ) '
 ; db ' given a valid execution token on the stack '
 ; db ' the name of the procedure'
 ; dw \\$-dotxt.doc
 dotxt:
 dw rcount.p 
 db '.xt', 3
 dotxt.p:
 ; xt
 db DECR ; adr-1
 db FCALL
 dw rcount.p
 db FCALL
 dw type.p
 db EXIT

 opcode.doc:
 ; db ' ( adr -- n )
 ; db ' Given the address of an execution token '
 ; db ' or procedure on the stack provides the numeric '
 ; db ' opcode for that procedure or else 0 (-1?) for '
 ; db ' an address which does not correspond to a '
 ; db ' bytecode.'
 ; db ' This is used to compile text to bytecode '
 ; db ' if not an opcode, then compile FCALL etc '
 opcode:
 dw dotxt.p
 db 'opcode', 6
 opcode.p:
 ; adr
 db DUP ; adr adr
 db LITW ; 
 dw op.table ; a a T 
 db FETCHPLUS ; a a T+2 [T]
 db SWAP, RON ; a a [T] r: T+2
 db EQUALS ; a flag r: T+2
 db JUMPT, 19 ; a r: T+2 
 db DUP, ROFF ; a a T+2
 ;**** check if end of table
 db DUP ; a a T+2 T+2
 db FETCH ; a a T+2 [T+2]
 db LIT, -1 ; a a T+2 [T+2] -1
 db EQUALS ; a a T+2 flag
 db JUMPF, 8 ; a a T+2
 db DROP, DROP, DROP ;
 db LIT, 0 ; 0 
 db EXIT
 ; 
 db JUMP, -21 ; a a T+2
 ;** opcode found, calculate offset
 db DROP, ROFF ; T+2
 db DECR, DECR ; T
 db LITW
 dw op.table 
 db MINUS ; T - optable
 db DIVTWO ; opcode
 db EXIT

 dotcode.doc:
 ; db ' ( opcode - ) '
 ; db ' given a valid opcode on the stack, print '
 ; db ' the textual version of the opcode. '
 ; dw \\$-dotcode.doc
 dotcode:
 dw opcode.p 
 db '.code', 5
 dotcode.p:
 ; op
 ;*** check for invalid code, nop always last
 db DUP ; op op
 db LIT, NOOP, INCR ; op op nop+1
 db ULESSTHAN ; op flag
 db JUMPT, 12 ; op

 ;*** invalid code
 db FCALL
 dw udot.p
 db LIT, '?', EMIT
 db LIT, '?', EMIT
 db EXIT

 ;*** valid opcode
 db DUP, PLUS ; op*2 
 db LITW
 dw op.table ; op*2 op.table
 db PLUS ; op*2+op.table
 db FETCH ; [op*2+op.table] / get execution adr
 db DECR ; a-1

 db FCALL
 dw rcount.p ; adr n 

 ;*** does the same as rcount
 ;db DUP, CFETCH ; adr n / get the count
 ;db DUP ; adr n n
 ;db RON, MINUS ; adr-n
 ;db ROFF ; adr-n n

 db FCALL
 dw type.p
 db EXIT

 listcodes.doc:
 ; db ' ( - ) '
 ; db ' list all valid opcodes for the bytecode machine'
 ; dw \\$-listcodes.doc
 listcodes:
 dw dotcode.p 
 db 'listcodes', 9
 listcodes.p:
 db LIT, NOOP, RON ; set up loop counter
 db ROFF, DUP, RON ; n r: n
 db DUP ; n n r: n
 db FCALL
 dw udot.p ; n r: n
 db LIT, ':', EMIT ; n r: n
 db FCALL
 dw dotcode.p ; r: n 
 db LIT, ' ', EMIT ; r: n
 db RLOOP, -16 ; r: n-1

 db ROFF, DROP ; clear counter from rstack
 db EXIT

 name.doc:
 ; db ' ( xt - ) '
 ; db ' given a valid execution token for a bytecode or'
 ; db ' procedure on the stack print the name '
 ; dw \\$-name.doc
 name:
 dw listcodes.p 
 db 'name', 4
 name.p:
 ; adr 
 db DECR ; adr-1 /point to count byte

 ;*** pointer to name and length
 ; rcount also "ands" out the immediate bit in
 ; the name count.
 db FCALL
 dw rcount.p ; a n
 db FCALL
 dw type.p
 db LIT, ' ', EMIT
 db EXIT

 asci.doc:
 ; db 'shows the asci chars'
 ; dw \\$-asci.doc
 asci:
 dw name.p 
 db 'asci', 4
 asci.p:
 ;*** in descending order
 ;*** a gotcha is db lit, 253 (253 too big for byte)
 db LITW
 dw 255
 .again:
 db DUP ; c c
 db LIT, 16 ; c c 16 
 db DIVMOD ; c rem quot
 db DROP ; c rem
 db JUMPNZ, .next-\\$ ; c
 db LIT, 13, EMIT
 db LIT, 10, EMIT
 db DUP ; c c 
 db FCALL
 dw udot.p ; c
 db LIT, ':', EMIT
 db LIT, ' ', EMIT
 .next:
 db DECR, DUP, EMIT ; c
 db DUP ; c c
 db JUMPNZ, .again-\\$ ; c
 db DROP
 db EXIT

 ; in ascending order
 db LIT, 1, INCR, DUP ; n n
 db EMIT ; n
 db DUP 
 db LIT, 252, MINUS, JUMPNZ, -7, EXIT

 keycode.doc:
 ; db 'shows the asci chars values when a key is pressed'
 ; dw \\$-keycode.doc
 keycode:
 dw asci.p 
 db 'keycode', 7
 keycode.p:
 db LIT, 20
 db FCALL
 dw ntimes.p
 db KEY, DUP ; c c
 db FCALL
 dw udot.p ; c 

 db LIT, ' ', EMIT
 db DUP, EMIT ; c
 db LIT, 13, EMIT
 db LIT, 10, EMIT
 db LIT, 'q', EQUALS
 db JUMPF, -19
 db EXIT


 nl.doc:
 ; db 'send a newline to the terminal. '
 ; dw \\$-nl.doc
 nl:
 dw keycode.p
 db 'nl', 2
 nl.p:
 db LIT, 10, EMIT, LIT, 13, EMIT, EXIT

 udot.doc:
 ; db ' ( n -- ) '
 ; db ' display top stack element as unsigned '
 ; db ' number in the current base. '
 ; dw \\$-udot.doc
 udot:
 dw nl.p 
 db 'u.', 2
 udot.p:
 db LIT, 0, RON ; n r: 0
 db LITW 
 dw base.d ; n adr r: 0
 db CFETCH ; n base
 db DIVMOD ; rem quotient
 db ROFF, INCR, RON ; rem quotient r: 0+1 
 db DUP, JUMPNZ, -9
 ; rem rem rem ... 0
 db DROP ; rem rem ... r: x

 db LITW ; use digit lookup table
 dw digits.d ; r r r ... adr r: x
 db PLUS ; r r ... adr+r r: x
 db CFETCH ; r r ... d r: x
 db EMIT ; rem ... print asci digit
 db RLOOP, -6 ; rem ... r: x-1
 db ROFF, DROP ; clear rstack
 db EXIT 

 ddot.doc:
 ; db ' ( n -- ) '
 ; db ' display top stack element as unsigned '
 ; db ' number in decimal '
 ; dw \\$-ddot.doc
 ddot:
 dw udot.p 
 db 'd.', 2
 ddot.p:
 ; n
 db LIT, 0, RON ; n r: 0
 db LIT, 10 ; n 10 r: 0 
 db DIVMOD ; rem quotient 
 db ROFF, INCR, RON ; rem quotient r: 0+1 
 db DUP, JUMPNZ, -7 
 ; rem rem rem ... 0 r: x
 db DROP ; rem rem ... r: x
 db LIT, '0', PLUS, EMIT ; rem ... print remainder
 db RLOOP, -4 ; rem ... r: x-1
 db ROFF, DROP ; clear rstack
 db EXIT 

 dothex.doc:
 ; db ' ( n -- ) '
 ; db ' display top stack element as an unsigned '
 ; db ' number in hexadecimal '
 ; dw \\$-dothex.doc
 dothex:
 dw ddot.p 
 db '.hex', 4
 dothex.p:
 ; n
 db LITW
 dw base.d ; n adr
 db CFETCH ; n base
 db SWAP ; base n /save original base 
 db LIT, 16 ; base n 16
 db LITW
 dw base.d ; base n 16 adr
 db CSTORE ; base n /set new base to 16
 db FCALL
 dw udot.p ; base /print number
 db LITW
 dw base.d ; base adr
 db CSTORE ; /restore original base
 db EXIT 

 dot.doc:
 ; db ' ( n -- ) '
 ; db ' display top stack element as a signed number '
 ; db ' in the current base (if u. does so) '
 ; dw \\$-dot.doc
 dot:
 dw dothex.p 
 db '.', 1
 dot.p:
 ; n
 db DUP ; n n
 db LIT, 0 ; n n 0
 db LESSTHAN ; n flag / n<0 ?
 db JUMPF, 6 ; n
 db NEGATE ; -n
 db LIT, '-', EMIT ; -n /print negative sign
 db FCALL
 dw udot.p
 db EXIT 

 cdot.doc:
 ; db ' ( n -- ) '
 ; db ' display top stack element as a signed 8 bit '
 ; db ' number in the current base (if u. does so) '
 ; db ' This is useful for displaying relative jumps '
 ; db ' which are 1 byte signed numbers. '
 ; db ' eg: 255 = -1, 254 = -2, 128 = -127
 ; dw \\$-cdot.doc
 cdot:
 dw dot.p 
 db 'c.', 2
 cdot.p:
 ; n
 db DUP ; n n
 db LITW
 dw 128 ; n n 128
 db LESSTHAN ; n flag / n < 128 
 db JUMPT, 6 ; n
 db LITW
 dw 256
 db MINUS ; n-256
 db FCALL
 dw dot.p
 db EXIT 

 todigit.doc:
 ; db ' ( c -- n flag ) '
 ; db ' converts the ascii character of a digit '
 ; db ' on the stack to its corresponding integer '
 ; db ' using the base (1<base<37) '
 ; db ' If the character c is not '
 ; db ' a valid digit in the current base, then zero '
 ; db ' is put onto the stack as a false flag. Otherwise'
 ; db ' -1 is put onto the stack '
 ; dw \\$-todigit.doc
 todigit:
 dw cdot.p 
 db '>digit', 6
 todigit.p: 
 ; c 
 db DUP ; c c
 db LITW
 dw digits.d ; c c adr 
 db LITW 
 dw base.d ; c c adr adr
 db CFETCH ; c c a n
 db RON ; c c a r: n

 db CFETCHPLUS ; c c a+1 C r: n
 db SWAP, RON ; c c C r: n a+1
 db EQUALS ; c flag r: n a+1
 db JUMPT, 10 ; c r: n a+1
 db DUP ; c c r: n a+1
 db ROFF ; c c a+1 r: n
 db RLOOP, -8 ; c c a+1 r: n-1
 ;*** not a valid digit
 db DROP, DROP ; c r: 0 
 db ROFF ; c 0
 db EXIT
 ;*** valid asci digit, convert to number
 db DROP ; r: n-x a+1 
 db ROFF, DROP ; r: n-x
 db ROFF ; n-x
 db LITW
 dw base.d ; n-x adr
 db CFETCH ; n-x n 
 db SWAP ; n n-x
 db MINUS ; x
 db LIT, -1 ; x -1 / -1 is true flag
 db EXIT

 digits.d: db '0123456789abcdefghijklmnopqrstuvwxyz'
 
 base.doc:
 ; db ' ( -- adr ) '
 ; db ' puts on the stack the address of the current '
 ; db ' numeric base '
 ; db ' which is used for displaying and parsing '
 ; db ' numbers. The base should be 1 < base < 37 '
 ; db ' since these are the digits which can be '
 ; db ' displayed using numerals and letters '
 ; db ' eg: base c@ . '
 ; db ' displays the current base '
 ; dw \\$-base.doc
 base:
 dw todigit.p 
 db 'base', 4
 base.p: 
 db LITW
 dw base.d
 db EXIT 
 base.d: db 10

 bin.doc:
 ; db ' ( -- ) '
 ; db ' sets the numeric base to binary '
 ; dw \\$-bin.doc
 bin:
 dw base.p 
 db 'bin', 3
 bin.p: 
 db LIT, 2 
 db LITW
 dw base.d
 db CSTORE
 db EXIT 

 hex.doc:
 ; db ' ( -- ) '
 ; db ' sets the numeric base to hexadecimal '
 ; dw \\$-hex.doc
 hex:
 dw bin.p 
 db 'hex', 3
 hex.p: 
 db LIT, 16
 db LITW
 dw base.d
 db CSTORE
 db EXIT 

 deci.doc:
 ; db ' ( -- ) '
 ; db ' sets the numeric base to 10 '
 ; dw \\$-deci.doc
 deci:
 dw hex.p 
 db 'deci', 4
 deci.p: 
 db LIT, 10
 db LITW
 dw base.d
 db CSTORE
 db EXIT 

 tonumber.doc:
 ; db ' ( adr n -- adr/n flag ) '
 ; db ' Given a pointer to string adr with length "n" '
 ; db ' attempt to convert the string to '
 ; db ' a number. If successful put number and true flag'
 ; db ' on the stack, if not put pointer to incorrect digit'
 ; dw \\$-tonumber.doc
 tonumber:
 dw deci.p 
 db '>number', 7
 tonumber.p: 

 ; a n 
 db LIT, 0, RON ; a n r: 0 /false neg flag
 db RON ; a r: 0 n
 ;*** check for +/- at first char
 db DUP, CFETCH ; a c r: 0 n
 db LIT, '+' ; a c '+' ... 
 db EQUALS ; a flag ... 
 db JUMPF, 8 ; a ... 
 db ROFF, DECR, RON ; a r: n-1
 db INCR ; a+1 r: 0 n-1
 db JUMP, 16 ; a+1 r: 0 n-1
 db DUP, CFETCH ; a c r: 0 n
 db LIT, '-' ; a c '-' r: 0 n
 db EQUALS ; a flag r: 0 n 
 db JUMPF, 9 ; a r: 0 n

 ;*** set a +/- flag on the rstack
 db ROFF, DECR ; a n-1 r: 0
 db ROFF, DECR ; a n-1 -1
 db RON, RON ; a r: -1 n-1
 db INCR ; a+1 r: -1 n-1

 ;*** exit if zero length string or just +/-
 db ROFF ; a n r: -1
 db DUP ; a n n r: -1
 db JUMPNZ, 5 ; a n r: -1
 db ROFF, DROP ; a 0
 db EXIT
 db RON ; a r: -1 n 

 db LIT, 0 ; a 0 r: n /initial sum
 db SWAP ; 0 a r: n
 db CFETCHPLUS ; 0 a+1 d r: n
 ;*** check if digit
 db FCALL
 dw todigit.p ; 0 a+1 D flag r: n
 db JUMPF, 24 ; 0 a+1 D r: n
 ;***
 db RON ; 0 a+1 r: n 0-9
 db SWAP ; a+1 s r: n digit /s is sum
 
 db LITW 
 dw base.d 
 db CFETCH ; a+1 s base r: n digit 
 db UMULT ; a+1 s*base r: n digit

 db ROFF ; a+1 s*base digit r: n
 db PLUS ; a+1 s r: -flag n
 db SWAP ; s a+1 r: -flag n
 db RLOOP, -16 ; s a+1 r: -flag n-1 /back to c@+ 
 db ROFF, DROP ; s a+1 r: -flag
 db DROP ; s r: -flag
 db ROFF ; s -flag
 db JUMPF, 3 ; s / skip if +
 db NEGATE ; -s / negate if flag set

 db LIT, -1 ; s -1 /value and true flag
 db EXIT
 ;*** non digit ; sum a+1 d
 db DROP, SWAP ; a+1 sum r: 0 n
 db DROP ; a+1 r: 0 n
 db LIT, 0 ; a+1 0
 db ROFF, DROP ; clear return stack
 db ROFF, DROP ; clear return stack
 db EXIT 

 test.tonumber.doc:
 ; db ' Testing >number by accepting input and '
 ; db ' displaying the number '
 test.tonumber:
 dw tonumber.p 
 db 'test.>number', 12 
 test.tonumber.p:
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db LITW
 dw term.d 
 db FCALL
 dw accept.p
 db LITW
 dw term.d 
 db COUNT ; a n
 db DUP ; a n n
 db JUMPNZ, 3 ; a n /exit if no input
 db EXIT
 db FCALL
 dw tonumber.p
 db LIT, ' ', EMIT
 db FCALL
 dw dot.p
 db LIT, ' ', EMIT
 db DUP
 db FCALL
 dw udot.p
 db LIT, ' ', EMIT
 db FCALL
 dw dot.p
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db JUMP, -42 
 db EXIT

 toword.doc:
 ; db ' ( -- adr n ) '
 ; db ' put on the stack a pointer to the current '
 ; db ' word and its length. '
 toword:
 dw test.tonumber.p 
 db '>word', 5 
 toword.p:
 ; handle zero case (no word found)
 db LITW
 dw toword.d ; adr
 db FETCH ; aw
 db DUP ; aw aw 
 db LITW
 dw toin.d ; aw aw a
 db FETCH ; aw aw ap
 db SWAP ; aw ap aw
 db MINUS ; aw n
 db EXIT
 toword.d dw 0 ; pointer to start of current word

 parse.doc:
 ; db ' ( a1 n -- a2 m ) '
 ; db ' given pointer to string a1 and length of '
 ; db ' n, find the start of next word (non-whitespace) '
 ; db ' starting at a2 of length m '
 ; db ' eg: >in parse 2dup setin 2dup type find etc '
 ; dw \\$-parse.doc
 parse:
 dw toword.p 
 db 'parse', 5
 parse.p: 
 ; adr n
 db RON ; adr r: n
 db CFETCHPLUS ; a+1 [a] r: n 
 db LIT, ' ' ; a+1 c space r: n
 db EQUALS ; a+1 flag ...
 db JUMPF, 7 ; a+1 ...
 db RLOOP, -6 ; a+1 r: n-1 
 ;*** no char found, so return 0
 db DECR ; a r: 0
 db ROFF ; a 0
 db EXIT ;
 ;*** char found
 db DECR ; a r: n-m
 ;*** for debug
 ; db DUP, CFETCH, EMIT
 db DUP ; a a ...
 ;*** check rstack==0 and exit if so
 
 ;*** scan for next whitespace
 db CFETCHPLUS ; a a+1 [a] r: n-m
 db LIT, ' ' ; a a+1 c space r: n-m
 db EQUALS ; a a+1 flag ...
 db JUMPT, 5 ; a a+1 ...
 db RLOOP, -6 ; a a+1 r: n-m-1 
 db INCR ; a a+2 ... /balance decr
 ;*** 
 db DECR ; a a+p-1 r: 0
 db ROFF, DROP ; a a+p-1 /clear rstack
 ;*** now calculate length of word 
 db RON, DUP, ROFF ; a a a+p-1
 db SWAP, MINUS ; a p-1
 db EXIT

 inwords.doc:
 ; db ' ( -- ) '
 ; db ' show all the words in the input buffer '
 ; db ' this can be used as a model for parsing '
 ; db ' and compiling each word '
 ; dw \\$-inwords.doc
 ; 
 inwords:
 dw parse.p 
 db 'inwords', 7 
 inwords.p:
 db LITW
 dw pad.d ; a
 db FCALL
 dw accept.p
 db FCALL 
 dw pad.p ; adr 
 db FCALL
 dw resetin.p ; reset >in >word atin etc
 db FCALL
 dw toin.p ; a n
 db DUP ; a n n
 db JUMPZ, 23 ; a n
 db FCALL
 dw parse.p ; a+x n
 db TWODUP ; a+x n a+x n
 db FCALL
 dw type.p ; a+x n
 ;*** if parse returns 0 then no more words (all space)
 db DUP ; a+x n n
 db JUMPZ, 13 ; a+x n
 ;*** update >word etc 
 db FCALL
 dw atin.p 
 db LIT, 13, LIT, 10 ; newline print
 db EMIT, EMIT
 db JUMP, -25
 ;*** no more words (>in returned zero)
 db DROP, DROP ; clear stack
 db EXIT


 findwords.doc:
 ; db ' ( -- ) '
 ; db ' parse and find each word'
 ; dw \\$-findwords.doc
 findwords:
 dw inwords.p 
 db 'findwords', 9 
 findwords.p:
 db LITW
 dw pad.d ; a
 db FCALL
 dw accept.p
 db FCALL 
 dw pad.p ; adr 
 db FCALL
 dw resetin.p ; reset >in >word atin etc
 db FCALL
 dw toin.p ; a n
 db DUP ; a n n
 db JUMPZ, 30 ; a n
 db FCALL
 dw parse.p ; a+x n
 ;*** if parse returns 0 then no more words (all space)
 db DUP ; a+x n n
 db JUMPZ, 24 ; a+x n
 ;*** update >word etc 
 db TWODUP ; a+x n a+x n
 db FCALL
 dw type.p ; a+x n
 ;
 db TWODUP ; a+x n a+x n
 db FCALL
 dw find.p ; a+x n xt 
 db FCALL
 dw udot.p ; a+x n
 ;
 db FCALL
 dw atin.p 

 db LIT, 13, LIT, 10 ; newline print
 db EMIT, EMIT
 db JUMP, -32
 ;*** no more words (>in returned zero)
 db DROP, DROP ; clear stack
 db EXIT


 seecomp.doc:
 ; db ' ( -- ) '
 ; db ' compiles one line of text to anon and '
 ; db ' displays the decompilation. This is for '
 ; db ' testing the compilation process. '
 ; dw \\$-seecomp.doc
 seecomp:
 dw findwords.p 
 db 'see,', 4
 seecomp.p:

 .again:

 db LITW
 dw anon.d
 db FCALL
 dw ishere.p

 db LITW
 dw pad.d ; a
 db DUP ; a a
 db FCALL
 dw accept.p ; a 
 ;*** exit if no input
 db DUP ; a a
 db CFETCH ; a count
 db JUMPNZ, 4 ; a 
 db DROP
 db EXIT

 db FCALL
 dw resetin.p ; reset >in >word atin etc
 .nextword:
 db FCALL
 dw toin.p ; a n
 db DUP ; a n n
 db JUMPZ, .end-\\$ ; a n
 db FCALL
 dw parse.p ; a+x n
 ;*** if parse returns 0 then no more words (all space)
 db DUP ; a+x n n
 db JUMPZ, .end-\\$ ; a+x n

 db TWODUP ; a+x n a+x n

 ;*** update >word etc 
 db FCALL
 dw atin.p ; a+x n

 db FCALL
 dw tick.p ; n/op/xt flag

 ;*** if flag==0 abort, unknown word/number
 db DUP ; m flag flag
 db JUMPZ, .error-\\$ ; n/op/xt flag 

 db FCALL
 dw compile.p ; ... (if/fi may leave data here) 

 db JUMP, .nextword-\\$
 .end:
 ;*** no more words (>in returned zero)
 db DROP, DROP ; clear stack
 db LIT, EXIT
 db LIT, 2 ; compile opcode exit
 db FCALL
 dw compile.p

 ;*** show the decompilation 
 
 db LIT, 'S', EMIT 
 db LIT, ':', EMIT 
 db FCALL
 dw dotstack.p

 db LIT, ' ', EMIT 
 db LIT, 'R', EMIT 
 db LIT, ':', EMIT 
 db FCALL
 dw dotrstack.p

 db LITW
 dw anon.d
 db LIT, 25 ; ad n
 db FCALL
 dw decomp.p ; ad2
 db DROP 

 db LIT, 13, EMIT 
 db LIT, 10, EMIT 

 ;*** loop forever
 db LJUMP
 dw .again-\\$
 .error:
 ;db FCALL
 ;dw dotstack.p
 db LIT, 13, EMIT 
 db LIT, 10, EMIT 
 db DROP, DROP ; a+x n 
 db FCALL
 dw toword.p ; a+x n
 db FCALL
 dw type.p
 db LIT, ' ', EMIT 
 db LIT, '?', EMIT 
 db LIT, '?', EMIT 
 db LIT, 13, EMIT 
 db LIT, 10, EMIT 
 db LJUMP
 dw .again-\\$
 db EXIT

 interp.doc:
 ; db ' ( -- ) '
 ; db ' parse compile and execute words'
 ; dw \\$-interp.doc
 interp:
 dw seecomp.p 
 db 'interp', 6
 interp.p:
 ;*** compile to anon
 db FCALL
 dw list.p
 .again:
 db LITW
 dw anon.d
 db FCALL
 dw ishere.p

 db LITW
 dw pad.d ; a
 db DUP ; a a
 db FCALL
 dw accept.p ; a 
 db FCALL
 dw resetin.p ; reset >in >word atin etc
 .nextword:
 db FCALL
 dw toin.p ; a n
 db DUP ; a n n
 db JUMPZ, .end-\\$ ; a n
 db FCALL
 dw parse.p ; a+x n
 ;*** if parse returns 0 then no more words (all space)
 db DUP ; a+x n n
 db JUMPZ, .end-\\$ ; a+x n

 db TWODUP ; a+x n a+x n

 ;*** update >word etc 
 db FCALL
 dw atin.p ; a+x n

 db FCALL
 dw tick.p ; n/op/xt flag

 ;db FCALL
 ;dw dotstack.p

 ;*** if flag==0 abort, unknown word/number
 db DUP ; m flag flag
 db JUMPZ, .error-\\$ ; n/op/xt flag 

 ;*** immediate words like if/fi begin will leave
 ; parameters on the data stack
 db FCALL
 dw compile.p ; 
 

 db JUMP, .nextword-\\$
 .end:
 ;*** no more words (>in returned zero)
 db DROP, DROP ; clear stack
 db LIT, EXIT
 db LIT, 2 ; compile opcode exit
 db FCALL
 dw compile.p

 db LIT, 0 ; zero terminate for convenience
 db FCALL
 dw ccomp.p

 ;*** now execute the compiled stuff
 db LITW
 dw anon.d
 db PCALL

 ;*** loop forever
 db LIT, 13, EMIT 
 db LIT, 10, EMIT 
 db LIT, 'o', EMIT 
 db LIT, 'k', EMIT 
 db LIT, 13, EMIT 
 db LIT, 10, EMIT 
 db LJUMP
 dw .again-\\$
 .error:

 ;db FCALL
 ;dw dotstack.p
 ; n/op/xt flag 
 db LIT, 13, EMIT 
 db LIT, 10, EMIT 
 db DROP, DROP ; 
 db FCALL
 dw toword.p ; ad n
 db FCALL
 dw type.p
 db LIT, ' ', EMIT 
 db LIT, '?', EMIT 
 db LIT, '?', EMIT 
 db LIT, 13, EMIT 
 db LIT, 10, EMIT 
 db LJUMP
 dw .again-\\$
 db EXIT

 find.doc:
 ; db ' ( adr n -- xt ) '
 ; db ' given a pointer to a string "adr" and a '
 ; db ' length "n", return the execution token '
 ; db ' for word or else zero if the word was '
 ; db ' not found '
 ; dw \\$-find.doc
 find:
 dw interp.p 
 db 'find', 4
 find.p: 
 ; a n
 db LITW ; pointer to last word
 dw last.p ; a+1 n A
 .again:
 db DECR

 db FCALL
 dw rcount.p

 ;***
 ;db DUP ; a+1 n A-1 A-1
 ;db CFETCH ; .. A-1 N / get the count
 ;db DUP ; .. A-1 N N
 ;db RON, MINUS ; .. adr-N r: N
 ;db ROFF ; .. adr-N N
 ; a n A N

 ;db FCALL
 ;dw print.p
 ;*** now compare the 2 string lengths n \\& N
 db SWAP ; a n N A
 db RON, RON ; a n r: A N
 db DUP, ROFF ; a n n N r: A
 db EQUALS ; a n flag r: A
 db JUMPF, 32 ; a n r: A
 db ROFF ; a n A
 db SWAP ; a A n

 ;*** save values on rstack, clumsy 
 db RON, RON, RON; r: n A a
 db ROFF, DUP ; a a r: n A
 db ROFF, DUP ; a a A A r: n 
 db ROFF, DUP ; a a A A n n 
 db RON, SWAP, RON ; a a A n r: n A

 ;*** compare two strings
 db FCALL ; are strings equal?
 dw compare.p ; a flag r: n A
 ;
 db JUMPF, 8 ; a r: n A
 ; if true clear stacks
 db DROP ; r: n A
 db ROFF, ROFF ; A n
 db PLUS, INCR ; A+n+1
 ; push A+n+1 to get execution token and exit
 db EXIT 
 ; if false, balance stacks and jump down
 ; a r: n A
 db ROFF, ROFF ; a A n
 db SWAP ; a n A
 db JUMP, 3 ; a n A /get next pointer
 db ROFF ; a n A
 db DECR, DECR ; a n A-2 /A-2 points to previous
 db FETCH ; a n [A-2]
 db DUP ; a n [A-2] [A-2]
 db JUMPNZ, .again-\\$ ; a n [A-2]

 db DROP, DROP, DROP ; clear stack 
 db LIT, 0 ; zero means not found
 db EXIT 

 test.find.doc:
 ; db ' Testing find by accepting input and '
 ; db ' displaying the found execution token '
 test.find:
 dw find.p 
 db 'test.find', 9
 test.find.p:
 db FCALL
 dw listxt.p
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db LITW
 dw term.d 
 db FCALL
 dw accept.p
 db LITW
 dw term.d ; adr 
 db COUNT ; adr+1 n
 db FCALL
 dw find.p
 db LIT, ' ', EMIT
 db FCALL
 dw udot.p
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db JUMP, -25 
 db EXIT

 immediate.doc:
 ; db ' ( xt -- flag ) '
 ; db ' given the execution address for a procedure '
 ; db ' return a flag indicating if the procedure '
 ; db ' is immediate or not. Immediate procedures are '
 ; db ' executed at compile time, not compiled '
 ; db ' so, essentially, they compile themselves. '
 ; db ' this allows the compiler to be extended by '
 ; db ' procedures. The immediate control bit is the ' 
 ; db ' most significant bit of the count byte in the '
 ; db ' name. '
 immediate:
 dw test.find.p 
 db 'imm?', 4 
 immediate.p:
 ; xt
 db DECR ; xt-1 
 db CFETCH ; [xt-1] /get the count byte
 db LIT, IMMEDIATE ; n 0b10000000
 db LOGAND ; n \\& imm / zero or non zero
 db EXIT

 tick.doc:
 ; db ' ( a1 n -- n flag) '
 ; db ' given a pointer "a" to a string of length '
 ; db ' "n", attempt to convert the name to either '
 ; db ' an opcode, procedure execution code, or number '
 ; db ' the flag indicates the type of token returned '
 ; db ' a flag of zero means that the name is neither '
 ; db ' number nor opcode nor xt'
 ; db ' flag=0 not number nor word '
 ; db ' flag=1 if number, 2 if opcode, 3 if procedure '
 ; dw \\$-tick.doc
 tick:
 dw immediate.p 
 db 'tick', 4
 tick.p:
 ; a n 
 db TWODUP ; a n a n 
 db FCALL
 dw find.p ; a n xt/0 
 db DUP ; a n xt xt 
 db JUMPNZ, 14 ; a n xt 

 ;*** not found, try to convert to number
 db DROP ; a n 
 db FCALL 
 dw tonumber.p ; adr/n flag
 db JUMPNZ, 5 ; adr/n
 ;*** not a number, push false flag and exit
 db LIT, 0 ; adr 0
 db EXIT
 ;*** is a number
 ; n
 db LIT, 1 ; n 1
 db EXIT

 ;*** check if opcode 
 ; a n xt 
 db SWAP, DROP ; a xt
 db SWAP, DROP ; xt
 db DUP ; xt xt
 db FCALL
 dw opcode.p ; xt op/0
 db DUP ; xt op op
 db JUMPZ, 7 ; xt op
 ;*** is opcode
 db SWAP, DROP ; op
 db LIT, 2 ; op 2 /2=opcode
 db EXIT
 
 ;*** must be procedure, 
 ; xt 0=op
 db DROP ; xt
 db LIT, 3 ; xt 3 /3=procedure
 db EXIT
 
 test.tick.doc:
 ; db ' Testing tick by accepting input and '
 ; db ' displaying the found execution token '
 test.tick:
 dw tick.p 
 db 'test.tick', 9
 test.tick.p:
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db LITW
 dw term.d 
 db FCALL
 dw accept.p
 db LITW
 dw term.d ; adr 
 db COUNT ; adr+1 n
 ;*** exit if no input
 db DUP
 db JUMPNZ, 3
 db EXIT

 db FCALL
 dw tick.p ; 0/n/op/xt flag
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db FCALL
 dw dotstack.p
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db JUMP, -32
 db EXIT

 args.doc:
 ; db ' ( op -- flag ) '
 ; db ' given a valid opcode return a flag '
 ; db ' of 1 if the opcode requires a one byte '
 ; db ' argument or return 2 if the opcode requires '
 ; db ' a 2byte argument or zero if no arguments '
 ; db ' required. '
 ; dw \\$-args.doc
 args:
 dw test.tick.p 
 db 'args', 4
 args.p:

 ; lit, jump, jumpz, jumpnz, rloop 

 db DUP ; op op 
 db LIT, LIT ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .one-\\$ ; op 

 db DUP ; op op 
 db LIT, JUMP ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .one-\\$ ; op 

 db DUP ; op op 
 db LIT, JUMPZ ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .one-\\$ ; op 

 db DUP ; op op 
 db LIT, JUMPNZ ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .one-\\$ ; op 

 db DUP ; op op 
 db LIT, RLOOP ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .one-\\$ ; op 

 ; litw, fcall, ljump,

 db DUP ; op op 
 db LIT, LITW ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .two-\\$ ; op 

 db DUP ; op op 
 db LIT, FCALL ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .two-\\$ ; op 

 db DUP ; op op 
 db LIT, LJUMP ; op op op2 
 db EQUALS ; op flag 
 db JUMPT, .two-\\$ ; op 

 .zero
 db DROP
 db LIT, 0 ; 0
 db EXIT
 .one:
 db DROP
 db LIT, 1 ; 1
 db EXIT
 .two:
 db DROP
 db LIT, 2 ; 2
 db EXIT

 ccomp.doc:
 ; db ' ( n -- ) '
 ; db ' compile byte value n at next available byte '
 ; b ' as given by here.p '
 ; dw \\$-ccomp.doc
 ccomp:
 dw args.p 
 db 'c,', 2
 ccomp.p:
 ;*** compile single byte
 db FCALL ; /get compile point
 dw here.p ; n adr
 db CSTOREPLUS ; a+1
 db FCALL
 dw ishere.p ; /update compile point 
 db EXIT

 wcomp.doc:
 ; db ' ( n -- ) '
 ; db ' compile 2 byte value at next available space '
 ; b ' as given by here '
 ; dw \\$-wcomp.doc
 wcomp:
 dw ccomp.p 
 db 'w,', 2
 wcomp.p:
 ;*** compile word 
 db FCALL ; /get compile point
 dw here.p ; n adr
 db STOREPLUS ; a+2
 db FCALL
 dw ishere.p ; /update compile point 
 db EXIT

 wordcompile.doc:
 ; db ' ( ad n -- ) '
 ; db ' compile the string at ad length n to the '
 ; b ' current compile position as given by here '
 ; dw \\$-wordcompile.doc
 wordcompile:
 dw wcomp.p 
 db 'word,', 5
 wordcompile.p:
 ;*** compile word 
 ; ad n

 ;*** check for n==0 here
 db DUP ; ad n n
 db JUMPNZ, 5 ; ad n
 db DROP, DROP ;
 db EXIT

 ;*** n > 0
 db RON ; ad r: n
 db CFETCHPLUS ; ad+1 c r: n
 db FCALL
 dw ccomp.p ; ad+1 r: n 
 db RLOOP, -4 ; ad+1 r: n-1
 db EXIT

 compile.doc:
 ; db ' ( n flag -- ) '
 ; db ' compile value "n" at next availabe position '
 ; b ' as given by here.p where flag indicates '
 ; db ' the type of value. ' 
 ; db ' 1 literal, 2 opcode, 3 procedure'
 ; dw \\$-compile.doc
 compile:
 dw wordcompile.p 
 db ',', 1
 compile.p:
 ;*** 1=literal number
 ; n flag 
 db DUP ; n flag flag 
 db LIT, 1, EQUALS ; n flag 0/-1
 db JUMPZ, 14 ; n flag 

 ;*** compile number
 db DROP ; n
 db LIT, LITW ; n op
 db FCALL ; /get compile point
 dw here.p ; n op adr
 db CSTOREPLUS ; n a+1
 db STOREPLUS ; a+3
 db FCALL
 dw ishere.p ; /update compile point 
 db EXIT

 ;*** check if opcode
 ; n flag 
 db DUP ; n flag flag 
 db LIT, 2, EQUALS ; n flag 0/-1
 db JUMPZ, 11 ; n flag 

 ;*** 2 is opcode
 ;*** compile the bytecode to given address 
 db DROP ; op
 db FCALL ; /get compile point
 dw here.p ; op adr
 db CSTOREPLUS ; adr+1 
 db FCALL ; /set compile point
 dw ishere.p ; 
 db EXIT
 
 ;*** check if procedure 
 ; n flag 
 db DUP ; n flag flag 
 db LIT, 3, EQUALS ; n flag 0/-1
 db JUMPF, .error-\\$ ; n flag 

 ;*** 3 procedure
 ; xt flag
 db DROP ; xt

 ;*** check if immediate? 
 db DUP ; xt xt 
 db FCALL
 dw immediate.p ; xt flag
 db JUMPT, .immediate-\\$ ; xt

 db LIT, FCALL ; xt op 
 db FCALL ; /get compile point
 dw here.p ; xt op adr
 db CSTOREPLUS ; xt adr+1
 db STOREPLUS ; adr+3
 db FCALL ; /set compile point
 dw ishere.p ; 
 db EXIT

 ;*** immediate proc, execute, dont compile 
 .immediate:
 ; xt
 db PCALL
 db EXIT

 .error:
 ; n flag
 db DROP, DROP
 db EXIT

 decomp.doc:
 ; db ' ( adr n -- a2 ) '
 ; db ' decompiles n bytes starting at address n '
 ; db ' returns the next address after the decompiled '
 ; db ' bytes. Need to handle jumps too.'
 ; dw \\$-decomp.doc
 decomp:
 dw compile.p 
 db 'decomp', 6
 decomp.p: 
 ; adr n
 db RON ; adr r: n 
 .again:
 db LIT, 13, EMIT
 db LIT, 10, EMIT
 db DUP ; a a
 db FCALL
 dw udot.p ; a
 db LIT, ':', EMIT
 db LIT, ' ', EMIT
 db CFETCHPLUS ; a+1 c r: n 
 db DUP ; a+1 c c
 db JUMPZ, .invalid-\\$ ; a+1 c 

 db DUP, LIT, NOOP, INCR ; a+1 c c op+1 r: n
 db ULESSTHAN ; a+1 c flag r: n 
 db JUMPT, .valid-\\$ ; a+1 c r: n

 ;*** invalid opcode
 .invalid:
 ; a+x c
 db FCALL
 dw udot.p
 db LIT, '?', EMIT

 ;*** clear rstack
 db ROFF, DROP
 db EXIT
 
 .valid:

 ;*** valid opcode
 db DUP ; a+1 c c r: n
 db FCALL
 dw dotcode.p ; a+1 c r: n
 db LIT, ' ', EMIT

 ;*** is fcall 
 db DUP ; a+1 c c r: n
 db LIT, FCALL ; a+1 c c op r: n
 db EQUALS ; a+1 c flag ...
 db JUMPF, 21 ; a+1 c ...

 ;*** fcall, get xt
 db DROP ; a+1 ...
 db FETCHPLUS ; a+3 xt ...
 db LIT, '<', EMIT
 db FCALL
 dw dotxt.p ; a+3 r: n 
 db LIT, '>', EMIT
 db LIT, ' ', EMIT
 db RLOOP, .again-\\$ ; a+3 r: n-1 
 db ROFF, DROP ; clear rstack
 db EXIT

 ;*** check if 1 or 2 byte argument or non 
 db DUP ; a+1 c c r: n

 db FCALL
 dw args.p ; a+1 c args r: n
 db DUP ; a+1 c args args r: n 
 db JUMPZ, .zerobytes-\\$ ; a+1 c args ...
 db LIT, 1, EQUALS ; a+1 c flag
 db JUMPT, .onebyte-\\$ ; a+1 c 

 ;*** opcode takes 2 byte argument
 .twobytes:
 db DROP ; a+1 ...
 db FETCHPLUS ; a+3 xt ...
 db LIT, '<', EMIT
 db FCALL
 dw dot.p ; a+3 r: n 
 db LIT, '>', EMIT
 db LIT, ' ', EMIT
 db RLOOP, .again-\\$ ; a+3 r: n-1 
 db ROFF, DROP ; clear rstack
 db EXIT
 
 ;*** opcode takes 1 byte argument
 .onebyte:
 db DROP ; a+1 ...
 db CFETCHPLUS ; a+2 n ...
 db LIT, '<', EMIT

 ;*** cdot prints 8 bit number as signed
 db FCALL
 dw cdot.p ; a+2 r: n 
 db LIT, '>', EMIT
 db LIT, ' ', EMIT
 db RLOOP, .again-\\$ ; a+2 r: n-1 
 db ROFF, DROP ; clear rstack
 db EXIT

 ;*** opcode takes no argument 
 .zerobytes:
 ; a+1 c args ...
 db DROP, DROP
 db RLOOP, .again-\\$ ; a+1 r: n-1 
 db ROFF, DROP ; clear rstack
 db EXIT

 tocode.doc:
 ; db ' ( -- adr ) '
 ; db ' puts on the stack the next available byte'
 ; db ' in the dictionary or just the variable ?? '
 ; dw \\$-tocode.doc
 tocode:
 dw decomp.p 
 db '>code', 5
 tocode.p: 
 db LITW
 dw tocode.d
 ; db FETCH / maybe not
 db EXIT
 tocode.d: dw dictionary 

 here.doc:
 ; db ' ( -- adr ) '
 ; db ' puts on the stack the current compile position'
 ; db ' in the code data space '
 ; dw \\$-here.doc
 here:
 dw tocode.p 
 db 'here', 4
 here.p: 
 db LITW
 dw here.d
 db FETCH
 db EXIT
 here.d: dw 0 ; pointer to next byte

 ; could call this "there" !!
 ishere.doc:
 ; db ' ( adr -- ) '
 ; db ' save position of next available byte'
 ; db ' for compilation '
 ; dw \\$-ishere.doc
 ishere:
 dw here.p 
 db 'ishere', 6
 ishere.p: 
 ; adr
 db LITW
 dw here.d ; adr b
 db STORE 
 db EXIT

 search.doc:
 ; db ' ( a-start ad n -- ad2) '
 ; db ' search for a string in memory '
 ; dw \\$-search.doc
 search:
 dw ishere.p 
 db '/', 1
 search.p:
 ; a A n
 db EXIT

 compare.doc:
 ; db ' ( a A n -- flag) '
 ; db ' given 2 pointers to strings a and A '
 ; db ' compare the 2 strings for n bytes '
 ; db ' and put -1 on stack as flag if the strings are '
 ; db ' the same or flag=0 on stack if the strings '
 ; db ' are different. '
 ; dw \\$-compare.doc
 compare:
 dw search.p 
 db 'compare', 7
 compare.p:
 ; a A n
 db RON ; a A r: n /n loop counter
 db CFETCHPLUS ; a A+1 [A] 
 ; db DUP, EMIT ; debug
 db SWAP ; a [A] A+1
 db RON, RON ; a r: n A+1 [A]
 db CFETCHPLUS ; a+1 [a] r: n A+1 [A] 
 ; db DUP, EMIT ; debug
 db ROFF ; a+1 [a] [A] r: n A+1
 db EQUALS ; a+1 flag r: n A+1
 db JUMPT, 10 ; a+1 r: n A+1 
 db ROFF, ROFF ; a+1 A+1 n
 db DROP, DROP, DROP ; clear stacks
 db LIT, 0 ; flag=0 (false)
 db EXIT
 db ROFF ; a+1 A+1 r: n 
 db RLOOP, -18 ; a+1 A+1 r: n-1
 db ROFF ; a+n A+n 0 
 db DROP, DROP, DROP ; clear stacks
 db LIT, -1
 db EXIT

 list.doc:
 ; db ' ( -- ) '
 ; db ' list all words by name '
 ; dw \\$-list.doc
 list:
 dw compare.p 
 db 'list', 4
 list.p: 
 db LITW
 dw last.p ; adr+1
 .again:
 db DECR ; adr

 db FCALL
 dw rcount.p ; adr-n n

 ;db DUP, CFETCH ; adr n / get the name count
 ;db DUP ; adr n n
 ;db RON, MINUS ; adr-n
 ;db ROFF ; adr-n n 

 db SWAP, DUP ; n adr-n adr-n
 db RON, SWAP ; adr-n n / save adr-n to rstack
 db FCALL
 dw type.p
 db LIT, ' ', EMIT
 db ROFF ; adr-n 
 db DECR, DECR ; point to next pointer
 db FETCH ; [adr-n-2]
 db DUP ; *p *p
 db JUMPNZ, .again-\\$ ; *p
 db LIT, '\\#', EMIT
 db DROP ; clear zero pointer
 db EXIT 

 listxt.doc:
 ; db ' ( adr -- ) '
 ; db ' list all words by name and execution address '
 ; dw \\$-listxt.doc
 listxt:
 dw list.p 
 db 'listxt', 6
 listxt.p: 
 db LITW
 dw last.p ; adr
 .again:
 db DUP ; adr adr
 db FCALL
 dw udot.p ; adr
 db LIT, ' ', EMIT ; print space

 db DECR
 db FCALL
 dw rcount.p ; a-n n

 db SWAP, DUP ; n adr-n adr-n
 db RON, SWAP ; adr-n n / save adr-n to rstack
 db FCALL
 dw type.p
 db LIT, ' ', EMIT
 db ROFF ; adr-n 
 db DECR, DECR ; point to next pointer
 db FETCH ; [adr-n-2]
 db DUP ; *p *p
 db JUMPNZ, .again-\\$ ; *p
 db DROP ; clear 0 pointer 
 db LIT, '\\$\\verb|~|', EMIT
 db EXIT 

 test.type.doc:
 ; db ' testing accept and type '
 test.type:
 dw listxt.p 
 db 'test.type', 9
 test.type.p:
 db LIT, '>', EMIT ; 
 db LITW
 dw term.d ; a
 db DUP ; a a
 db FCALL
 dw accept.p ; a
 db COUNT ; a+1 n
 ;*** show count of buffer
 db DUP ; a+1 n n
 db FCALL
 dw udot.p ; a+1 n
 db LIT, ':', EMIT ; a+1 n
 ;*** show contents of buffer
 db FCALL
 dw type.p
 db LIT, 13, EMIT ;
 db LIT, 10, EMIT ; 
 db KEY, LIT, 13, EQUALS ; <escape> terminates
 db JUMPT, -28
 db EXIT

 print.doc:
 ; db ' ( adr n -- adr n ) '
 ; db ' same as type but doesnt alter stack '
 ; db ' usefull for debugging '
 ; dw \\$-print.doc
 print:
 dw test.type.p 
 db 'print', 5
 print.p: 
 db TWODUP
 db FCALL
 dw type.p
 db EXIT

 type.doc:
 ; db ' ( adr n -- ) '
 ; db ' Prints out n number of characters starting '
 ; db ' at address adr. '
 ; dw \\$-type.doc
 type:
 dw print.p 
 db 'type', 4
 type.p: 
 ; adr n
 ;*** if count zero, do nothing 
 db DUP ; adr n n
 db JUMPNZ, 5 ; adr n
 db DROP, DROP ; clear data stack
 db EXIT
 db RON ; adr r: n
 db CFETCHPLUS ; adr+1 c r: n
 db EMIT ; adr+1 r: n
 db RLOOP, -2 ; adr+1 r: n-1
 db ROFF, DROP, DROP ; clear stacks
 db EXIT 

 accept.doc:
 ; db ' ( buffer -- )
 ; db ' receive a line of input from the terminal '
 ; db ' and store it as a counted string in the buffer. '
 ; db ' should have a character limit'
 ; db ' backspaces are mainly handled. '
 ; dw \\$-accept.doc
 accept:
 dw type.p 
 db 'accept', 6
 accept.p: 
 ;*** to elimate backspace problems, need to
 ; emit character after finding out what it is
 ; not before
 ; a
 db DUP, RON ; a r: a 
 db INCR ; a+1 r: a 
 db KEY, DUP ; a+1 c c r: a 
 db DUP, EMIT ; a+1 c c r: a

 ;*** enter terminates input
 db LIT, 13, EQUALS; a+1 c flag r: a /enter press
 db JUMPT, 32 ; a+1 c r: a 

 ;*** handle backspace
 db DUP ; a+1 c c r: a
 db LIT, 8, EQUALS ; a+1 c flag r: a /backspace
 db JUMPF, 22 ; a+1 c r: a 
 db DROP ; a+1 r: a 
 ;*** test for at 1st char
 db DUP ; a+1 a+1 r: a
 db ROFF, DUP, RON ; a+1 a+1 a r: a
 db MINUS ; a+1 n r: a
 db LIT, 1 ; a+1 n 1 r: a
 db EQUALS ; a+1 flag r: a
 db JUMPT, -24 

 ;*** not 1st char so go back 1 space
 db LIT, ' ', EMIT ; a+n r: a
 db LIT, 8, EMIT ; go back
 db DECR ; a+n-1 r: a
 db JUMP, -33 ; a+n-1 r: a /get next char

 db SWAP ; c a+1 r: a
 db CSTOREPLUS ; a+2 r: a
 db JUMP, -37 ; a+2 r: a
 ; a+n 13 r: a
 db LIT, 10, EMIT ; print newline if enter pressed
 db LIT, 13, EMIT ; 
 db DROP ; a+n r: a
 db ROFF ; a+n a
 db DUP, RON ; a+n a r: a
 db MINUS ; n r: a
 db DECR ; n-1 r: a
 db ROFF ; n-1 a
 db CSTORE
 db EXIT

 in.doc:
 ; db ' ( -- adr )
 ; db 'Puts on the stack the address of the '
 ; db 'current input source/ buffer '
 ; dw \\$-in.doc
 in:
 dw accept.p 
 db 'in', 2
 in.p:
 db LITW
 dw in.d
 db FETCH
 db EXIT
 in.d: dw 0 ; need to initialize

 term.doc:
 ; db ' ( -- adr )
 ; db 'Puts on the stack the address of the '
 ; db 'user input buffer (terminal buffer). This'
 ; db 'is a common source for interpreting and '
 ; db ' compiling '
 ; dw \\$-term.doc
 term:
 dw in.p 
 db 'term', 4
 term.p:
 db LITW
 dw term.d 
 db EXIT
 term.d: times 64 db 0 ; counted buffer for user input

 dotin.doc:
 ; db ' ( -- )
 ; db 'display the contents of the user terminal '
 ; db 'input buffer '
 ; dw \\$-dotin.doc
 dotin:
 dw term.p 
 db '.in', 3
 dotin.p:
 db LITW
 dw in.d ; adr
 db COUNT ; a+1 n 
 db DUP ; a+1 n n
 db FCALL
 dw udot.p ; a+1 n
 db LIT, ':', EMIT ; a+1 n
 db FCALL
 dw type.p 
 db EXIT

 toin.doc:
 ; db ' ( -- adr n ) '
 ; db ' put on stack parse position in input stream'
 ; db ' and number of characters remaining in stream. '
 ; db ' This is used with parse etc'
 ; dw \\$-toin.doc
 ; was a strange bug with this link
 toin:
 dw dotin.p 
 db '>in', 3
 toin.p: 
 ;** need to remember that toin.d and in.d are 
 ; pointer and need to be fetched before use... *p
 ;
 db LITW
 dw toin.d ; adr
 db FETCH ; Ap 
 db LITW
 dw in.d ; adr
 db FETCH ; Ap As 
 db MINUS ; Ap-As
 db DECR ; Ap-As-1 /dont count 'count' byte
 db LITW
 dw in.d ; adr 
 db FETCH ; Ap-As As
 db CFETCH ; m len
 db SWAP ; len m
 db MINUS ; len-m /remaining chars 
 db LITW
 dw toin.d ; 
 db FETCH ; n adr
 db SWAP ; adr n
 db EXIT 
 toin.d: dw 0

 atin.doc:
 ; db ' ( adr n -- ) '
 ; db ' update word and parse position in input'
 ; db ' where the start of the word is given by pointer'
 ; db ' adr and the length of the word is n. The parse '
 ; db ' position will be adr+n after this call '
 ; db ' eg: pad resetin pad accept >in parse 2dup type '
 ; db ' atin >in .s etc'
 ; dw \\$-atin.doc
 atin:
 dw toin.p 
 db 'atin', 4
 atin.p: 
 ; adr n
 db SWAP, DUP ; n adr adr
 db LITW
 dw toword.d ; n adr adr a2
 db STORE ; n adr 
 db PLUS ; n+adr
 db LITW
 dw toin.d ; n+adr ap 
 db STORE ; 
 db EXIT 
 
 resetin.doc:
 ; db ' ( adr -- ) '
 ; db ' set word and parse position to 0 in input'
 ; db ' and set the input buffer to point to address '
 ; db ' adr '
 ; dw \\$-resetin.doc
 resetin:
 dw atin.p 
 db 'resetin', 7
 resetin.p: 
 ; adr
 db DUP ; adr adr
 db LITW
 dw in.d ; adr adr in.d
 ;*** pointer to start of input stream -> in.d
 db STORE ; adr adr
 db INCR ; a+1
 db LITW
 dw toin.d ; a+1 toin.d
 db STORE 
 db LIT, 0
 db LITW
 dw toword.d ; 0 in.nw 
 db STORE 
 db EXIT 
 
 anon.doc:
 ; db ' ( -- adr )
 ; db 'Puts on the stack the address of the '
 ; db 'buffer to hold anonymous definitions. This '
 ; db 'contains compiled byte code for user input '
 ; dw \\$-anon.doc
 anon:
 dw resetin.p 
 db 'anon', 4
 anon.p:
 db LITW
 dw anon.d
 db EXIT
 anon.d: times 64 db 0 ; compiled byte code 

 buff.doc:
 ; db ' ( -- adr )
 ; db ' a testing buffer'
 ; dw \\$-buff.doc
 buff:
 dw anon.p 
 db 'buff', 4
 buff.p:
 db LITW
 dw buff.d
 db EXIT
 buff.d: times 64 db 0 ; compiled byte code 

 pad.doc:
 ; db ' ( -- adr )
 ; db 'Puts on the stack the address of the '
 ; db 'general purpose text buffer '
 ; dw \\$-pad.doc
 pad:
 dw buff.p 
 db 'pad', 3
 pad.p:
 db LITW
 dw pad.d
 db EXIT
 pad.d: times 64 db 0 

 one.doc:
 ; db ' A loop to compile and execute one word typed'
 ; db ' at the terminal and interpreted from the term.d'
 ; db ' buffer. The word can be either opcode '
 ; db ' procedure or number '
 one:
 dw pad.p 
 db 'one', 3
 one.p:
 db FCALL
 dw listxt.p
 db LIT, 10, EMIT
 db LIT, 13, EMIT

 .again:
 db LITW
 dw term.d 
 db FCALL
 dw accept.p
 
 db LITW
 dw term.d ; adr
 
 db CFETCH ; n
 db JUMPZ, -10 ; 
 db LITW
 dw term.d ; adr

 db COUNT ; adr+1 n
 db FCALL
 dw find.p ; xt
 db LIT, 'x', EMIT
 db LIT, 't', EMIT
 db LIT, '=', EMIT

 db DUP ; xt xt
 db FCALL
 dw udot.p ; xt
 db DUP ; xt xt
 db FCALL
 dw opcode.p ; xt op/0
 db DUP ; xt op op
 db LIT, ' ', EMIT
 db LIT, 'o', EMIT
 db LIT, 'p', EMIT
 db LIT, '=', EMIT
 db FCALL
 dw udot.p ; xt op
 db LIT, ' ', EMIT

 ;*** check if valid opcode
 db DUP ; xt op op
 db JUMPZ, 17 ; xt op /opcode or procedure

 ;*** compile the bytecode to anon buffer
 db SWAP, DROP ; op
 db LITW
 dw anon.d ; op adr
 db CSTOREPLUS ; adr+1 
 db LIT, EXIT ; adr+1 n
 db SWAP ; n a+1
 db CSTORE ; 

 db FCALL 
 dw anon.d
 db JUMP, 21 
 ;

 ;*** check if valid word
 ; xt op
 db DROP ; xt
 db DUP ; xt xt
 db JUMPZ, 19 ; xt

 ;**** here compile fcall and xt

 db LITW ; 
 dw anon.d ; xt a
 db LIT, FCALL ; xt a n
 db SWAP ; xt n a
 db CSTOREPLUS ; xt a+1
 db STOREPLUS ; a+4
 db LIT, EXIT ; a+4 n
 db SWAP ; n a+4
 db CSTORE ; 
 db FCALL 
 dw anon.d 
 db JUMP, 43 
 
 ;*** check if valid number
 ; xt
 db DROP
 db LITW ; 
 dw term.d ; adr
 db COUNT ; adr+1 n
 db FCALL
 dw tonumber.p ; a/n flag
 db JUMPNZ, 5 ; a/n 

 db DROP ; 
 db JUMP, 30
 ; n 
 ;*** valid number, so print
 db LIT, 'n', EMIT
 db LIT, '=', EMIT
 db DUP ; n n
 db FCALL
 dw udot.p ; n 
 db LIT, ' ', EMIT

 db LITW ; 
 dw anon.d ; n a
 db LIT, LITW ; n a op
 db SWAP ; n op a 
 db CSTOREPLUS ; n a+1
 db STOREPLUS ; a+3
 db LIT, EXIT ; a+3 op 
 db SWAP ; op a+3
 db CSTORE ; 

 db FCALL 
 dw anon.d ; 
 ;
 db LIT, 10, EMIT
 db LIT, 13, EMIT
 db LJUMP
 dw .again-\\$
 db EXIT

 drive.doc:
 ; db ' ( -- adr )
 ; db ' a variable to hold virtual drive number '
 ; db ' but this should be an opcode '
 ; drive: /another drive 
 dw one.p 
 db 'drive', 5
 drive.p:
 db LITW
 dw drive.d ; ad
 db EXIT
 drive.d: db -1 

 last.doc:
 ; db ' ( -- adr )
 ; db 'Puts on the stack the address of the '
 ; db 'last word '
 last:
 dw drive.p 
 db 'last', 4
 last.p:
 db LITW
 dw last.d ; ad
 ;db FETCH ; [ad]
 db EXIT
 last.d: dw last.p 

 ; testing multisector stack machine byte code

 code:
 
 db FCALL
 dw interp.p
 db 0

 start:

 mov ax, cs ; cs is already correct (?!) 
 mov ds, ax ; data segment 

 ;*** save the (virtual) drive we have loaded 
 ; code from. Handy for disk writes later
 ; have to set data segment DS first
 mov [drive.d], byte dl ;

 ; point es:di directly after the code and data segment
 ; i.e. after the 8 sectors (8 * 512 bytes) 
 ; which contain code and data. We will use es:di 
 ; as the return stack pointer. When 
 ; a value is pushed on the return stack, value 
 ; is written to [es:di] and di is incremented by 2
 ;add ax, 128 ; 128 * 16 = 2048

 ; add ax, 256 ; 256 * 16 = 4096, 4K (8 sectors)
 ; put a gap of 4K between code and stacks for 
 ; dictionary entries etc
 ;*** 4K code + 4K gap then rstack
 add ax, 512 ; 512 * 16 = 8K 
 mov es, ax ; using es:di as return stack pointer 
 mov di, 0

 ; the calculations are as follows
 ; we have loaded 8 sectors = 8 * 512 bytes = 4096 bytes
 ; we want a data stack of size 4K 
 ; (which is big) = 4094 bytes
 ; also we want a return stack of size 4K 
 ; for hefty recursive
 ; functions, although these huge sizes are not 
 ; necessary.
 ; x86 hardware stack grows up or down? ...
 ; divide by 16 because that is how segment 
 ; addressing works
 ; That is: if we multiply the number in ss or es 
 ; or ds by 16
 ; we get a absolute memory address

 ;*** ax is pointing to start of the rstack so 
 ;*** add 4K more for rstack
 add ax, 256 ; 256*16=4K
 mov ss, ax ; a 4K stack here
 mov sp, 4096 ; set up the stack pointer

 push code 
 call exec.x

 stayhere: jmp stayhere

 ;*** new words can be compiled here
 ;
 dictionary: dw 0

 ; times 1024-(\\$-\\$\\$) db 0 ; Pad sectors with 0s 
 ; Pad remainder of 8 sectors with 0s
 times 4096-(\\$-\\$\\$) db 0 

 ;*** some text in sector 8 which we can load
 ;*** with LOAD opcode !!!
 times 4096 db 0
 db 40, 'load code!!! '

 ,,,

BYTE CODE FORTH STYLE SYSTEM

 A byte code system using opcodes and table offsets as 
 shown above. Looping might actually be easier to implement
 with byte code, but a return stack is needed for >r and r>

COMPILING FORTH STYLE SYSTEM

 This is the same as the core system below, but instead
 of the interp: word there is compile: where the entered 
 text is compiled to a temporary buffer and then executed
 with exec.

CORE FORTH STYLE SYSTEM READ ONLY

 This system does not compile new words, it just executes
 words in an interpreter. Without any compiling system or 
 the use of byte code, it seems difficult to use looping 
 or conditionals, since there are no instruction numbers to
 jump to...

 This section is designed to contain a core bootloading system
 with reliable debugged word-functions. The core words will be
 
 inbuffer - push address of input buffer on the stack
 wbuffer - push the word buffer on the stack
 base - push pointer to current base on stack
 hex - make the base variable 16
 bin - make the base 2 
 decimal - make the base 10
 atparse - push onto stack pointer to position in inbuffer and char count
 accept - receive typed input
 nword - get next space delimited word from input buffer
 dup - duplicate top item on stack
 drop - drop top item on stack
 store - ! stores char at pointer
 fetch - @ fetches char from pointer 
 fetchplus - @+ fetches 1 char from data memory, advances pointer
 plus - + add top 2 items on stack
 key - get one char from key
 emit - pop and display top stack item as ascii
 dothex - pop and display top stack item as hex
 count - forth count word
 type - print counted string 
 last - gives pointer to last word in dictionary.
 find - search through the dictionary for word matching wbuffer 
 exec - execute the word-function token found by 'find'
 num - try to convert wbuffer text to a signed number -32K < n < +32K
 dump - print in hex and asci n bytes starting at p*
 list - show what words are available
 .sx - show the stack in hex and asci
 interp - provide a read/parse/execute/ loop (repl)
 flags - pointer to flags such as "not a number" etc, not used
 cursor - keep track of next print position (not implemented)
 cursorb - an alternative cursor (not implemented)

 Flags is not at all a tradition forth idea, but all microcontrollers
 have a flags register, so why shouldn't forthish, which is a type
 of virtual machine.

 The emphasis will be writing the minimal amount of code in the 
 simplest way possible. Idea: if nword just return pointer and char
 count, then wbuffer is not necesssary. Find then can operate
 with pointer and count, but nword still has to update atparse
 structure.

 This core system can be used by build.pl and other words as a framework for
 interactively testing other functions. A script can replace all .doc field
 with "dw 0" for minimal code size

 * a bootloading forth-like core system 
 ----------

 ; replace this with bootcode
 ; eg: sed '/bootload/r bootload.asm' 
 ; [bootload]

 ; first deal with buffers and variables needed

 
 ; some colours (in BL reg) for int 10h, ah=0x0E 
 ; these colours only work when video mode is graphical
 ; such as 0x12 or 0x13

 BLUE equ 1 
 GREEN equ 2
 AQUA equ 3
 RED equ 4
 PURPLE equ 5
 BROWN equ 6
 WHITE equ 7
 DGREY equ 8
 LBLUE equ 9
 ; ... colours up to 0xF (foreground and background) 

 inbuffer.doc:
 db 'Push pointer to input buffer (a counted string)'
 db 'The "inbuffer" is the user input buffer filled with the '
 db '"accept" function. The 1st byte of the buffer is the '
 db 'character count. The buffer is not zero terminated'
 dw \\$-inbuffer.doc
 inbuffer:
 dw 0 
 db 8, 'inbuffer'
 inbuffer.x:
 pop ax ; preserve fn ip return
 push inbuffer.d
 push ax
 ret
 inbuffer.d times 65 db 0 ; buffer where user input goes

 wbuffer.doc:
 db 'Pointer to parsed word buffer.'
 db 'The wbuffer contains one word or number (+/-nnnn) with no'
 db 'leading or trailing spaces that has been parsed from the'
 db 'inbuffer with the "word" function. '
 dw \\$-wbuffer.doc
 wbuffer:
 dw inbuffer
 db 7, 'wbuffer'
 wbuffer.x:
 pop ax ; preserve fn ip return
 push wbuffer.d
 push ax
 ret
 wbuffer.d times 64 db 0 ; counted string word buffer

 ; just print a hash for testing
 hash:
 dw wbuffer
 db 4, 'hash'
 hash.x:
 mov ah, 0Eh ; just print a hash with bios
 mov al, '\\#'
 int 10h ; x86 bios interrupt
 ret

 base.doc:
 db 'Pointer to base variable.'
 db 'the base variable may influence how num: words and dot'
 dw \\$-base.doc
 base:
 dw hash 
 db 4, 'base'
 base.x:
 pop ax ; preserve fn ip return
 push base.d
 push ax
 ret
 base.d db 10 ; make base initial decimal 

 dw 0
 hex:
 dw base
 db 3, 'hex'
 hex.x:
 mov [base.d], byte 16
 ret

 decimal.doc 
 db 'make "base" variable decimal.'
 db 'The base variable influences how numbers are parsed and '
 db 'displayed.'
 dw \\$-decimal.doc
 decimal:
 dw hex
 db 7, 'decimal'
 decimal.x:
 mov [base.d], byte 10
 ret

 bin.doc 
 db 'make base binary'
 dw \\$-bin.doc
 bin:
 dw decimal
 db 3, 'bin'
 bin.x:
 mov [base.d], byte 2
 ret

 atparse.doc:
 db 'Push onto stack parse position in "inbuffer" and remaining count.'
 db 'atparse helps nword to keep track of where in the input buffer'
 db 'it is currently parsing.'
 dw \\$-atparse.doc
 atparse:
 dw bin 
 db 7, 'atparse'
 atparse.x:
 pop ax ; preserve fn ip return
 push word [atparse.d]
 xor bh, bh
 mov bl, byte [atparse.count]
 push bx ; push byte count onto stack
 push ax
 ret
 atparse.d dw inbuffer.d+1 ; pointer to inbuffer, skip count 
 atparse.count db 0 ; count zero

 flags.doc:
 db 'Not used currently. push flag register (just nan)'
 dw \\$-flags.doc
 flags:
 dw atparse 
 db 5, 'flags'
 flags.x:
 pop ax ; preserve fn ip return
 push word [flags.d]
 push ax
 ret
 flags.d dw 0
 
 ; some place holders for compilation

 ; ---------------
 ; basic forth words

 dup.doc:
 db 'Duplicates the top stack item'
 db 'eg: 12 dup stack now has 12 12'
 dw \\$-dup.doc
 dup: 
 dw flags ; link to previous word 
 db 3, 'dup' ; strings are 'counted' 
 dup.x:
 pop bx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push bx ; restore fn return address
 ret

 drop.doc:
 db 'removes top stack item'
 dw \\$-drop.doc
 drop: 
 dw dup ; link to previous word 
 db 4, 'drop' ; strings are 'counted' 
 drop.x:
 pop bx ; juggle fn return address
 pop ax ; discard top stack item 
 push bx ; restore fn return address
 ret

 store.doc:
 db 'stores a byte at a memory address'
 db 'The low byte of the stack item is stored at given address'
 db 'eg: 12 date ! puts 12 in the variable date'
 dw \\$-store.doc
 store:
 dw drop
 db 1, '!'
 store.x:
 pop dx
 pop di ; where to store
 pop ax ; what to store
 stosb ; [di] := al
 push dx
 ret

 fetch.doc:
 db 'fetches a byte (char) at given memory address.'
 db 'The top stack item is replaced with the value stored '
 db 'at that memory address. This is once of the most fundamental'
 db 'forth words, and can be used to implement lots of others.'
 db 'it is the equivalent of peek in basic, or pointer dereferencing'
 db 'in c.'
 db 'eg: wbuffer @ . displays count of wbuffer'
 dw \\$-fetch.doc
 fetch:
 dw store
 db 1, '@'
 fetch.x:
 pop dx ; juggle
 pop si ; address from which to fetch
 xor ax, ax ; set ax = 0
 mov al, [si] ; 
 push ax ; leave char
 push dx
 ret

 fetchplus.doc:
 db 'fetches a byte and advances memory address.'
 db 'leaves addr, char on stack. char is top item'
 db 'this is like lodsb in x86 or ld X+ in avr assembler'
 db 'eg: wbuffer @+'
 dw \\$-fetchplus.doc
 fetchplus:
 dw fetch 
 db 2, '@+'
 fetchplus.x:
 pop dx ; juggle
 pop si ; address from which to fetch
 xor ax, ax ; set ax = 0
 lodsb ; get value into al
 push si ; save incremented address on stack
 push ax ; leave char
 push dx
 ret

 plus.doc:
 db 'add top 2 stack items'
 dw \\$-plus.doc
 plus:
 dw fetchplus
 db 1, '+'
 plus.x:
 pop dx ; juggle
 pop ax
 pop bx
 add ax, bx
 push ax
 push dx
 ret

 key.doc:
 db 'get one keystroke from user and place on stack'
 dw \\$-key.doc
 key: 
 dw plus
 db 3, 'key' ; forth-style function header 
 key.x:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 pop bx ; juggle function return pointer
 push ax ; save keypress value on stack
 push bx ; restore return pointer to stack
 ret

 emit.doc:
 db 'removes and displays top item on stack as an ascii character.'
 db 'I suppose the character is in the low byte of the stack item...'
 dw \\$-emit.doc
 emit:
 dw key
 db 4, 'emit'
 emit.x:
 pop bx ; juggle return pointer
 pop ax ; char in al
 push bx
 mov ah, 0x0E ; bios teletype function 
 int 10h ; x86 bios 
 ret

 dothex.doc:
 db 'displays the top item on the stack in 4 digit hex format.'
 db 'This function does not take the item off the stack.'
 dw \\$-dothex.doc
 dothex:
 dw emit 
 db 4, '.hex'
 dothex.x:
 pop bx ; return address
 pop dx ; the number to print (top item on stack)
 push dx ; restore item to stack
 push bx ; restore return address
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 4 ; number of digits to print
 .again:
 rol dx, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again
 mov al, 'H' ; print an H to indicate hex number
 mov ah, 0eH ; echo the char (just for debugging)
 int 10H
 ret

 hextable db "0123456789ABCDEF" ; translation table

 dothexbyte.doc:
 db 'prints the low byte of top stack item in 2 digit hex'
 db 'the stack item is not removed'
 dw \\$-dothexbyte.doc
 dothexbyte:
 dw dothex ; link
 db 6, '.xbyte'
 dothexbyte.x:
 pop bx ; fn return address
 pop dx ; the number to print (parameter on stack)
 push dx ; restore top stack item
 push bx ; restore return address

 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 2 ; number of digits to print
 .again:
 rol dl, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again

 ret

 ; ------------
 ; help words

 dump.doc:
 db 'prints contents of memory in hex and ascii'
 db 'eg: inbuffer 20 dump shows 20 bytes of the input buffer'
 dw \\$-dump.doc
 dump:
 dw dothexbyte
 db 4, 'dump' 
 dump.x:
 pop dx ; juggle return fn
 pop cx ; how many chars to print 
 pop si ; where to start printing
 push dx ; restore
 push si ; save si and cx
 push cx
 .nextbyte:
 xor ax, ax
 lodsb
 mov dl, al
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 push cx ; save cx again
 mov cx, 2 ; number of digits to print
 .again:
 rol dl, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in bx translation table
 int 10H ; invoke bios print function
 loop .again
 pop cx ; restore counter
 mov ah, 0eh ; print char func
 mov al, ' ' ; space
 int 10h
 loop .nextbyte 

 pop cx ; restore counter, how many chars
 pop si ; restore pointer to start of memory 
 mov ah, 0eh ; print char func
 mov al, 13 ; 
 int 10h
 mov al, 10 ; new line
 int 10h
 .nextchar:
 lodsb ; get [si] into al
 int 10h ; print char in al
 mov al, ' ' ; space
 int 10h
 mov al, ' ' ; space
 int 10h
 loop .nextchar
 ret

 bstack.d dw 0 ; pointer to bottom of stack, init at start

 ; print the memory address: then print each item of stack
 ; starting at bottom of stack, 2bytes then space. Underneath
 ; print ascii of hex values. Print top of stack indicator
 ; such as <<
 ; also print return address in brackets
 ; eg
 ; 0F45: A454 3333 2222 1111 <<
 ; : a 6 e r ...

 dotsx.doc:
 db 'Prints out stack in hex/asci with memory addresses'
 dw \\$-dotsx.doc
 dotsx:
 dw dump
 db 3, '.sx'
 dotsx.x:
 mov dx, sp ; top of stack address
 mov ah, 0x0E ; bios teletype function 

 mov cx, 4 ; number of digits to print
 .again:
 rol dx, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 mov bx, hextable ; translation table
 xlatb ; replace al with hex char in bx translation table
 mov bl, BLUE ; print in blue
 int 10H ; invoke bios print function
 loop .again

 mov bl, GREEN ; print in blue
 mov ah, 0x0E ; type char function 
 mov al, ':' ; greater than indicates top of stack 
 int 10H
 mov al, ' ' ; greater than indicates top of stack 
 int 10H
 mov al, '>' ; greater than indicates top of stack 
 int 10H
 mov al, '>' ; 
 int 10H
 mov al, ' ' ; separate 
 int 10H
 
 ; stack grows down, not up
 cld ; lodsw forwards
 mov si, sp ;

 .nextitem:
 mov dx, [ss:si] ; get next stack item 
 mov ah, 0x0E ; type char function
 mov cx, 4 ; number of digits to print
 .nextnibble:
 rol dx, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 mov bx, hextable ; translation table
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .nextnibble
 mov al, ' ' ; separate 
 int 10H
 add si, 2 ; 
 cmp si, [bstack.d] ; check if last item 
 jne .nextitem

 .exit:
 ret

 ; This assumes the dict has at least one word
 list.doc:
 db 'List all function words in the dictionary. List traverses the '
 db 'linked list dictionary and prints the name of each function '
 db 'word found in the function header. This '
 db 'leaves nothing on the stack. It relies on a lastword data '
 db 'item that contains a pointer to the last word in the dictionary '
 db '... needs paging etc '
 db ' eg: list '
 dw \\$-list.doc
 list:
 dw dotsx ; link 
 db 4, 'list'
 list.x:
 mov bx, last
 .nextword:
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 xor ax, ax ; ax := 0 
 lodsb ; al := [si]++
 mov cx, ax ; load the string count into cx for looping
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .nextchar:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .nextchar ; decr cx loop counter 
 mov al, 32 ; space char
 int 10h
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 jne .nextword 
 .exit:
 ret

 ; --------- 
 ; interp words

 ; cant call this 'word' because of nasm syntax
 ; there is still a bug here when the last word in
 ; inbuffer is a single character...
 nword.doc:
 db 'Get next word from inbuffer using "atparse" position'
 db 'The word is copied to the wbuffer with no leading or '
 db 'trailing spaces.'
 dw \\$-nword.doc
 nword: 
 dw list
 db 5, 'nword'
 nword.x:
 xor cx, cx ; set counter = 0
 mov cl, [atparse.count] ; remaining chars in inbuffer
 mov si, [atparse.d] ; current pos in inbuffer
 lea di, [wbuffer.d+1] ; copy chars into word buffer, skip count
 xor dx, dx ; use dl as char counter for wbuffer, no dont
 cld
 cmp cl, 0 ; no more chars in inbuffer so exit 
 je .exit ; 
 .spaces:
 lodsb ; get char into al (si++)
 cmp al, ' ' ; skip leading spaces 
 loope .spaces ; loop while cx>0 and char is space

 cmp cl, 0 ; no more chars in inbuffer so exit
 ja .nextchar ; if final word in inbuffer is single char
 cmp al, ' ' ; make sure we store it
 je .exit ; last char is space, so dont store
 stosb ; store non-space char 
 jmp .exit ; no more chars so exit

 .nextchar:
 stosb
 lodsb ; get next char into al (si++) 
 cmp al, ' ' ; if space then word is finished
 loopne .nextchar
 
 cmp cl, 0 ; at end of inbuffer write last char
 jne .exit
 stosb
 .exit:
 xor ax, ax
 mov ax, di ; how many chars in wbuffer
 sub ax, wbuffer.d+1
 mov [wbuffer.d], al ; write count to 1st byte of wbuffer
 mov [atparse.count], cl ; update remaining chars
 mov [atparse.d], si ; update parse pointer
 ret
 
 ; the forth count word
 ; stack: addr -- addr+1, char count
 dw 0
 count:
 dw nword
 db 5, 'count'
 count.x:
 pop dx ; preserve return fn pointer
 pop si ; buffer address
 xor ax, ax ; ax := 0
 lodsb ; get count into al, increment si
 push si ; new buffer address
 push ax ; char count
 push dx ; restor fn return ip
 ret

 ; stack: buffer address, char count << 
 dw 0 ; no doc
 type:
 dw count ; link to previous dictionary entry 
 db 4, 'type' 
 type.x:
 cld ; make lodsb step forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop si ; address of buffer to print
 push bx ; restore return function call
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .again ; decr cx loop counter 
 .exit:
 ret

 num.doc:
 db 'Tries to convert a counted string to a (signed) integer.'
 db 'If successful, put the number on the stack. If not successful '
 db 'set the the not-a-number flag 1 in num.error register'
 dw \\$-num.doc
 num:
 dw type
 db 3, 'num'
 num.x:
 ; check for valid first char +/-[0-9] 
 ; if '-' set negative flag (register?)
 ; check for valid digits

 pop bx ; juggle fn return pointer
 pop si ; get pointer to counted buffer
 push bx ; restore fn pointer
 
 mov di, si ; save counted string pointer to get sign later
 xor dx, dx ; accumulator
 xor bx, bx ; multiplier 
 xor cx, cx ; char counter for wbuffer

 cld ; make lodsb step forwards
 lodsb ; get count into al, al <- [si], si++
 mov cl, al ;
 cmp cx, 0 ; no chars in buffer so its an error 
 je .error
 lodsb ; 1st char into al, al <- [si], si++
 ; dec cx ; no, shouldnt, decrement char counter
 cmp al, '-' ; is 1st char negative sign?
 jne .notnegative
 cmp cx, 0 ; no chars left, just - sign, error
 je .error
 lodsb ; get next char (digit) from wbuffer
 dec cx ; decrement char counter
 jmp .notpositive 
 .notnegative:
 cmp al, '+'
 jne .notpositive
 cmp cx, 0 ; wbuffer only has '+' in it, error
 je .error
 lodsb ; get next char (digit) from wbuffer
 dec cx ; decrement char counter
 .notpositive:
 ; actually a valid digit is dependant on the base!!!!
 ; we should use base 1 < n < 17
 .nextdigit:
 cmp al, '0' ; if char is less than '0' then not digit
 jb .notdigit ; unsigned jump to error 
 cmp al, '9' ; if char is less than '0' then not digit
 ja .notdigit ; unsigned jump to error 
 sub al, '0' ; convert to digit
 xor ah, ah ; set ah = 0, so ax := al

 push ax ; save digit 0-9 on stack
 mov ax, dx ; get intermediate result into ax
 xor bh, bh ; set bh := 0
 mov bl, [base.d] ; multiply by base (eg 2, 10, 16 - 1 < n < 256) 
 mul bx ; do dx:ax := ax*bx 
 
 pop bx ; get last digit 0-9 from stack
 jo .toobig ; overflow... result too big to store in AX 
 add ax, bx ; add digit to result
 mov dx, ax ; store intermediate result in dx

 lodsb ; next char into al, al <- [si]
 loop .nextdigit ; keep going while more digits/chars (cx > 0)

 .exit:
 mov [num.error], word 0 ; set is-a-number flag 
 mov ax, dx 
 mov bl, [di+1] ; is first char - ?
 cmp bl, '-' ; if so, negate the result
 jne .continue
 neg ax
 .continue:
 pop bx ; juggle fn return
 push ax ; leave result on stack
 push bx ; restore fn return
 ret

 .toobig: 
 mov [num.error], word 1 ; set number-too-big flag 
 ret

 .notdigit: 
 mov [num.error], word 2 ; set not-a-number flag 
 ret

 .error:
 mov [num.error], word 3 ; set other not-a-number flag 
 ret

 num.error dw 0

 ; !! allow arrow keys to move cursor and insert ??
 accept.doc:
 db 'get max 64 chars from keyboard and put in inbuffer. '
 db 'Enter terminates input and stores count in inbuffer'
 db 'This version allows user to edit with the backspace key'
 db 'It achieves this by echoing backspace, space, backspace and'
 db 'updating the inbuffer'
 dw \\$-accept.doc
 accept:
 dw num ; link 1st word has a zero link 
 db 6, 'accept' ; forth-style function header 
 accept.x:
 mov di, inbuffer.d ; where to store line
 inc di ; skip count byte to store 1st char
 xor dl, dl ; char counter := zero
 cld ; make stosb go forwards
 .nextkey:
 cmp dl, 64 ; only accept max 64 chars
 je .exit ; 
 mov ah, 0 ; wait for keypress bios function
 int 16h
 cmp al, 13 ; was the key press an 'enter'?
 je .exit ; exit if enter pressed
 cmp al, 8 ; was the key press a backspace
 je .backspace ; do something sensible
 mov ah, 0eh ; echo the character
 int 10h
 stosb ; put the char into the buffer
 inc dl ; increment char counter
 jmp .nextkey
 .backspace: ; allow user to edit input with backspace
 cmp dl, 0 ; if at start of buffer do nothing
 je .nextkey ;
 dec dl ; decrement char count
 dec di ; one char back in buffer
 mov [di], byte 0 ; erase char 
 mov ah, 0eh ; print char func
 mov al, 0x08 ; ASCII for Backspace
 int 10h
 mov al, 0x20 ; ASCII for Space
 int 10h
 mov al, 0x08 ; ASCII for Backspace
 int 10h
 jmp .nextkey
 .exit:
 mov [inbuffer.d], dl ; store char count in inbuffer
 mov [atparse.count], dl ; reset parse position to beginning
 mov [atparse.d], word inbuffer.d+1
 mov ah, 0eh ; echo the character
 mov al, 13 ; a new line
 int 10h
 mov al, 10
 int 10h

 ret

 ; execute a function given a pointer to its header on the stack
 ; if pointer is zero, then this should pop the 0 and exit, no??
 exec.doc:
 db 'execute a word given a pointer on the stack'
 db ' eg: lastword exec '
 dw \\$-exec.doc
 exec:
 dw accept ; link to prev
 db 4, 'exec'
 exec.x:
 pop ax
 pop bx ; get pointer to function
 push ax ; preserve fn return pointer
 cmp bx, 0 ; a zero pointer should not be executed
 je .exit
 add bx, 2 ; point to name count
 mov cl, [bx] ; get the count
 inc bx ; skip over count
 add bl, cl ; advance the pointer to the function

 ; !! not call [bx] thats a pointer to jumptable
 ; !!! call bx may change the stack (probably will) so we need 
 ; !!! to preserve the call return ip 
 ; instead of this below, we should have a return stack
 ; so that function words can be nested
 pop word [execreturn] ; save return ip
 call bx ; call the fn pointed to by bx
 push word [execreturn] ; restore fn return ip
 .exit:
 ret
 ; a dodgy solution, but any register might get overwritten
 execreturn dw 0
 
 ; point and return a pointer to the found word or else 0 on the 
 ; stack
 ; stack: search term, start pointer -- function header pointer

 find.doc:
 db 'Search dictionary for word in wbuffer and return pointer.'
 db ' eg: in lastword find '
 dw \\$-find.doc
 find:
 dw exec ; link to prev
 db 4, 'find'
 find.x:
 pop dx ; juggle fn return ip
 pop bx ; where to start searching (eg last entry in dict)
 pop ax ; counted string buffer to search for 
 push dx ; restore fn ip
 .again:
 xor cx, cx ; set cx:=0
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 mov cl, [si] ; the count of the search term
 inc cl ; we also have to compare the count bytes
 mov di, ax ; the search term pointer
 cld ; search forwards (clear direction flag)
 repe cmpsb ; compare all characters for equality
 je .found
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 je .notfound ; no more to words search, so exit
 jmp .again 
 .notfound:
 pop dx
 push 0 ; not found so return 0
 push dx
 ret
 .found: 
 pop dx ; juggle return ip
 push bx ; return pointer to found word on stack 
 push dx
 ret

 ; -----------------------------

 interp.doc:
 db 'The main interpreting loop for a forthish system.'
 dw \\$-interp.doc
 interp:
 dw find ; link to prev
 db 6, 'interp'
 interp.x:

 .nextline: 
 mov bl, 9 ; colour pale blue
 mov ah, 0eh ; print char func
 mov al, 13 
 int 10h
 mov al, 10 
 int 10h
 mov al, '>' ; print a prompt 
 int 10h
 mov bl, 2 ; colour green
 call accept.x ; put max 64 chars in input buffer (inbuffer.d)

 ;call atparse.x ; should push pointer \\& count on stack
 ;call type.x ; just to debug

 .nextword:
 mov cl, [atparse.count] 
 cmp cl, 0 ; if no more chars in inbuffer, get new line
 je .nextline 

 call nword.x ; parse next word into wbuffer
 mov al, [wbuffer.d]; if wbuffer count is zero, no word parsed 
 cmp al, 0 ; wbuffer count zero 
 je .nextline ; no more words, so get next line of input 

 push wbuffer.d ; what buffer to look in 
 push last ; where to start searching
 call find.x ; try to find word in dict 
 pop ax ; ax is pointer to executable for [word] 
 push ax ; restore pointer to fn for exec
 cmp ax, 0 ; 0 means word was not found
 jne .found

 pop ax ; get rid of zero pointer (word not found)
 push wbuffer.d ; word buffer to convert to number
 call num.x ; try to convert to a number and push on stack
 mov ax, [num.error] ; check the "not-a-number" flag
 cmp ax, 0 ; if num.error==0 then number parsed ok
 je .nextword ; its a number, already on stack
 
 call werror.x ; print error message if word not found
 jmp .nextword ; for debugging loop through all words 
 ;jmp .nextline ; unknown word so just get a new line

 .found:
 call exec.x ; watch out for stack mangling with exec 
 ; print some good message in green like
 ; [ ok ] 
 jmp .nextword

 .exit: ; never get here because interp goes forever !
 ret

 werror.doc:
 db 'prints an error message when word not found/not number.'
 dw \\$-werror.doc
 werror:
 dw interp 
 db 6, 'werror'
 werror.x:
 ; some error indicator
 mov bl, 7 ; colour white
 mov ah, 0eh ; x86 bios echo char fn
 mov al, '[' ; a delimiter char for debug
 int 10h
 push wbuffer.d ; when word is neither function nor number 
 call count.x ; print it out with ? and stop parsing line 
 mov bl, 4 ; colour
 call type.x
 mov ah, 0eh ; x86 bios echo char fn
 mov al, ']' ; a delimiter char for debug
 int 10h
 mov al, ' ' ; a delimiter char for debug
 int 10h
 mov al, '?' ; a delimiter char for debug
 int 10h
 mov al, '?' ; a delimiter char for debug
 int 10h
 mov al, ' ' ; a delimiter char for debug
 int 10h
 ret

 ; 
 colour.doc:
 db 'show colours'
 dw \\$-colour.doc
 colour:
 dw werror
 db 6, 'colour'
 colour.x:
 mov cx, 9
 .next:
 mov ah, 0x0E
 mov bl, cl
 mov al, cl
 add al, '0'
 int 10h
 loop .next
 ret

 ; make last the last word for convenience
 last.doc:
 db 'Push pointer to header of last dictionary word '
 dw \\$-last.doc
 last:
 dw colour 
 db 4, 'last'
 last.x:
 pop ax ; preserve fn ip return
 push word [last.d]
 push ax
 ret
 last.d dw last

 ; ---------------
 ; start of main program

 start:

 mov ax, cs ; make data segment and es same as code segment
 mov ds, ax
 mov es, ax
 mov [bstack.d], sp ; 

 mov ah, 0
 mov al, 12h
 int 10H

 ; try to make cursor visible
 mov ah, 1
 mov ch, 1
 mov cl, 2
 int 10H

 call interp.x
 here: jmp here ; loop forever 

 ,,,


IDEAS ABOUT A FORTHISH SYSTEM

 The forth language introduced some revolutionary ideas that
 never led to any kind of revolution. Namely: place code units
 (functions/ words/ objects) within a datastructure which includes
 the words name. This provides what is called today 'reflexivity'-
 the ability of code to 'know' something about itself. Since code
 is within a data structure it provides the ability to analyse that
 code. Code speaking about itself is the realm of AI, even if
 those ambitions are not helpful.

 Basic forth functions: receive a word from the keyboard and look
 up the word in a dictionary (find). If word found, execute the code
 associated with the word (exec). If word not found, try to convert
 input to a number (>number) and push it on the stack. All functions
 receive their parameters on the 'stack' (which is either the
 system stack, or else a software stack)

 * an example of a forth word data structure (from 'itsy-forth')
 -----------------
 ; header
 dw link\\_to\\_previous\\_word
 db 3, 'nip' ; strings are 'counted' in forth (3 chars in nip)
 xt\\_nip dw docolon ; xt= execution token, forth jargon
 ; body
 dw xt\\_swap ; pointers to other forth 'words'
 dw xt\\_drop ; remove last item on stack
 dw xt\\_exit ; pop stack etc 
 ,,,


 * example of forth dictionary entry with assembly code
 -----------
 dw link\\_to\\_previous\\_word
 db 1, '+'
xt\\_plus dw mc\\_plus
mc\\_plus pop ax
 add bx,ax
 jmp next
 ,,,

TOKEN THREADED FORTH ....

 In this style of forth each primitive is a number which is
 a virtual opcode. This type of forth creates a proper virtual
 machine but is reputed to be the slowest. There is 
 probably a table containing opcodes and function pointers eg:
 1 AAF1
 2 AAFF
 3 AB1C etc

 We could think about universal naming of forth words. That
 is a prefix to each word stored in a table. 

SUBROUTINE THREADED FORTHS ....

 each function call is just a native 'call fn'

INDIRECT THREADED ....

 An inner interpreter is used to call a series of function 
 pointers within a forth word. This is considered slower than
 subroutine threaded on modern machines.

EXERCISES TOWARD A FORTH LIKE SYSTEM ....

 We can write small programs which perform forth-like 
 functions to demonstrate different techniques for creating
 forth-ish systems.

 The following code is similar to the forth 'accept' word. 
 It gets a certain number of characters and copies them to 
 a buffer. In forth, the line is then parsed into counted
 words and executed, one word at a time.

 The entry code below should handle 'backspaces' to allow
 the user to edit the text entered.

 * get some text from the keyboard and copy to a counted buffer
 ------------------
 org 7c00h
 jmp start
 SIZE equ 9 
 buffer resb SIZE+1 ; 1 byte for the count + 9 for chars
 start:

 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov es, ax ; es is needed for stosb
 cld ; go forwards, not backwards
 .keys:
 mov cx, SIZE ; maximum chars in buffer
 lea di, [buffer+1]
 .again:
 mov ah,0 ; wait for any key
 int 16h ; bios keyboard functions
 cmp al, 13 ; was the key press an 'enter' 
 je .count
 stosb ; copy the char to the buffer
 mov ah, 0eh ; echo the key pressed
 int 10h
 loop .again ; loop while CX > 0
 .count:
 mov bx, SIZE ; calculate and store char count in [buffer] 
 sub bx, cx
 mov [buffer], bl 
 .type: ; print count and 1st character 
 call newline
 mov al, [buffer] ; print char count (one digit) 
 add al, '0' ; convert digit to ascii
 int 10h
 mov al, [buffer+1] ; print 1st char of buffer
 call newline
 jmp .keys ; keep looping! 

 newline:
 mov ah, 0eh
 mov al, 13 ; print to a newline 
 int 10h
 mov al, 10 
 int 10h
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


 Below a difficult Gotcha!. With org 7c00h and the mov ax, 07c0h
 the code does not work.

 * get one letter from keyboard and look up in a dictionary 
 ------------------
 ;org 7c00h
 jmp start
 buffer db ' ' ; single character buffer 

 ; the dictionary, a linked list.
 aa dw 0 ; zero link means top of dictionary
 db 1,'a' ; count + 
 mov ah, 0eh
 mov al, 'A' 
 int 10h
 bb dw aa ; link to previous entry in dictionary
 db 1,'b' 
 mov ah, 0eh
 mov al, 'B' 
 int 10h
 cc dw bb ; link to previous entry in dictionary
 db 1,'c' 
 mov ah, 0eh
 mov al, 'C' 
 int 10h
 ret
 last dw cc ; link to last dictionary entry

 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax
 mov es, ax ; es is needed for stosb
 cld ; go forwards, not backwards
 .again:
 mov ah,0 ; wait for any key
 int 16h ; bios keyboard functions
 mov [buffer], al ; copy the char to the buffer
 mov ah, 0eh ; echo the key pressed
 int 10h
 .search: ; print count and 1st character 
 ;call newline
 mov bx, [last]
 lea si, [bx]
 mov al, [si+3]
 mov ah, 0eh ; echo the last character in dict 
 int 10h
 ;lea bx, newline 
 ;call bx

 jmp .again ; keep looping! 

 newline:
 mov ah, 0eh
 mov al, 13 ; print to a newline 
 int 10h
 mov al, 10 
 int 10h
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The assembly coder will notice that it is easier to compare 2 
 strings if those strings are 'counted', that is, the number
 of characters they contain are stored (preferably) in front
 of the string text.

 * look up a word in a linked-list dictionary and report if found 
 ------------------
 ,,,

 * write functions which take and leave parameters from some stack
 --------
 ,,,
 
FORTH WORDS 


 Below follows some implementations of common forth words.
 However no attempt is made to adhere to any kind of 'standard'
 forth.

 I am using a convention of ; ** then ; * to indicate canonical
 implementations which can be collected together to form a 
 running system.

CRLF ....

 * print a newline
 ---------------
 ; **
 ; just print a newline
 dw 0 ; no doc
 crlf:
 dw 0 ; link
 db 4, 'crlf'
 crlf.x:
 mov ah, 0eh ; bios type char function 
 mov al, 13 ; cr lf
 int 10h
 mov al, 10
 int 10h
 ret
 ; *
 ,,,,,
 
INBUFFER ....


 Just pushes the input buffer onto the stack

 * address of input buffer
 ---------------
 ; **
 ; puts on stack the current parse position in the input buffer
 dw 0 ; no doc
 inbuffer:
 dw input ; link
 db 8, 'inbuffer'
 inbuffer.x:
 pop dx ; juggle return pointer
 push inbuffer.n ; current position
 push dx
 ret
 ; data field 
 inbuffer.n db 0, ' ' 
 ; *
 ,,,,

DUP ....

 One of the more fundamental forth words, just duplicates the 
 top item on the stack.

 * a dup implementation
 -----------------------
 ; **
 dw 0 ; no doc
 dup: 
 dw 0 ; link to previous word 
 db 3, 'dup' ; strings are 'counted' 
 dup.x:
 pop bx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push bx ; restore fn return address
 ret
 ; *
 ,,,

DUMP ....

 Dump displays memory from a given pointer for n number of 
 bytes. It prints the hex value of the byte as well as the 
 asci value if possible. This is an important function for 
 debugging.

 The layout could be

 Address Memory Values ....
 0x1000: FA 12 34 
 a \\^ b 
 0x1010: 23 45 56 ... 
 n y m

 * display the contents of memory
 ----------------

 BITS 16
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 base:
 dw 0 ; top of dictionary
 db 4, 'base' ; forth style counted name
 base.x:
 pop dx ; juggle return pointer for word
 push base.n ; push address of base on stack
 push dx
 ret
 base.n dw 16

 hextable db "0123456789ABCDEF"
 dotbyte.doc:
 db 'displays a 1 byte number in current base', 13, 10
 db 'eg: 23 .byte '
 dw \\$-dotbyte.doc
 dotbyte:
 dw base
 db 5, '.byte' 
 dotbyte.x:
 pop dx ; juggle the return function pointer
 pop ax ; byte value in al to print
 push dx ; restore the return ip
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; max base is 256 currently (8 bits)
 xor cx, cx ; set counter = 0
 .again:
 xor ah, ah ; ah = 0, ax is the dividend
 div bl ; does ax/bl. remainder  ah
 push ax ; save remainder:quotient on the stack 
 inc cx ; increment the digit counter
 cmp al, 0 ; if the quotient != 0 do the next digit 
 jne .again ; loop while quotient > 0
 .print:
 pop ax ; get digit from the stack
 mov al, ah ; convert digit to ascii
 mov bx, hextable ; translation table
 xlatb ; replace al with hex digit from table
 mov ah, 0eH ; print digit in al
 int 10H
 loop .print ; using cx the digit counter to loop 
 ret

 dump.doc:
 db 'Displays the contents of memory', 13, 10
 db 'eg: 1000 20 dump /displays 20 bytes of values from 0x1000 '
 dw \\$-dump.doc
 dump:
 dw dotbyte
 db 4, 'dump' 
 dump.x:
 pop dx ; juggle return fn
 pop cx ; how many bytes to display
 pop si ; set si to memory pointer
 push dx ; restore fn return

 cld ; make lodsw go forwards 
 .nextbyte:
 xor ah, ah ; only lower bit is printed
 lodsb ; al := [ds:si++]
 push cx ; save byte counter (dotbyte modifies it) 
 push ax ; byte to display
 call dotbyte.x ; display value of byte in current numeric base
 mov ah, 0x0E ; type char fn
 mov al, ' ' ; a space between each byte
 int 0x10
 pop cx
 loop .nextbyte ; do next memory byte 
 .exit:
 ret

 start:

 mov ax, cs ; make data segment and es same as code segment
 mov ds, ax
 mov es, ax
 ;mov [base.n], word 16
 push 0 ; offset 0 from data segment
 push 20 ; show 100 bytes
 call dump.x
 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of MBR boot sector with 0s
 dw 0xAA55 ; The standard MBR boot signature

 ,,,

DOT ....

 The word '.' in forth type systems just displays the top number
 on the stack in the current base and removes that number from the 
 stack


 * implement dot for 2 byte number on stack
 ----------------

 BITS 16
 [ORG 0]

 cr equ 13 ; carriage return
 lf equ 10 ; form feed 

 jmp 07C0h:start ; Goto segment 07C0

 base.doc:
 db 'Puts the address of variable base on stack', 13, 10
 db 'Base determines the current numerical base for conversions', 13, 10
 db 'eg: 16 base ! /makes the base hexadecimal '
 dw \\$-base.doc
 base:
 dw 0 ; top of dictionary
 db 4, 'base' ; forth style counted name
 base.x:
 pop dx ; juggle return pointer for word
 push base.n ; push address of base on stack
 push dx
 ret
 base.n dw 16

 ; There was a bug with this taking one too many values off
 ; the stack (ie the fn return pointer) and crashing the code
 ; but seems to be working now.

 hextable db "0123456789ABCDEF"
 dot.doc:
 db 'displays a 2 byte number on stack in current base', 13, 10
 db 'eg: 32 hex . /displays 20 (32 in hexadecimal) '
 dw \\$-dot.doc
 dot:
 dw base
 db 1, '.' 
 dot.x:
 pop dx ; juggle the return function pointer
 pop ax ; 2 byte value in ax to print
 push dx ; restore the return ip
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; base
 xor cx, cx ; set counter = 0
 .again:
 xor dx, dx ; dividend is ax
 div bx ; does dx:ax/bx. remainder  dx, quotient -> ax
 push dx ; save remainder (ie digit) on the stack 
 inc cx ; increment the digit counter
 cmp ax, 0 ; if the quotient != 0 do the next digit 
 jne .again ; loop while quotient > 0
 .print:
 pop ax ; get digit from the stack (digit in AL)
 mov bx, hextable ; translation table
 xlatb ; replace al with hex digit from table
 mov ah, 0eH ; print digit in al
 int 10H
 loop .print ; using cx the digit counter to loop 
 ret

 start:

 mov ax, cs ; make data segment and es same as code segment
 mov ds, ax
 mov es, ax
 
 push 0xFF12 
 call dot.x
 push 0x1234 
 call dot.x
 mov [base.n], word 10
 push 12345 
 call dot.x

 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of MBR boot sector with 0s
 dw 0xAA55 ; The standard MBR boot signature
 ,,,


 Below is a one byte version of dot '.'

 * implement dot for one byte number on stack
 ----------------

 BITS 16
 [ORG 0]

 cr equ 13 ; carriage return
 lf equ 13 ; carriage return

 jmp 07C0h:start ; Goto segment 07C0

 ; base is a standard forth word. Pushes var base (eg 16, 10)
 base.doc:
 db 'Puts the address of variable base on stack', 13, 10
 db 'eg: 16 base ! /makes the base hexadecimal '
 dw \\$-base.doc
 base:
 dw 0 ; top of dictionary
 db 4, 'base' ; forth style counted name
 base.x:
 pop dx ; juggle return pointer for word
 push base.n ; push address of base on stack
 push dx
 ret
 base.n dw 16 

 ; There was a bug with this taking one too many values off
 ; the stack (ie the fn return pointer) and crashing the code
 ; but seems to be working now.
 ; at the moment this is only 8 bit division. Use dx:ax for 16 bit
 ; division, with xor dx, dx; remainder->dx; quotient->ax 

 hextable db "0123456789ABCDEF"
 dotbyte.doc:
 db 'displays a 1 byte number in current base', 13, 10
 db 'eg: 23 .byte '
 dw \\$-dotbyte.doc
 dotbyte:
 dw base
 db 5, '.byte' 
 dotbyte.x:
 ; expects the 8 bit number to display on stack in AL and 
 ; the base in BL register
 pop dx ; juggle the return function pointer
 pop ax ; byte value in al to print
 push dx ; restore the return ip
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; max base is 256 currently (8 bits)
 xor cx, cx ; set counter = 0
 .again:
 xor ah, ah ; ah = 0, ax is the dividend
 div bl ; does ax/bl. remainder  ah
 push ax ; save remainder:quotient on the stack 
 inc cx ; increment the digit counter
 cmp al, 0 ; if the quotient != 0 do the next digit 
 jne .again ; loop while quotient > 0
 .print:
 pop ax ; get digit from the stack
 mov al, ah ; convert digit to ascii
 mov bx, hextable ; translation table
 xlatb ; replace al with hex digit from table
 mov ah, 0eH ; print digit in al
 int 10H
 loop .print ; using cx the digit counter to loop 
 ;push dx ; dodgy fix if one too many off stack 
 ret
 start:

 mov ax, cs ; make data segment and es same as code segment
 mov ds, ax
 mov es, ax
 
 push 32 
 call dotbyte.x
 push 255 
 call dotbyte.x
 mov [base.n], word 10
 push 255 
 call dotbyte.x

 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of MBR boot sector with 0s
 dw 0xAA55 ; The standard MBR boot signature
 ,,,

DOTSTACK ....

 Show stack, this is usually called .s in old forths.
 Perhaps we can use bp basepointer register to find
 out where the bottom of the stack is

 Need to debug this. It may be tricky. Stack grows down.
 Also stack is pointing to return fn first. need to jump 

 x86 stack diagram

 ss:0 sp (tos) bos = bottom of stack

 ?? ?? ?? ?? 0xFF12 0x1234 0x2222

 sp pointer to byte with value 12 in last item on stack

 sp always points to low order byte of last item on stack
 ss:0 points to stack limit. stack grows to lower memory 
 (ie small memory addresses). 

 working, just need to resolve the bx conflict below and 
 loop through each item on the stack

 * display the current contents of the stack 
 ----------------

 BITS 16
 [ORG 0]

 cr equ 13 ; carriage return
 lf equ 10 ; form feed 

 jmp 07C0h:start ; Goto segment 07C0

 ; **
 base.doc:
 db 'Puts the address of variable base on stack', 13, 10
 db 'Base determines the current numerical base for conversions', 13, 10
 db 'eg: 16 base ! /makes the base hexadecimal '
 dw \\$-base.doc
 base:
 dw 0 ; link top of dictionary
 db 4, 'base' ; forth style counted name
 base.x:
 pop dx ; juggle return pointer for word
 push base.n ; push address of base on stack
 push dx
 ret
 base.n dw 16

 ; the bottom of the stack ie ss. This needs to be initialised when the 
 ; program starts. But the stack will also contain return pointers 
 ; for words ..
 bos:
 bos.n: dw 0
 ;hextable db "0123456789ABCDEF"
 dotstack.doc:
 db 'displays the contents of the stack without modifying.', 13, 10
 db 'in the current numeric base ', 13, 10
 db 'eg: .s /displays all items on stack '
 dw \\$-dotstack.doc
 dotstack:
 dw base ; link
 db 2, '.s' 
 dotstack.x:
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; base
 cld ; make lodsw go forwards 

 mov si, sp ; set bx to top of stack
 .nextitem:
 add si, 2 ; increment the stack pointer (and avoid fn return pointer)
 cmp si, [bos.n] ; check if is the last element of the stack
 je .exit ; 

 mov ax, [ss:si] ; get top item on stack into ax
 xor cx, cx ; set counter = 0
 .again:
 xor dx, dx ; dividend is ax
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; base
 div bx ; does dx:ax/bx. remainder  dx, quotient -> ax
 push dx ; save remainder (ie digit) on the stack 
 inc cx ; increment the digit counter
 cmp ax, 0 ; if the quotient != 0 do the next digit 
 jne .again ; loop while quotient > 0
 .print:
 pop ax ; get digit from the stack (digit in AL)
 mov bx, hextable ; translation table
 xlatb ; replace al with hex digit from table
 mov ah, 0eH ; print digit in al
 int 10H
 loop .print ; using cx the digit counter to loop 
 mov al, ' ' ; print a space between each value
 int 10H

 jmp .nextitem ; do next stack item
 .exit:
 ret
 ; * 

 start:

 mov ax, cs ; make data segment and es same as code segment
 mov ds, ax
 mov es, ax
 mov [bos.n], sp ; 
 
 mov [base.n], word 16 
 push 0xFFFF 
 push 0xAA23 
 push 0x1234 
 call dotstack.x

 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of MBR boot sector with 0s
 dw 0xAA55 ; The standard MBR boot signature
 ,,,


DOTSTACK REVISITED ....

 An interesting thing about forth is that we can write 
 forth code even without a compiler for it!

 So, in forth the implementation of dotstack could be
 : .s tos dup bos == if exit then @word . +2 dup bos == if exit then
 @word . +2 dup bos == if exit then ....
 
 So we need a loop to implement this. Once we have these primitives
 in assembler we can call each function in turn.

 A forth equivalent of x86 lodsw
 : @word+ @word swap 2 + swap

 where tos puts the address of the top of stack (sp register in x86)
 on the stack. bos puts pointer to bottom of stack on the stack.
 @word gets 2 bytes from pointer on the top of the stack.

LOAD ....

 loads a block 1024 bytes and interprets it.

TONUM BASE HEX ....

 >num: a word that converts an ascii buffer into a number 
 .hex: prints a number in hexadecimal. This is mainly for testing

 * a forthstyle >number function to convert ascii number onto stack
 ---------
 BITS 16
 [ORG 0]
 cr equ 13 ; carriage return
 lf equ 13 ; carriage return

 jmp 07C0h:start ; Goto segment 07C0

 ; the ascii number to convert
 buffer db 4, '102'

 ; example doc field with reverse count field
 ; the count field for the word doc is 2 bytes because it
 ; may contain a lot of text
 dothex.doc db 'displays a 2 byte number in hex format'
 dw \\$-dothex.doc
 dothex:
 dw 0 ; top of dictionary
 db 4, '.hex'
 dothex.x:
 pop bx ; return address
 pop dx ; the number to print (parameter on stack)
 push bx ; restore return address
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 4 ; number of digits to print
 .again:
 rol dx, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again
 mov al, 'H' ; print an H to indicate hex number
 mov ah, 0eH ; echo the char (just for debugging)
 int 10H
 ret
 hextable db "0123456789ABCDEF" ; translation table

 ; base is a standard forth word. Pushes current base (eg 16, 10)
 base:
 dw dothex
 db 4, 'base'
 base.x:
 pop dx
 push word [base.n]
 push dx
 ret
 base.n dw 10

 ; just set the base to 16 (hexadecimal)
 ; forth def.. : hex 16 base !
 hex:
 dw base
 db 3, 'hex'
 hex.x:
 mov word [base.n], 16
 ret

 ; just set the base to 10 (decimal numbers)
 ; forth def.. : base10 10 base !
 base10:
 dw hex 
 db 6, 'base10'
 base10.x:
 mov word [base.n], 10
 ret

 ; need to rethink these parameters
 ; 
 ; this would be called >number in many forths
 ; parameters, a buffer address and how many chars to convert
 ; leaves a pointer to first char unconverted
 ; (stack addr, chars - ptr char, n)
 ; this routine just assumes that the number is base 10 
 ; which it shouldn't
 num:
 dw base10 ; link to previous dict entry
 db 3, 'num' ; counted name of function
 num.x:
 pop dx ; juggle fn return ip
 pop cx ; maximum chars to convert
 pop si ; buffer address
 push dx ; restore fn return ip
 cld ; make lodsb step forward through chars
 ;push 0 ; initial result
 .again:
 lodsb ; get next char into al 
 ;mov ah, 0eH ; echo the char (just for debugging)
 ;int 10H
 cmp al, '0' ; check for valid digit (a-c)
 jb .exit ; if ascii value is less than '0' not digit
 cmp al, '9'
 ja .exit ; if ascii value greater than '9' not digit
 sub ah, ah ; set ah = 0
 sub al, '0' ; convert digit from ascii
 push ax ; store digit on stack
 mov ax, [num.result]
 mov bx, [base.n] ; multiply by 10 (for decimal numbers)
 ;mov bx, 16 ; multiply by 10 (for decimal numbers)
 mul bx ; do AX x BX and store in DX:AX 

 jo .toobig ; result too big to store in AX 
 pop bx ; get digit from stack
 add ax, bx
 mov [num.result], ax
 loop .again
 jmp .exit

 .toobig: 
 mov al, '!' ; print ! if integer is too big for 2 bytes
 mov ah, 0eH ; bios teletype function 
 int 10H ; invoke bios
 mov word [num.result], 0x0000 ; set result := 0

 .exit:
 pop dx ; return ip
 push si
 push word [num.result] 
 push dx
 ret
 num.result dw 0x0000 ; store intermediate results of conversion

 start:
 mov ax, cs ; initialize the data segment register DS
 mov ds, ax
 mov es, ax
 push buffer+1
 xor ax, ax
 mov al, [buffer]
 push ax 
 call num.x
 call dothex.x

 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

WORD TOIN ....

 This is a mess. should use scasb with es:di to skip leading 
 spaces of words. eg mov di, buff; mov al, ' '; repe scasb

 In this implementation of nextword, the input buffer must
 be zero terminated.

 >IN - this version of '>in' pushes onto the stack the 
 current parse position in the input text buffer.

 Word: skip over leading spaces and copy all characters to a 
 counted buffer. It is named nextword in the code below because
 'word' is a reserved word in nasm

 stack: wordbuffer, input buffer, max chars -- >in new input buffer position
 
 Code seems to be working.

 * an implementation of >in and word
 ----------------
 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0
 
 pad db 0
 times 64 db ' '

 ; puts the address of the input buffer on the stack
 input:
 dw 0
 db 5, 'input'
 input.x:
 pop dx
 push input.buffer
 push dx
 ret
 input.buffer db 17, 'c bigg. and. is ', 0 ; input buffer zero terminated
 times 64 db 0 

 ; I think it may be necessary to have a zero terminated
 ; input buffer to simplify this code.

 ; puts on stack the current parse position in the input buffer
 in:
 dw input 
 db 2, 'in'
 in.x:
 pop dx ; juggle return pointer
 push in.pointer ; current position
 push dx
 ret
 ; parameter fields for in, this should be initialized
 ; when the input buffer is filled
 in.pointer dw 0
 
 ; nextword will probably only work with 0 terminated strings.
 
 ; get next word from input buffer 
 ; could write a function with a delimiter parameter
 ; stack: target buffer, parse position in input -- target buffer, new parse
 nextword:
 dw in
 db 8, 'nextword'
 nextword.x:
 pop dx
 pop si ; where to start parsing in input buffer
 pop di ; counted buffer to write into
 push dx ; restore fn return ip
 cld ; make lodsb step forwards
 mov cx, 64 ; maximum target buffer
 mov bx, di ; save counted buffer address
 inc di ; skip count byte
 xor dx, dx ; use dl as a char counter
 .spaces:
 ; !! no use repe scasb with es:di
 lodsb ; get 1st char into al
 cmp al, 0 ; input buffer is zero terminated
 je .exit
 cmp al, ' ' ; skip all leading spaces
 loope .spaces 
 stosb ; put char in al into [di]
 inc dx ; increment char counter
 .again:
 ; !! now use repne movsb
 lodsb ; get next char into al 
 cmp al, 0 ; input buffer is zero terminated
 je .exit
 cmp al, ' ' ; stop if space encountered
 je .exit
 stosb ; put char in al into [di]
 inc dx ; increment char counter
 mov ah, 0Eh ; just for debugging type char
 ;int 10h ; 
 loop .again

 .exit:
 mov [bx], dl ; store char count in 1st byte of buffer
 pop dx ; juggle return fn ip
 push bx ; target buffer address
 push si ; new parse position in input buffer
 push dx ; restore
 ret

 ; the forth count word
 ; stack: addr -- addr+1, char count
 count:
 dw nextword 
 db 5, 'count'
 count.x:
 pop dx ; preserve return fn pointer
 pop bx ; buffer address
 xor ax, ax ; ax := 0
 mov al, [bx] ; get count into al
 inc bx
 push bx ; new buffer address
 push ax ; char count
 push dx ; restor fn return ip
 ret

 ; stack: buffer address, char count -- 
 type:
 dw count ; link to previous dictionary entry 
 db 4, 'type' 
 type.x:
 cld ; make lodsb step forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop si ; address of buffer to print
 push bx ; restore return function call
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .again ; decr cx loop counter 
 .exit:
 ret
 
 start:
 mov ax, cs ; cs is already correct (?!) 
 mov ds, ax ; data segment 
 mov es, ax ; es needed for stosb 
 ;mov sp, ? ; what about the stack pointer?

 push pad ; where to write the word
 push input.buffer+2 ; start of input buffer
 call nextword.x
 ; here we need to update >in with the top item on the stack
 ; which is the new parse position
 pop word [in.pointer] ; now 'pad' is left on stack
 call count.x
 call type.x
 push pad ; do again 
 push word [in.pointer] 
 call nextword.x
 pop word [in.pointer] ; now 'pad' is left on stack
 call count.x
 call type.x

 here: jmp here ; loop forever 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,

EXEC ....

 We need to recode the exec example testing return addresses
 since the stack is becoming mangled.

 Maybe use BP to point to the return ip 

FIND EXEC ....
 
 Find:
 find a word by name in a dictionary and return a pointer
 to its header. This should probably use a 'last' word
 to get the last dictiony entry, rather than starting with
 a pointer on the stack, as in this implementation.
 Exec:
 given a pointer to the header of a word execute that word
 If the pointer is zero exit ??

 * find a word by name in a forth style dictionary 
 ----------------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 buffer db 4, 'hash' 

 ; just print a hash for testing
 hash:
 dw 0
 db 4, 'hash'
 hash.x:
 mov ah, 0Eh ; just print a hash with bios
 mov al, '\\#'
 int 10h ; x86 bios interrupt
 ret

 ; duplicate top item on stack 
 dup:
 dw hash
 db 3, 'dup'
 dup.x:
 mov ah, 0Eh 
 mov al, '*'
 int 10h ; x86 bios interrupt
 pop ax ; preserve fn call return ip
 pop dx
 push dx ; duplicate top stack item
 push dx
 push ax ; restore return ip
 ret

 ; **
 ; execute a function given a pointer to its header on the stack
 ; if pointer is zero, then this should pop the 0 and exit, no??
 exec.doc:
 db 'execute a word given a pointer on the stack'
 db ' eg: lastword exec '
 dw \\$-exec.doc
 exec:
 dw dup ; link to prev
 db 4, 'exec'
 exec.x:
 pop ax
 pop bx ; get pointer to function
 push ax ; preserve fn return pointer
 cmp bx, 0 ; a zero pointer should not be executed
 je .exit
 add bx, 2 ; point to name count
 mov cl, [bx] ; get the count
 inc bx ; skip over count
 add bl, cl ; advance the pointer to the function

 ; !! not call [bx] thats a pointer to jumptable
 ; !!! call bx may change the stack (probably will) so we need 
 ; !!! to preserve the call return ip 
 pop word [execreturn] ; save return ip
 call bx ; call the fn pointed to by bx
 push word [execreturn] ; restore fn return ip
 .exit:
 ret
 ; a dodgy solution, but any register might get overwritten
 execreturn dw 0
 
 ; point and return a pointer to the found word or else 0 on the 
 ; stack
 ; stack: search term, start pointer -- function header pointer

 find.doc:
 db 'Search dictionary for a word and return pointer.'
 db ' eg: in lastword find '
 dw \\$-find.doc
 find:
 dw exec ; link to prev
 db 4, 'find'
 find.x:
 pop dx ; juggle fn return ip
 pop bx ; where to start searching (eg last entry in dict)
 pop ax ; counted string buffer to search for 
 push dx ; restore fn ip
 .again:
 xor cx, cx ; set cx:=0
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 mov cl, [si] ; the count of the search term
 inc cl ; we also have to compare the count bytes
 mov di, ax ; the search term pointer
 cld ; search forwards (clear direction flag)
 repe cmpsb ; compare all characters for equality
 je .found
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 je .notfound ; no more to words search, so exit
 ;push ax ; save ax, the search term pointer
 ;mov ah, 0Eh ; print a dot on each unsuccessful search
 ;mov al, '.' ; for debugging
 ;int 10h ; x86 bios interrupt
 ;pop ax ; restore the search term pointer 
 jmp .again 
 .notfound:
 pop dx
 push 0 ; not found so return 0
 push dx
 ret
 .found: 
 pop dx ; juggle return ip
 push bx ; return result on stack 
 push dx
 ret
 ; *

 start:
 mov ax, cs ; cs is already correct (?!) 
 mov ds, ax ; data segment 
 mov es, ax ; es needed for stosb 
 ;mov sp, ? ; what about the stack pointer?

 ;call star.x
 ;call hash.x
 push buffer ; word to search for
 push find
 call find.x
 call exec.x

 here: jmp here 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

DOCS ....

 This is a function which traverses the dictionary and prints the 
 name of the word/function and its docs if any. Forth systems did 
 not usually have docs in the word header for memory constraints
 but now, it seems ok to do it, and makes using the system more 
 pleasant to use

 * list words in the dictionary and their docs 
 ----------------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 dummy.doc:
 db 'The first word in the dict '
 dw \\$-dummy.doc
 dummy:
 dw 0
 db 5, 'dummy'
 dummy.x:
 ret

 ; this word has no document, so the doc offset is 0
 ; this means we can compile words without docs for space reasons
 dw 0
 nodoc:
 dw dummy
 db 6, 'no.doc'
 nodoc.x:
 ret

 more.doc:
 db 'About more ...'
 dw \\$-more.doc
 more:
 dw nodoc 
 db 4, 'more'

 moree.doc:
 db 'About moree ...'
 dw \\$-moree.doc
 moree:
 dw more 
 db 5, 'moree'

 stuff.doc:
 db 'This is not a real word, just testing "docs"! '
 dw \\$-stuff.doc
 stuff:
 dw moree
 db 5, 'stuff'
 stuff.x:
 ret

 lastword dw docs

 ; This assumes the dict has at least one word
 ; **
 docs.doc:
 db 'List all words in the dict and their docs. '
 db 'Has simple paging. Needs colours. Maybe just print up to'
 db '1st full stop since this is the summary.'
 db ' eg: docs '
 dw \\$-docs.doc
 docs:
 dw stuff ; link
 db 4, 'docs'
 docs.x:
 mov bx, [lastword]
 xor dx, dx ; use dx as a function counter
 .nextword:

 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 xor ax, ax ; ax := 0 
 lodsb ; al := [si]++
 mov cx, ax ; load the string count into cx for looping
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .nextchar:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .nextchar ; decr cx loop counter 
 mov al, 13 ; newline
 int 10h
 mov al, 10 
 int 10h
 mov cx, [bx-2] ; get doc char count into loop counter
 cmp cx, 0 ; no document for this word ?
 je .continue ; go to next word if no document here
 mov si, bx ; get a pointer to word doc pointer
 sub si, 2 
 sub si, cx ; start of word doc+2
 mov al, 32 ; print a space char before document
 int 10h
 .docnextchar:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .docnextchar ; decr cx loop counter 
 mov al, 13 ; newline
 int 10h
 mov al, 10 
 int 10h
 
 .continue:
 inc dx ; increment counter
 test dx, 0x07 ; pause after 8 function words
 jnz .nopage ; 
 mov al, '>' ; 
 int 10h
 mov al, '>' ; 
 int 10h
 mov ah, 0 ; wait for keypress bios function
 int 16h
 .nopage:
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 jne .nextword 
 .exit:
 ret
 ; *

 start:
 mov ax, cs ; cs is already correct (?!) 
 mov ds, ax ; data segment 
 mov es, ax ; es needed for stosb 

 call docs.x

 here: jmp here 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

LIST ....

 list all words by name in the dictionary. I dont think this 
 is a standard word

 The version below is much more succinct than the version which uses
 count, type etc. 

 * list all words in the dictionary, the canonical version
 ----------------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 dummy:
 dw 0
 db 11, 'top.of.dict'
 dummy.x:
 ret

 stuff:
 dw dummy
 db 5, 'stuff'
 stuff.x:
 ret

 lastword dw list
 ; This assumes the dict has at least one word
 ; **
 list.doc:
 db 'List all function words in the dictionary. List traverses the '
 db 'linked list dictionary and prints the name of each function '
 db 'word found in the function header. This '
 db 'leaves nothing on the stack. It relies on a lastword data '
 db 'item that contains a pointer to the last word in the dictionary '
 db '... needs paging etc '
 db ' eg: list '
 dw \\$-list.doc
 list:
 dw stuff ; link 
 db 4, 'list'
 list.x:
 mov bx, [lastword]
 .nextword:
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 push bx ; save bx (since count/type will mangle
 xor ax, ax ; ax := 0 
 lodsb ; al := [si]++
 mov cx, ax ; load the string count into cx for looping
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .nextchar:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .nextchar ; decr cx loop counter 
 mov al, 32 ; space char
 int 10h
 pop bx ; restore function header pointer
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 jne .nextword 
 .exit:
 ret
 ; *

 start:
 mov ax, cs ; cs is already correct (?!) 
 mov ds, ax ; data segment 
 mov es, ax ; es needed for stosb 

 call list.x

 here: jmp here 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * list all words in the dictionary, old version
 ----------------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 ; just print one space
 space:
 dw 0
 db 5, 'space'
 space.x:
 mov ah, 0eh ; bios type char function 
 mov al, 32 ; space character
 int 10h
 ret

 ; the forth count word
 ; stack: addr -- addr+1, char count
 count:
 dw space
 db 5, 'count'
 count.x:
 pop dx ; preserve return fn pointer
 pop bx ; buffer address
 xor ax, ax ; ax := 0
 mov al, [bx] ; get count into al
 inc bx
 push bx ; new buffer address
 push ax ; char count
 push dx ; restor fn return ip
 ret

 ; stack: buffer address, char count -- 
 type:
 dw count ; link to previous dictionary entry 
 db 4, 'type' 
 type.x:
 cld ; make lodsb step forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop si ; address of buffer to print
 push bx ; restore return function call
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .again ; decr cx loop counter 
 .exit:
 ret
 
 ; just pushes a pointer to last word in dict onto the stack
 last:
 dw type
 db 4, 'last'
 last.x:
 pop ax ; preserve fn ip return
 push word [lastword]
 push ax
 ret
 lastword dw listbefore

 ; This assumes the dict has at least one word

 list.doc:
 db 'List all function words in the dictionary. This probably '
 db 'leaves nothing on the stack. This version uses count type etc'
 db ' eg: list '
 dw \\$-list.doc
 list:
 dw last
 db 4, 'list'
 list.x:
 call last.x ; get pointer to last word
 pop bx ; where to start searching (eg last entry in dict)
 .again:
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 push bx ; save bx (since count/type will mangle
 push si ; the name pointer to print with count/type
 call count.x ; count the function name
 call type.x ; display the name
 call space.x ; print one space
 pop bx ; restore function header pointer
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 jne .again 

 pop dx
 ;push 0 ; could return a word count here ??
 push dx
 ret

 ; listbefore searches through the dictionary starting at a 
 ; given words and lists all words before that one. 
 ; stack: last word in dict --
 listbefore:
 dw list
 db 10, 'listbefore'
 listbefore.x:
 pop dx
 pop bx ; where to start searching (eg last entry in dict)
 push dx
 .again:
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 push bx ; save bx (since count/type will mangle
 push si ; the name pointer to print with count/type
 call count.x ; count the function name
 call type.x ; display the name
 call space.x ; print one space
 pop bx ; restore function header pointer
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 jne .again 
 ret

 start:
 mov ax, cs ; cs is already correct (?!) 
 mov ds, ax ; data segment 
 mov es, ax ; es needed for stosb 

 call list.x

 here: jmp here 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

ACCEPT TYPE COUNT....

 Below is a simple implementation of the standard forth words
 accept, count, and type 

 * read a line of input and store counted string in a buffer
 ------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0
 
 buffer db 0, ' '

 ; just print a newline
 crlf:
 dw 0
 db 4, 'crlf'
 crlf.x:
 mov ah, 0eh ; bios type char function 
 mov al, 13 ; cr lf
 int 10h
 mov al, 10
 int 10h
 ret

 ; **
 accept.doc:
 db 'get a specified number of input characters and place them '
 db 'in the given buffer. This function should probably allow for '
 db 'line-editing (at least backspace) and is terminated by an <enter> '
 db 'keypress. '
 db ' eg: input 20 accept '
 db ' accepts 20 typed characters from user and puts them at the '
 db ' address specified by "input". Leaves the address off the buffer '
 db ' on the stack. '
 dw \\$-accept.doc

 accept: 
 dw crlf ; link 1st word has a zero link 
 db 6, 'accept' ; forth-style function header 
 accept.x:
 pop bx ; juggle return pointer
 pop cx ; how many chars maximum to get 
 pop di ; where to copy chars
 push di ; save buffer address on stack 
 push bx ; restore return pointer

 inc di ; skip count byte to store 1st char
 xor dl, dl ; char counter := zero
 cld ; make stosb go forwards
 .again:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 cmp al, 13 ; was the key press an 'enter'?
 je .exit ; exit if enter pressed
 mov ah, 0eh ; echo the character
 int 10h
 stosb ; put the char into the buffer
 inc dl ; increment char counter
 loop .again
 .exit:
 pop ax ; return pointer
 pop bx ; buffer address
 mov [bx], dl ; store char count in buffer

 push bx ; restore buffer addr
 push ax ; restore fn return pointer
 ret
 
 ; the forth count word
 ; stack: addr -- addr+1, char count
 ; !! we can improve this version of count.. eg pop si, lodsb etc
 count:
 dw accept ; link to prev word
 db 5, 'count'
 count.x:
 pop dx ; preserve return fn pointer
 pop bx ; buffer address
 xor ax, ax ; ax := 0
 mov al, [bx] ; get count into al
 inc bx
 push bx ; new buffer address
 push ax ; char count
 push dx ; restor fn return ip
 ret
 ; *

 ; **
 ; stack: buffer address, char count -- 
 dw 0 ; no doc
 type:
 dw count ; link to previous dictionary entry 
 db 4, 'type' 
 type.x:
 cld ; make lodsb step forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop si ; address of buffer to print
 push bx ; restore return function call
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .again ; decr cx loop counter 
 .exit
 ret
 ; *

 start:
 mov ax, cs ; cs is already correct (?!) 
 mov ds, ax ; data segment 
 mov es, ax ; es needed for stosb 
 ;mov sp, ? ; what about the stack pointer?

 here:
 push buffer ; where to store chars (1st byte is count)
 push 8 ; maximum number of chars to accept and store
 call accept.x ; stack: addr
 call crlf.x
 call count.x ; st: addr, char count
 call type.x
 call crlf.x
 jmp here 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


INTERPRET ....

 This is my name for a forth word which was usually called 'quit'.
 It is what is called the "read evaluate print loop" cycle which
 allows a user and programmer to use the system interactively. 

 This word performs the basic functions: 
 get a line of input from keyboard/input into buffer
 copy one word at a time from input buffer to a word buffer
 see if word is a defined function, if so execute
 and print results
 if not, see if word is a number, if so push on stack
 loop again. 

 Even more powerfully, the read words can be "compiled" into
 a temporary buffer and then executed. This means that the semantic
 difference between compiling and interpreting is removed. 

 * a skeleton implementation of interpret
 ----------
 ; **

 interp.doc:
 db 'an interpreting loop for a forth style system.'
 db ' eg '
 dw \\$-interp.doc
 interp:
 dw 0 ; link to prev
 db 6, 'interp'
 interp.x:
 ;pop ax
 ;pop bx ; get pointer to function
 ;push ax ; preserve fn return pointer

 .again: 
 call inbuffer.x ; where to store chars (1st byte is count)
 push 40 ; maximum number of chars to accept and store
 call accept.x ; stack: addr
 call crlf.x
 ;call dup.x
 ;call count.x ; st: addr, char count
 ;call type.x
 push word [lastword] ; but find can get the top of dict from last
 call find.x
 pop ax
 cmp ax, 0 ; if find returns 0, then word was not found 
 je .again 
 push ax ; restore the word function pointer
 call exec.x ; the bug in exec was fixed (stack mangling)
 call crlf.x
 jmp .again 
 .exit:
 ret
 ; *
 ,,,

VAR ....
 
 compiles a new variable, which is just a name which pushes
 a pointer onto the stack

CONSTANT ....

 compiles a new constant which just pushes a value onto the stack

 eg: 
 : constant here last ! (inc here) word (copy counted word to memory)
 (pop stack to get constant value)
 compile 'pop dx, push constant, push dx, ret'
 ie compile return fn juggling code
 update here
 update last

COLON ....

 The : colon word is the normal compiler of a forth like system.

 colon reads the next word from the input buffer, creates 
 a function header with a link to previous dictionary entry
 eg uses a 'last' variable. colon compiles into the space
 pointed to by the 'here' variable.

FORTH LIKE SYSTEMS

MINIMAL BOOTLOADING FORTH STYLE SYSTEM ....

 The following is an attempt to write a minimal compiling forth
 like system which will run 'standalone' (without any operating system)
 on an x86 computer. The aims are to keep the size and complexity of
 the code to a minimum while having a colon : compiler.

 This system takes ideas from forth but is not forth. It doesnt have 
 a return stack, only interprets one word at a time, doesnt compile 
 anything etc...

 The following code provides a general template for how to boot load
 a forth like system which is greater than 512 bytes in length (ie
 greater than one boot sector. A complete forth should be no bigger 
 that 8K so about 16 sectors
 
 We have to use the following between the 2 stages of the bootloader
 to make the memory offsets work...!!!
 
 "section stage2 vstart=0" 

 Another solution is to
 compile the 2 stages of the bootloader separately so that the 
 assembler nasm can work out what are the correct memory addresses
 for labels etc.

 See answer at
 https://forum.nasm.us/index.php?topic=2160.0

 Another issue to consider, is whether we are in danger of 
 loading our new code on top of the existing stack, which would 
 not be nice. 

 Working!! Most needed words are now 

 * a very minimal bootloader with some forth ideas. 
 -----------------
 
 BITS 16
 [ORG 0]

 jmp 07C0h:load ; Goto segment 07C0
 drive db 0 ; a variable to hold boot drive number
 load:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 mov [drive], dl ; save the boot drive number
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax ; with a 4K gap between stack and code
 mov sp, 4096

 ; save the DL register or else dont modify it
 ; it contains the number of the boot medium (hard disk,
 ; usb memory stick etc)
 ; The 'floppy' Drive is NOT necesarily 0!!!

 reset: ; Reset the floppy drive
 mov ax, 0 ; 
 mov dl, [drive] ; the boot drive number (eg for usb 128)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ; es:bx determines where data loaded to
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 5 ; Load 5 sectors (only 1 used here)
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2 (sector 1 is the boot sector)
 mov dh, 0 ; Head=0
 mov dl, [drive] ; 
 int 13h ; Read!
 jc read ; ERROR => Try again

 jmp 1000h:0000 ; Jump to the loaded code 

 times 510-(\\$-\\$\\$) db 0 ; pad out the boot sector (512 bytes)
 dw 0AA55h ; end with standard boot signature

 ;Or just put a jmp start and jump over all the forth 
 ;definitions
 
 ; this below is the magic line to make the new memory offsets
 ; work. Or compile the 2 files separately
 ; Good answer from
 ; https://forum.nasm.us/index.php?topic=2160.0 

 section stage2 vstart=0

 ; the code to be loaded and executed
 ; cs is ok because of far jump
 ; is ds and es ok ? no, but stack seems ok
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 ; the interpreting loop
 here:
 push buffer ; where to store chars (1st byte is count)
 push 40 ; maximum number of chars to accept and store
 call accept.x ; stack: addr
 call crlf.x
 ;call dup.x
 ;call count.x ; st: addr, char count
 ;call type.x
 push find ; but find can get the top of dict from last
 call find.x
 pop ax
 cmp ax, 0 ; if find returns 0, then word was not found 
 je here
 push ax ; restore the word function pointer
 call exec.x ; the bug in exec was fixed (stack mangling)
 call crlf.x
 jmp here 

 hang: jmp hang

 ; start of forth style words.

 ; clear the screen, but this is doing something
 ; funny to the video mode.
 cls:
 dw 0 ; top of dictionary
 db 3, 'cls'
 cls.x:
 mov ah, 0
 mov al, 13h
 int 10H
 ret

 asc:
 dw cls
 db 3, 'asc'
 asc.x:
 mov cx, 255 
 .again:
 mov ah, 0eh
 mov al, cl
 int 10H
 mov al, ' ' 
 int 10H
 loop .again
 ret

 ; get one keystroke from user and place on stack
 key: 
 dw asc 
 db 3, 'key' ; forth-style function header 
 key.x:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 pop bx ; juggle function return pointer
 push ax ; save keypress value on stack
 push bx ; restore return pointer to stack
 ret

 emit:
 dw key ; link to previous dictionary entry 
 db 4, 'emit' 
 emit.x:
 pop bx ; juggle return address for call
 pop ax ; character to print (into al)
 push bx ; restore return function call
 mov ah, 0eh ; bios print character function
 int 10h
 ret

 ; just pushes a pointer to last word in dict onto the stack
 last:
 dw emit 
 db 4, 'last'
 last.x:
 pop ax ; preserve fn ip return
 push word [lastword]
 push ax
 ret
 lastword dw find

 ; puts the address of the input buffer on the stack 
 in:
 dw last
 db 2, 'in'
 in.x:
 pop ax ; preserve fn ip return
 push word buffer
 push ax
 ret
 buffer db 0
 times 80 db ' ' 

 ; just print one space
 space:
 dw in 
 db 5, 'space'
 space.x:
 mov ah, 0eh ; bios type char function 
 mov al, 32 ; space character
 int 10h
 ret

 ; -- dup just duplicates the top item on the stack
 dup: dw space ; link to previous word 
 db 3, 'dup' ; strings are 'counted' 
 dup.x:
 pop bx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push bx ; restore fn return address
 ret
 
 base.doc:
 db 'Puts the address of variable base on stack', 13, 10
 db 'Base determines the current numerical base for conversions', 13, 10
 db 'eg: 16 base ! /makes the base hexadecimal '
 dw \\$-base.doc
 base:
 dw dup ; previous word 
 db 4, 'base' ; forth style counted name
 base.x:
 pop dx ; juggle return pointer for word
 push base.n ; push address of base on stack
 push dx
 ret
 base.n dw 16

 ; There was a bug with this taking one too many values off
 ; the stack (ie the fn return pointer) and crashing the code
 ; but seems to be working now.
 ; at the moment this is only 8 bit division. Use dx:ax for 16 bit
 ; division, with xor dx, dx; remainder->dx; quotient->ax 
 ; 
 ; this should be called udot or unsigned

 hextable db "0123456789ABCDEF"
 dot.doc:
 db 'displays a 2 byte number on stack in current base', 13, 10
 db 'eg: 32 hex . /displays 20 (32 in hexadecimal) '
 dw \\$-dot.doc
 dot:
 dw base
 db 1, '.' 
 dot.x:
 ; expects the 16 bit number to display on stack in AX and 
 ; the base in BL register
 pop dx ; juggle the return function pointer
 pop ax ; 2 byte value in ax to print
 push dx ; restore the return ip
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; base
 xor cx, cx ; set counter = 0
 .again:
 xor dx, dx ; dividend is ax
 div bx ; does dx:ax/bx. remainder  dx, quotient -> ax
 push dx ; save remainder (ie digit) on the stack 
 inc cx ; increment the digit counter
 cmp ax, 0 ; if the quotient != 0 do the next digit 
 jne .again ; loop while quotient > 0
 .print:
 pop ax ; get digit from the stack (digit in AL)
 mov bx, hextable ; translation table
 xlatb ; replace al with hex digit from table
 mov ah, 0eH ; print digit in al
 int 10H
 loop .print ; using cx the digit counter to loop 
 ret

 ; just print a newline
 crlf:
 dw dot 
 db 4, 'crlf'
 crlf.x:
 mov ah, 0eh ; bios type char function 
 mov al, 13 ; cr lf
 int 10h
 mov al, 10
 int 10h
 ret

 ; get a line of input from the user 
 ; stack: buffer address, max characters -- buffer addr
 accept: 
 dw crlf ; 1st word has a zero link 
 db 6, 'accept' ; forth-style function header 
 accept.x:
 pop bx ; juggle return pointer
 pop cx ; how many chars maximum to get 
 pop di ; where to copy chars
 push di ; save buffer address on stack 
 push bx ; restore return pointer
 inc di ; skip count byte to store 1st char
 xor dl, dl ; char counter := zero
 cld ; make stosb go forwards
 .again:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 cmp al, 13 ; was the key press an 'enter'?
 je .exit ; exit if enter pressed
 mov ah, 0eh ; echo the character
 int 10h
 stosb ; put the char into the buffer
 inc dl ; increment char counter
 loop .again
 .exit:
 pop ax ; return pointer
 pop bx ; buffer address
 mov [bx], dl ; store char count in buffer
 push bx ; restore buffer addr
 push ax ; restore fn return pointer
 ret

 ; the forth count word
 ; stack: addr -- addr+1, char count
 count:
 dw accept 
 db 5, 'count'
 count.x:
 pop dx ; preserve return fn pointer
 pop bx ; buffer address
 xor ax, ax ; ax := 0
 mov al, [bx] ; get count into al
 inc bx
 push bx ; new buffer address
 push ax ; char count
 push dx ; restor fn return ip
 ret

 ; stack: buffer address, char count -- 
 type:
 dw count ; link to previous dictionary entry 
 db 4, 'type' 
 type.x:
 cld ; make lodsb step forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop si ; address of buffer to print
 push bx ; restore return function call
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .again ; decr cx loop counter 
 .exit:
 ret
 

 ; execute a function given a pointer to its header on the stack
 exec:
 dw type
 db 4, 'exec'
 exec.x:
 pop ax
 pop bx ; get pointer to function
 push ax ; preserve fn return pointer
 add bx, 2 ; point to name count
 mov cl, [bx] ; get the count
 inc bx ; skip over count
 add bl, cl ; advance the pointer to the function

 ; !! not call [bx] thats a pointer to jumptable
 ; !!! but call bx may change the stack and mangle 
 ; any register so we need to be careful here.
 ; the solution below seems dodgy but anyway

 pop word [returnexec] ; save return ip
 call bx ; call the fn pointed to by bx
 push word [returnexec] ; restore fn return ip
 ret
 ; a dodgy solution, but any register may be mangled by call bx
 returnexec dw 0

 ; list searches through the dictionary starting at a given
 ; and lists all words found. This assumes the dict has at
 ; least one word
 ; stack: --
 list:
 dw exec
 db 4, 'list'
 list.x:
 call last.x ; get pointer to the last word in dict
 pop bx 
 .again:
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 push bx ; save bx (since count/type will mangle
 push si ; the name pointer to print with count/type
 call count.x ; count the function name
 call type.x ; display the name
 call space.x ; print one space
 pop bx ; restore function header pointer
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 jne .again 

 pop dx
 ;push 0 ; could return a word count here ??
 push dx
 ret

 ; find searches through the dictionary starting at a given
 ; point and return a pointer to the found word or else 0 on the 
 ; stack
 ; stack: search term, start pointer -- function header pointer
 find.doc:
 db 'Search dictionary for a word and return pointer.'
 db ' eg: in find '
 dw \\$-find.doc
 find:
 dw list
 db 4, 'find'
 find.x:
 pop dx ; juggle fn return ip
 pop bx ; where to start searching (eg last entry in dict)
 pop ax ; counted string buffer to search for 
 push dx ; restore fn ip
 .again:
 xor cx, cx ; set cx:=0
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 mov cl, [si] ; the count of the search term
 inc cl ; we also have to compare the count bytes
 mov di, ax ; the search term pointer
 cld ; search forwards (clear direction flag)
 repe cmpsb ; compare all characters for equality
 je .found
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 je .notfound ; no more to words search, so exit
 ;push ax ; save ax, the search term pointer
 ;mov ah, 0Eh ; print a dot on each unsuccessful search
 ;mov al, '.' ; for debugging
 ;int 10h ; x86 bios interrupt
 ;pop ax ; restore the search term pointer 
 jmp .again 
 .notfound:
 pop dx
 push 0 ; not found so return 0
 push dx
 ret
 .found: 
 pop dx ; juggle return ip
 push bx ; return result on stack 
 push dx
 ret

 ,,,

OS FORTH STYLE BOOTLOADING SYSTEM ....
 
 Provides more common forth words and moves towards a more
 capable forth system.
 Forth features are: a linked list dictionary, accepts commands
 all parameter passed on the stack.
 Unforth features: no return stack (>R <R etc), no compiling (yet)

 We have to use the following between the 2 stages of the bootloader
 to make the memory offsets work...!!!
 
 "section stage2 vstart=0" 

 Another solution is to
 compile the 2 stages of the bootloader separately so that the 
 assembler nasm can work out what are the correct memory addresses
 for labels etc.

 See answer at
 https://forum.nasm.us/index.php?topic=2160.0

 Another issue to consider, is whether we are in danger of 
 loading our new code on top of the existing stack, which would 
 not be nice. 

 Working!! Most needed words are now 
 >number - convert an ascii number and put on stack
 word - get the next word from the input buffer
 create - make a dictionary header
 : - compile a dictionary definition

 Also we can just make fun words, like colourful ascii output
 etc.

 * an extensible bootloading forth-like system for x86 realmode 
 -----------------
 BITS 16
 [ORG 0]

 jmp 07C0h:load ; Goto segment 07C0
 drive db 0 ; a variable to hold boot drive number
 load:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 mov [drive], dl ; save the boot drive number
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax ; with a 4K gap between stack and code
 mov sp, 4096

 ; save the DL register or else dont modify it
 ; it contains the number of the boot medium (hard disk,
 ; usb memory stick etc)
 ; The 'floppy' Drive is NOT necesarily 0!!!

 reset: ; Reset the floppy drive
 mov ax, 0 ; 
 mov dl, [drive] ; the boot drive number (eg for usb 128)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ; es:bx determines where data loaded to
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 5 ; Load 5 sectors (only 1 used here)
 ; try mov cx, 0x0002 ; cylinder 0, sector 2
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2 (sector 1 is the boot sector)
 mov dh, 0 ; Head=0
 mov dl, [drive] ; 
 int 13h ; Read!
 jc read ; ERROR => Try again

 jmp 1000h:0000 ; Jump to the loaded code 

 times 510-(\\$-\\$\\$) db 0 ; pad out the boot sector (512 bytes)
 dw 0AA55h ; end with standard boot signature

 ;Or just put a jmp start and jump over all the forth 
 ;definitions
 
 ; this below is the magic line to make the new memory offsets
 ; work. Or compile the 2 files separately
 ; Good answer from
 ; https://forum.nasm.us/index.php?topic=2160.0 

 section stage2 vstart=0

 ; the code to be loaded and executed
 ; cs is ok because of far jump
 ; is ds and es ok ? no, but stack seems ok
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 mov [bos.n], sp ;set up bottom of stack

 ; the interpreting loop
 here:
 push in.buffer ; where to store chars (1st byte is count)
 push 40 ; maximum number of chars to accept and store
 call accept.x ; stack: addr
 call crlf.x
 pop bx
 push bx
 mov al, [bx] ; check if any characters entered
 cmp al, 0
 je here
 ;call dup.x
 ;call count.x ; st: addr, char count
 ;call type.x
 push find ; but find can get the top of dict from last
 call find.x
 pop ax
 cmp ax, 0 ; if find returns 0, then word was not found 
 je .number
 push ax ; restore the word function pointer
 call exec.x ; the bug in exec was fixed (stack mangling)
 call crlf.x
 jmp here 
 .number ; try to convert word into a number
 jmp here 

 hang: jmp hang

 ; start of forth style words.

 ; clear the screen, but this is doing something
 ; funny to the video mode.
 dw 0 ; a link to help document or else zero
 cls:
 dw 0 ; top of dictionary
 db 3, 'cls'
 cls.x:
 mov ah, 0
 mov al, 13h
 int 10H
 ret

 ; just put 50 on the stack for testing
 ; since I dont have >number yet !!!
 dw 0 ; a link to help document 
 fifty:
 dw cls 
 db 5, 'fifty'
 fifty.x:
 pop dx
 push 50
 push dx
 ret

 ; just set the base to 16 (hexadecimal)
 ; forth def.. : hex 16 base !
 hex:
 dw fifty
 db 3, 'hex'
 hex.x:
 mov word [base.n], 16
 ret

 ; just set the base to 10 (decimal numbers)
 ; forth def.. : base10 10 base !
 base10:
 dw hex 
 db 6, 'base10'
 base10.x:
 mov word [base.n], 10
 ret

 ; need to rethink these parameters
 ; 
 ; this would be called >number in many forths
 ; parameters, a buffer address and how many chars to convert
 ; leaves a pointer to first char unconverted
 ; (stack: addr, chars - ptr char, n)
 ; this routine just assumes that the number is base 10 
 ; which it shouldn't

 ; stack: buffer address - ptr char, result
 num:
 dw base10 ; link to previous dict entry
 db 3, 'num' ; counted name of function
 num.x:
 pop dx ; juggle fn return ip
 pop cx ; maximum chars to convert
 pop si ; buffer address
 push dx ; restore fn return ip
 cld ; make lodsb step forward through chars
 ;push 0 ; initial result
 .again:
 lodsb ; get next char into al 
 ;mov ah, 0eH ; echo the char (just for debugging)
 ;int 10H
 cmp al, '0' ; check for valid digit (a-c)
 jb .exit ; if ascii value is less than '0' not digit
 cmp al, '9'
 ja .exit ; if ascii value greater than '9' not digit
 sub ah, ah ; set ah = 0
 sub al, '0' ; convert digit from ascii
 push ax ; store digit on stack
 mov ax, [num.result]
 mov bx, [base.n] ; multiply by 10 (for decimal numbers)
 ;mov bx, 16 ; multiply by 10 (for decimal numbers)
 mul bx ; do AX x BX and store in DX:AX 
 jo .toobig ; result too big to store in AX 
 pop bx ; get digit from stack
 add ax, bx
 mov [num.result], ax
 loop .again
 jmp .exit
 .toobig: 
 mov al, '!' ; print ! if integer is too big for 2 bytes
 mov ah, 0eH ; bios teletype function 
 int 10H ; invoke bios
 mov word [num.result], 0x0000 ; set result := 0
 .exit:
 pop dx ; return ip
 push si
 push word [num.result] 
 push dx
 ret
 num.result dw 0x0000 ; store intermediate results of conversion

 dw 0 ; doc link or zero 
 asc:
 dw num
 db 3, 'asc'
 asc.x:
 mov cx, 255 
 .again:
 mov ah, 0eh
 mov al, cl
 int 10H
 mov al, ' ' 
 int 10H
 loop .again
 ret

 ; set video to big text 40x25
 vid:
 dw asc
 db 3, 'vid'
 vid.x: 
 mov ah, 0 ; set graphics display mode function.
 mov al, 1h ; mode 0h = text 40x25 
 int 10h ; set it!
 ret

 ; get one keystroke from user and place on stack
 key: 
 dw vid 
 db 3, 'key' ; forth-style function header 
 key.x:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 pop bx ; juggle function return pointer
 push ax ; save keypress value on stack
 push bx ; restore return pointer to stack
 ret

 emit:
 dw key ; link to previous dictionary entry 
 db 4, 'emit' 
 emit.x:
 pop bx ; juggle return address for call
 pop ax ; character to print (into al)
 push bx ; restore return function call
 mov ah, 0eh ; bios print character function
 int 10h
 ret

 ; type text in rainbow colours
 ; (stack: text buffer addr - )
 rainbow:
 dw emit
 db 7, 'rainbow' ; fn counted name
 rainbow.h:
 pop ax ; balance return ip
 pop si
 push ax
 xor bx, bx ; bx := 0 bh := 0 so no background colours
 xor cx, cx ; set cx:=0 
 mov cl, [si] ; the character count, used by loop and colours
 add si, cx ; set pointer to last char in string
 std ; make lodsb go in reverse
 .again:
 mov ah, 09h ; the 'function' number
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 lodsb ; get next char to al
 int 10h ; do it with a bios interrupt
 loop .again
 ret

 ; just pushes a pointer to last word in dict onto the stack
 last:
 dw rainbow 
 db 4, 'last'
 last.x:
 pop ax ; preserve fn ip return
 push word [lastword]
 push ax
 ret
 lastword dw find

 
 in.doc 
 db 'puts the address of the input buffer on the stack '
 dw \\$-in.doc
 in:
 dw last
 db 2, 'in'
 in.x:
 pop ax ; preserve fn ip return
 push word in.buffer
 push ax
 ret
 in.buffer db 0
 times 80 db ' ' 

 ; puts the current parse position in input buffer on stack 
 ; this gets updated by the nextword fn
 toin:
 dw in
 db 3, '>in'
 toin.x:
 pop ax ; preserve fn ip return
 push word in.buffer
 push ax
 ret
 toin.data dw 0

 pad.doc db 'puts the address of a scratch area on the stack'
 dw \\$-pad.doc
 pad:
 dw toin 
 db 3, 'pad'
 pad.x:
 pop ax ; preserve fn ip return
 push word pad.buffer
 push ax
 ret
 pad.buffer db 0
 times 80 db ' ' 

 ; just print one space
 space:
 dw pad
 db 5, 'space'
 space.x:
 mov ah, 0eh ; bios type char function 
 mov al, 32 ; space character
 int 10h
 ret

 ; -- dup just duplicates the top item on the stack
 dup: dw space ; link to previous word 
 db 3, 'dup' ; strings are 'counted' 
 dup.x:
 pop bx ; juggle fn return address
 pop ax ; get param to duplicate
 push ax
 push ax
 push bx ; restore fn return address
 ret
 
 base.doc:
 db 'Puts the address of variable base on stack', 13, 10
 db 'Base determines the current numerical base for conversions', 13, 10
 db 'eg: 16 base ! /makes the base hexadecimal '
 dw \\$-base.doc
 base:
 dw dup ; 
 db 4, 'base' ; forth style counted name
 base.x:
 pop dx ; juggle return pointer for word
 push base.n ; push address of base on stack
 push dx
 ret
 base.n dw 16

 ; the bottom of the stack ie ss. This needs to be initialised when the 
 ; program starts. But the stack will also contain return pointers 
 ; for words ..
 bos:
 bos.n: dw 0

 hextable db "0123456789ABCDEF"
 dotstack.doc:
 db 'displays the contents of the stack without modifying', 13, 10
 db 'in the current numeric base ', 13, 10
 db 'eg: .s /displays all items on stack '
 dw \\$-dotstack.doc
 dotstack:
 dw base
 db 2, '.s' 
 dotstack.x:
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; base
 mov si, sp ; set bx to top of stack
 .nextitem:
 add si, 2 ; increment the stack pointer (and avoid fn return pointer)
 cmp si, [bos.n] ; check if is the last element of the stack
 je .exit ; 

 mov ax, [ss:si] ; get top item on stack into ax
 xor cx, cx ; set counter = 0
 .again:
 xor dx, dx ; dividend is ax
 mov bx, [base.n] ; eg decimal, hex, any 1 < n < 17 ok 
 ; we cannot display any base > 16 at the moment
 xor bh, bh ; base
 div bx ; does dx:ax/bx. remainder  dx, quotient -> ax
 push dx ; save remainder (ie digit) on the stack 
 inc cx ; increment the digit counter
 cmp ax, 0 ; if the quotient != 0 do the next digit 
 jne .again ; loop while quotient > 0
 .print:
 pop ax ; get digit from the stack (digit in AL)
 mov bx, hextable ; translation table
 xlatb ; replace al with hex digit from table
 mov ah, 0eH ; print digit in al
 int 10H
 loop .print ; using cx the digit counter to loop 
 mov al, ' ' ; print a space between each value
 int 10H

 jmp .nextitem ; do next stack item
 .exit:
 ret


 ; example doc field with reverse count field
 dothex.doc db 'displays a 2 byte number in hex format'
 dw \\$-dothex.doc
 dothex:
 dw dotstack
 db 4, '.hex'
 ; below an example doc field
 ; db 20, 'displays a 2 byte number in hex format'
 dothex.x:
 pop bx ; return address
 pop dx ; the number to print (parameter on stack)
 push bx ; restore return address
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 4 ; number of digits to print
 .again:
 rol dx, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again
 mov al, 'H' ; print an H to indicate hex number
 mov ah, 0eH ; echo the char (just for debugging)
 int 10H
 ret
 ;hextable db "0123456789ABCDEF" ; translation table

 hash:
 dw dothex 
 db 4, 'hash'
 hash.x:
 mov ah, 0Eh ; just print a star with bios
 mov al, '\\#'
 int 10h ; x86 bios interrupt
 ret
 ; another fn for testing

 star:
 dw hash
 db 4, 'star'
 star.x:
 mov ah, 0Eh ; just print a star with bios
 mov al, '*'
 int 10h ; x86 bios interrupt
 ret

 ; just print a newline
 crlf:
 dw star
 db 4, 'crlf'
 crlf.x:
 mov ah, 0eh ; bios type char function 
 mov al, 13 ; cr lf
 int 10h
 mov al, 10
 int 10h
 ret

 ; get a line of input from the user 
 ; stack: buffer address, max characters -- buffer addr
 accept: 
 dw crlf ; 1st word has a zero link 
 db 6, 'accept' ; forth-style function header 
 accept.x:
 pop bx ; juggle return pointer
 pop cx ; how many chars maximum to get 
 pop di ; where to copy chars
 push di ; save buffer address on stack 
 push bx ; restore return pointer
 inc di ; skip count byte to store 1st char
 xor dl, dl ; char counter := zero
 cld ; make stosb go forwards
 .again:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 cmp al, 13 ; was the key press an 'enter'?
 je .exit ; exit if enter pressed
 mov ah, 0eh ; echo the character
 int 10h
 stosb ; put the char into the buffer
 inc dl ; increment char counter
 loop .again
 .exit:
 pop ax ; return pointer
 pop bx ; buffer address
 mov [bx], dl ; store char count in buffer
 push bx ; restore buffer addr
 push ax ; restore fn return pointer
 ret

 ; the forth count word
 ; stack: addr -- addr+1, char count
 count:
 dw accept 
 db 5, 'count'
 count.x:
 pop dx ; preserve return fn pointer
 pop bx ; buffer address
 xor ax, ax ; ax := 0
 mov al, [bx] ; get count into al
 inc bx
 push bx ; new buffer address (+1)
 push ax ; char count
 push dx ; restor fn return ip
 ret

 ; stack: buffer address, char count -- 
 type:
 dw count ; link to previous dictionary entry 
 db 4, 'type' 
 type.x:
 cld ; make lodsb step forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop si ; address of buffer to print
 push bx ; restore return function call
 cmp cx, 0 ; if nothing to print exit
 je .exit
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message into al
 int 10h ; x86 bios interrupt
 loop .again ; decr cx loop counter 
 .exit:
 ret
 

 ; execute a function given a pointer to its header on the stack
 exec:
 dw type
 db 4, 'exec'
 exec.x:
 pop ax
 pop bx ; get pointer to function
 push ax ; preserve fn return pointer
 add bx, 2 ; point to name count
 mov cl, [bx] ; get the count
 inc bx ; skip over count
 add bl, cl ; advance the pointer to the function

 ; !! not call [bx] thats a pointer to jumptable
 ; !!! but call bx may change the stack and mangle 
 ; any register so we need to be careful here.
 ; the solution below seems dodgy but anyway

 pop word [returnexec] ; save return ip
 call bx ; call the fn pointed to by bx
 push word [returnexec] ; restore fn return ip
 ret
 ; a dodgy solution, but any register may be mangled by call bx
 returnexec dw 0

 ; list searches through the dictionary starting at a given
 ; and lists all words found. This assumes the dict has at
 ; least one word
 ; stack: --
 list:
 dw exec
 db 4, 'list'
 list.x:
 call last.x ; get pointer to the last word in dict
 pop bx 
 .again:
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 push bx ; save bx (since count/type will mangle
 push si ; the name pointer to print with count/type
 call count.x ; count the function name
 call type.x ; display the name
 call space.x ; print one space
 pop bx ; restore function header pointer
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 jne .again 

 pop dx
 ;push 0 ; could return a word count here ??
 push dx
 ret

 ; find searches through the dictionary starting at a given
 ; point and return a pointer to the found word or else 0 on the 
 ; stack
 ; stack: search term, start pointer -- function header pointer
 find:
 dw list
 db 4, 'find'
 find.x:
 pop dx ; juggle fn return ip
 pop bx ; where to start searching (eg last entry in dict)
 pop ax ; counted string buffer to search for 
 push dx ; restore fn ip
 .again:
 xor cx, cx ; set cx:=0
 mov si, bx ; pointer to current function header
 add si, 2 ; the counted string is 2 bytes after header
 mov cl, [si] ; the count of the search term
 inc cl ; we also have to compare the count bytes
 mov di, ax ; the search term pointer
 cld ; search forwards (clear direction flag)
 repe cmpsb ; compare all characters for equality
 je .found
 mov bx, [bx] ; get the pointer to the next function (or 0)
 cmp bx, 0 ; if start of dict, then link is 0
 je .notfound ; no more to words search, so exit
 ;push ax ; save ax, the search term pointer
 ;mov ah, 0Eh ; print a dot on each unsuccessful search
 ;mov al, '.' ; for debugging
 ;int 10h ; x86 bios interrupt
 ;pop ax ; restore the search term pointer 
 jmp .again 
 .notfound:
 pop dx
 push 0 ; not found so return 0
 push dx
 ret
 .found: 
 pop dx ; juggle return ip
 push bx ; return result on stack 
 push dx
 ret
 

 ,,,

READING TEXT FROM KEYBOARD 

 Below is a forth style program, in traditional forths it would be
 called "expect" but this version has no line editing. This is working
 except for the char count at the beginning of the buffer. The next
 step is a 'find' function which looks up a word in the dict and
 executes it.

 * read a line of input and store in a buffer
 ------------
 jmp start

 buffer db 0, ' '
 ; just print a newline
 crlf:
 dw 0
 db 4, 'crlf'
 crlf.x:
 mov ah, 0eh ; bios type char function 
 mov al, 13 ; cr lf
 int 10h
 mov al, 10
 int 10h
 ret

 ; get a line of input from the user, this is called 'accept'
 ; in most forths
 ; stack parameters: buffer address, max characters
 line: 
 dw 0 ; 1st word has a zero link 
 db 4, 'line' ; forth-style function header 
 line.x:
 pop bx ; juggle return pointer
 pop cx ; how many chars maximum to get 
 pop ax ; where to copy chars
 push bx ; restore return pointer
 inc ax ; first byte of buffer is char count
 mov di, ax ; where stosb will put characters 
 sub dl, dl ; simple char counter
 .again:
 mov ah, 0 ; wait for keypress bios function
 int 16h
 cmp al, 13 ; was the key press an 'enter' 
 je .exit ; exit if enter pressed
 mov ah, 0eh ; echo the character
 int 10h
 stosb ; put the char into the buffer
 inc dl ; increment char counter
 loop .again
 .exit:
 ; have to save
 ;mov buffer, dl ; store char count in buffer
 ret

 ; type takes its arguments on the stack (buffer address, char count)
 type:
 dw line ; link to previous dictionary entry 
 db 5, 'type' 
 type.x:
 cld ; set dir flag to forwards
 pop bx ; juggle return address for call
 pop cx ; how many chars to print
 pop ax ; address of buffer to print
 push bx ; restore return function call
 mov si, ax ; maybe should use "lea si, ax" but how?? 
 mov ah, 0eh ; bios print character function
 .again:
 lodsb ; get next char from message 
 int 10h
 loop .again
 ret
 
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax 
 mov es, ax ; es needed for stosb 
 ;mov sp, ? ; what about the stack pointer?

 here:
 push buffer ; where to store chars
 push 8 ; maximum number of chars to store
 call line.x
 call crlf.x
 push buffer+1 ; buffer to print (but not count)
 push 8 ; chars to print
 call type.x
 call crlf.x
 jmp here 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * read a line of input and count the characters 
 ---------------------------------------------------------
 jmp start
 start:
 mov ax, 07C0h ; Initialize data segment register 
 mov ds, ax ; via AX 
 sub cx, cx ; set cx = 0
 .again
 mov ah, 0 ; bios read character function
 int 16h ; invoke bios interrupt
 mov ah, 0eH ; echo char entered
 int 10H
 inc cx ; increment the character counter
 cmp al, 13 ; was the key press a 'enter' 
 jne .again ; loop if not enter pressed
 mov al, 10 ; print a newline
 mov ah, 0eH
 int 10H
 dec cx ; dont include newline in character count
 mov ax, cx ; 
 add al, '0' ; convert count digit to ascii
 mov ah, 0eH ; x86 bios print char function
 int 10h ; print first digit of character count

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 Todo!!

 * read a line of input, and copy counted string to buffer 
 ---------------------------------------------------------
 jmp start
 buffer db ' '
 start:
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov di, buffer+2 ; the first word of buffer is for the count

 sub cx, cx ; set cx = 0
 .again
 mov ah, 0 ; x86 bios get char from keyboard
 int 16h ; invoke the bios
 mov ah, 0eH ; print char
 int 10H ; invoke bios function

 ; NOTE: I think the code below could be better done with
 ; stosb, and loopne
 mov [di], al ; copy AL to buffer
 inc di
 inc cx ; increment the character counter
 cmp al, 13 ; was the key press a 'enter' 
 jne .again ; loop if not enter pressed
 mov al, 10 ; print a newline
 mov ah, 0eH
 int 10H
 dec cx ; dont include newline in character count
 mov ax, cx ; 
 add al, '0' ; convert count digit to ascii
 mov ah, 0eH ; x86 bios print char function
 int 10h ; print first digit of character count
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
NUMBERS AS USER INPUT ....

 We would like to be able to read a positive integer
 entered by the user. So we need to read each digit,
 then multiply it by the base and add the next digit,
 and so on.


MOUSE INPUT 

 \url{http://wiki.osdev.org/Mouse\\_Input}
 just enough info

 The good news is that a modern usb mouse emulates or behaves
 like a normal PS/2 mouse. So you dont have to actually write
 a usb 'stack' (software api) in order to use the mouse. Phew!

 Mouse (and Keyboard) data come on port 0x60
 port 0x64 bit 1 - data is available, bit 5 - data is from
 mouse, not keyboard.

ASCII CODE

 The ascii code is a way of mapping common western (latin)
 characters to numbers
 
 * some common useful ascii codes
 -----------
 cr equ 13 ; carriage return
 lf equ 10 ; line feed
 bell equ 7 ; bell (sort of)
 spc equ 32 ; space
 bs equ 8 ; back space
 del equ 127 ; 'delete' character
 ,,,

 * print ascii in descending order
 --------------------
 jmp start
 start:
 mov cx, 255 
 .again:
 mov ah, 0eh
 mov al, cl
 int 10H
 mov al, ' ' 
 int 10H
 loop .again
 .exit:
 hang: jmp hang ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * print the first 128 ascii in ascending order
 --------------------
 jmp start
 start:
 mov cx, 128 
 .again:
 mov ah, 0eh ; bios teletype function
 mov al, 128
 sub al, cl
 int 10H ; call bios function
 mov al, ' ' ; print a space
 int 10H ; do it
 loop .again ; loop 128 times
 .exit:
 hang: jmp hang ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The code below is designed to use direct memory writes

 * print a coloured ascii table with values in hex
 -------------
 [org 0]
 jmp 07C0h:start

 ; **
 hextable db "0123456789ABCDEF" ; digit translation table
 dw 0 ; no doc
 asctable:
 dw 0 ; link
 db 8, 'asctable'
 asctable.x:
 mov cx, 0x00FF ; loop through all asci chars
 mov bx, hextable ; pointer to digit translation table
 mov ax, es
 mov fs, ax ; save es pointer
 mov ax, 0xB800 ; video memory address 
 mov es, ax ; set up es for stosw 
 mov di, 320 ; start at 3rd line (80 chars/ line, 2 bytes/char)

 .nextchar:
 mov dx, 0x00FF ; loop through all asci chars
 sub dx, cx ; might be better to use incrementing counter

 mov ah, 0x0E
 mov al, dl ; high nibble of ascii code to print 
 rol al, 4 ; print first digit
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 ; put colour in ah for direct memory write
 mov ah, 0b00000010 ; green on black 
 stosw

 mov al, dl ; lower nibble of ascii code 
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 mov ah, 0b00000010 ; green on black 
 stosw

 mov ax, 0x0020 ; a black on black space
 stosw
 cmp dl, 0x0D ; dont print a formfeed
 je .skip
 mov al, dl ; print actual asci char
 mov ah, 0b00001111 ; white on black 
 stosw
 .skip:
 mov ax, 0x0020 ; a black on black space
 stosw
 ; newline calculation is a bit trickier with direct memory display
 ;test dl, 0x0F
 ;jmpne .end
 .end:
 loop .nextchar
 mov ax, fs
 mov es, ax ; restore es pointer 
 ret
 ; *

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax ; stosw uses es segment reg
 ;call asctable.x
 mov bx, asctable.x ; check for strange bugs
 call bx
 jmp \\$

 times 510-(\\$-\\$\\$) db 0 ; Pad boot sector with 0s
 dw 0xAA55 ; MBR boot signature

 ,,,,

 * print a monochrome ascii table with values in hex
 -------------
 [org 0]
 jmp 07C0h:start

 hextable db "0123456789ABCDEF" ; digit translation table
 asctablebw:
 dw 0
 db 8, 'asctable'
 asctablebw.x:
 mov cx, 0x00FF ; loop through all asci chars
 mov bx, hextable ; pointer to digit translation table
 .nextchar:
 mov dx, 0x00FF ; loop through all asci chars
 sub dx, cx ; might be better to use incrementing counter

 mov ah, 0x0E ; x86 int 0x10 type char function
 mov al, dl ; high nibble of ascii code to print 
 rol al, 4 ; print first digit
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 int 10h ; invoke bios 

 mov al, dl ; lower nibble of ascii code 
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 int 10h ; invoke bios 

 mov ah, 0x0E ; separate with a space 
 mov al, ' ' 
 int 10h
 cmp dl, 0x0D ; dont print a formfeed
 je .skip
 mov al, dl ; print actual asci char
 int 10h ; x86 bios interrupt
 .skip:
 mov al, ' ' 
 int 10h

 test cl, 0b00000111 ; modulus 8, 
 jne .page ; 

 mov ah, 0x0E ; print a new line every 8 chars
 mov al, 13
 int 10h
 mov al, 10
 int 10h

 .page:
 cmp cl, 0x7F ; page chars at 128th char 
 jne .end
 mov ah,0 ; wait for any key:
 int 16h 

 .end: 
 loop .nextchar
 ret

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax ; stosw uses es segment reg
 call asctablebw.x
 jmp \\$

 times 510-(\\$-\\$\\$) db 0 ; Pad boot sector with 0s
 dw 0xAA55 ; MBR boot signature

 ,,,,

 * print ascii characters (and some non-ascii) in a table 
 ---------------------------------------------------------
 jmp start
 \\%include 'printi8.asm'
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov cx, 0 
 .again:
 mov al, cl
 mov bl, 10 
 call printi8 ; print value of ascii character
 mov ah, 0eH
 mov al, ':' ; print a separator character
 int 10H
 mov al, cl 
 int 10H
 mov al, ' ' 
 int 10H
 inc cx
 cmp cx, 0xFF
 je .exit
 mov ax, cx
 and ax, 0007h ; mod 8
 cmp ax, 0
 jne .again
 mov ah, 0eh
 mov al, 13
 int 10h
 mov ah, 0eh
 mov al, 10
 int 10h
 jmp .again
 .exit:
 hang: jmp hang ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

FONTS AND TEXT

 \url{http://wiki.osdev.org/VGA\\_Fonts}
 good info

 A 'standard' x86 bios outputs text in one of its 
 'text video modes'. Its uses font 'bitmaps' with 8 columns
 and 16 rows for each character.Within the bitmap a 0 represents
 the background colour, and a 1 represents the foreground colour.
 The first row of the glyph (character) - 8 bits or 1 byte is
 contained in the first byte of the bitmap, the 2nd row in the
 second byte... etc. 

 In the various graphics video modes, there are no BIOS 
 functions for writing a character to the screen. The programmer
 must provide this functionality, by writing a character 1 pixel
 at a time.

 The first step to writing characters in graphics
 mode is getting the bitmap fonts data matrix...

 Standard vga fonts are 8x16 pixels. Each byte contains 1 row of the
 given character- the first byte is the top row etc. 1=foreground,
 0=background So character takes up 16 bytes.

 The bios contains tables of information 
 laying out the fonts used to display characters.

 Int 10h service AH=11h, subservice AL=30h

 * get the table of font information, untested
 -------------
 mov ax, 1130h ; (Get font information) 
 mov bh, 06h ; 8x16 font (vga/mcga) 
 int 10h ; leave font table pointer in ES:BP 
 ,,,
 
 Next step: access a character and display it pixel by 
 pixel in some graphics mode.

 To access 1 character from the 4K (4096byte) character
 bitmap multiply ascii code by 16 (bytes per character) and 
 add to the ES:BP register 

 * code to store the full 4K (256 chars x 16 bytes/char) bitmap
 -------------------
 ;in: es:di=4k buffer
 ;out: buffer filled with font
 push ds
 push es
 ;ask BIOS to return VGA bitmap fonts in es:bp
 mov ax, 1130h
 mov bh, 6
 int 10h
 ;copy charmap
 push es ; make the extended segment and 
 pop ds ; the data segment the same 
 pop es ;
 mov si, bp
 mov cx, 256*16/4
 rep movsd
 pop ds
 ,,,

 * display an ascii character by copying a bios font bitmap 
 -------------
 jmp start
 \\%include 'printi8.asm'
 char db '\\$'
 start:
 mov ax, 07C0h
 mov ds, ax ; set the data segment

 mov ax, 1130h ; (Get font information) 
 mov bh, 06h ; 8x16 font (vga/mcga) 
 int 10h ; leave font table pointer in ES:BP 
 mov al, [char]
 mov bl, 10
 call printi8
 ; left shift char 4 times (to multiply by 16)
 ; eg
 ; sub ah, ah mov cx, 4 shr ax, 4
 ; add this to the bitmap offset
 ; add bp, ax
 ; now just print out the bytes or draw pixel by
 hang: jmp hang
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
 Make a function bigchar which has stack parameters cursorx:y, colour,
 character and return cursorx:y or next character. This will allow us to print
 a big clock with cmos rtc easily. 
 
 The code below enters a loop an displays ascii characters in big
 blocky format. This code could be simplified by writing directly
 to video memory, instead of using x86 bios interrupts, which are 
 slow and clunky.

 The "show" code has a great deal of redundant rubbish in it

 * get a bios font glyph from memory and push pointer on stack 
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 ; **

 block equ 0xFE ; ascii code for small block
 bigblock equ 219 ; ascii code for big block
 alpha equ 224 ; Greek letter alpha 
 beta equ 225 ; Greek letter beta
 gamma equ 226 ; Greek letter gamma
 char db '*'
 ;char db [gamma]

 ; (stack: char -- segment address, pointer -> bios glyph font address )
 getglyph.doc:
 db 'gets the font map pointer to a bios glyph', 13, 10
 db 'eg: 65 getglyph show'
 db 'stack: char -- seg address, offset to glyph'
 dw \\$-getglyph.doc
 getglyph:
 dw 0 ; link to previous dict word or null
 db 8, 'getglyph' ; forth counted name
 getglyph.x:
 mov ax, 1130h ; (Get font information) 
 mov bh, 06h ; 8x16 font (vga/mcga) 
 int 10h ; leave font table pointer in ES:BP 
 pop dx ; balance return fn
 pop ax ; character -> al
 push dx
 xor ah, ah ; set ax := al
 shl ax, 4 ; set ax := ax * 16 (16 bytes per character)
 add bp, ax ; add char offset pointer to font map 
 pop dx ; balance return IP
 push es ; segment address of glyph, Yes need to do this
 push bp ; put pointer to glyph on stack
 push dx 
 ret

 ; (stack: segment address, glyph address - )
 ; the code below modifies es segment register which doesnt
 ; seem a good idea, and could lead to some tricky bugs
 show.render db ' ', block
 show.doc:
 db 'displays an 8x16 glyph in text mode', 13, 10
 db 'eg: 65 getglyph show'
 db ' [stack: segment address, glyph address -- ] '
 dw \\$-show.doc
 show:
 dw getglyph ; link to previous dict word or null
 db 4, 'show' ; forth counted name
 show.x:
 pop dx ; balance return ip
 pop si ; pointer to 1st byte of glyph
 mov ax, ds ; save ds in es
 mov fs, ax ; could save in fs, gs etc... better
 pop ds ; get segment address in DS register
 push dx
 xor bx, bx ; bx := 0 bh := 0 so no background colours
 mov cx, 16 ; 16 bytes in the glyph (8 x 16 pixels)
 cld ; make lodsb go forwards ("clear direction flag")
 .nextrow:
 lodsb ; get next byte (glyph row) to al
 mov bl, al ; save glyph row to bl
 push cx ; save current row number
 mov cx, 8 ; loop through the 8 bits
 .nextbit:
 test bl, 0b10000000 ; check if bit is set
 jne .fill ; 
 mov al, ' ' ; space character
 jmp .next
 .fill:
 mov al, block ; fill character
 .next:
 mov ah, 0eh ; bios type char function 
 int 10h ; x86 bios int 
 rol bl, 1
 loop .nextbit

 mov ah, 0eh ; bios type char function 
 mov al, 13 ; <return> char
 int 10h ; 
 mov al, 10 ; <form feed> 
 int 10h ; 
 pop cx ; restore current row number 
 loop .nextrow
 mov ax, fs ; restore ds from es
 mov ds, ax ; 
 ret

 glyphpress.doc:
 db 'Displays bigs glyphs of key presses'
 dw \\$-glyphpress.doc
 glyphpress:
 dw show ; link to previous dict word or null
 db 10, 'glyphpress' ; forth counted name
 glyphpress.x:

 .loop:
 xor ax, ax ; ax := 0
 mov ah, 0 ; wait for keypress bios function
 int 16h ; key value -> reg: al
 cmp al, ' ' ; exit loop if space is pressed
 je .exit
 push ax ; push the character on the stack (value in al, ah==0)
 ; mov ah, 0eh ; bios type char function 
 ; int 10h ; 
 call getglyph.x 
 call show.x
 jmp .loop

 .exit:
 ret
 ; *

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 call glyphpress.x
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 

 * a big char 
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 block equ 0xFE ; ascii code for small block
 bigblock equ 219 ; ascii code for big block
 alpha equ 224 ; Greek letter alpha 
 beta equ 225 ; Greek letter beta
 gamma equ 226 ; Greek letter gamma
 ;char db '*'
 char db alpha 
 ;char db [gamma]

 ; (stack: char -- segment address, pointer -> bios glyph font address )
 getglyph.doc:
 db 'gets the font map pointer to a bios glyph', 13, 10
 db 'eg: 65 getglyph show'
 db 'stack: char  seg address, offset to glyph'
 dw \\$-getglyph.doc
 getglyph:
 dw 0 ; link to previous dict word or null
 db 8, 'getglyph' ; forth counted name
 getglyph.x:

 mov ax, 1130h ; (Get font information) 
 mov bh, 06h ; 8x16 font (vga/mcga) 
 int 10h ; leave font table pointer in ES:BP 
 pop dx ; balance return fn
 pop ax ; character -> al
 push dx
 xor ah, ah ; set ax := al
 shl ax, 4 ; set ax := ax * 16 (16 bytes per character)
 add bp, ax ; add char offset pointer to font map 

 pop dx ; balance return IP
 push es ; segment address of glyph, Yes need to do this
 push bp ; put pointer to glyph on stack
 push dx 
 ret

 ; (stack: segment address, glyph address - )
 ; the code below modifies es segment register which doesnt
 ; seem a good idea, and could lead to some tricky bugs

 ; 
 bigchar.doc:
 db 'displays an 8x16 glyph in text mode', 13, 10
 db 'eg: 65 getglyph show'
 db ' [stack: segment address, glyph address  ] '
 db 'should be [stack: startx:y, segment address, glyph address -- endx:y ] '
 db 'or ??? [stack: startx:y, colour, char, nextx:y  ] '
 dw \\$-bigchar.doc
 bigchar:
 dw getglyph ; link to previous dict word or null
 db 7, 'bigchar' ; forth counted name
 bigchar.x:
 pop bx ; balance return ip
 pop si ; pointer to 1st byte of glyph
 mov ax, ds ; save ds in es
 mov fs, ax ; 
 pop ds ; get segment address in DS register
 push bx
 xor bx, bx ; bx := 0 bh := 0 so no background colours
 mov cx, 16 ; 16 bytes in the glyph
 cld ; make lodsb go forwards ("clear direction flag")
 .nextrow:
 lodsb ; get next byte (glyph row) to al
 mov bl, al ; save glyph row to bl
 push cx ; save current row number
 mov cx, 8 ; print the 8 bits
 .nextbit:
 mov dl, bl ; 
 test dl, 0b10000000 ; check if bit set
 jnz .fill
 mov ah, 0eh ; bios type char function 
 mov al, ' ' ; fill character
 int 10h ; 
 jmp .next
 .fill:
 mov ah, 0eh ; bios type char function 
 mov al, bigblock ; fill character
 int 10h ; 
 .next:
 rol bl, 1
 loop .nextbit

 mov ah, 0eh ; bios type char function 
 mov al, 13 ; <return> char
 int 10h ; 
 mov al, 10 ; <form feed> 
 int 10h ; 
 pop cx ; restore current row number 
 loop .nextrow
 mov ax, fs ; restore ds from es
 mov ds, ax ; 
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 xor ax, ax
 push 0x0909 ; initial cursor position, not used yet...
 mov al, [char] ; push the big character to print on the stack
 push ax
 call getglyph.x 
 call bigchar.x
 jmp \\$ ; loop forever

 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
CUSTOM FONTS

 You can modify a glyph for a character in text mode
 just by writing the desired bit map to the correct location
 in memory. The code is identical to reading the 4K font 
 bit map but change the direction of the MOV instruction.

 Another way to set fonts used for text mode
 Ralph Brown Interrupt list Int 10/AX=1110h.

 The following displays a 8x16 glyph given a pointer to
 its 1st byte on the stack. The code is written as a forth-style
 linked list dictionary. This code can be used display glyphs
 from bios memory (standard text mode ascii characters).

 Maybe need to have a segment address on the stack as well?
 (since ax=1130h, int 10h returns address in es:bp)

 * display a 8x16 pixel glyph
 -----------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0
 
 glyph db 100111001
 db 00000000b
 db 01111111b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01111111b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01100011b
 db 00000000b
 db 00000000b
 db 11111111b

 block equ 0xFE ; ascii code for small block
 ;block equ 219 ; ascii code for big block

 ; (stack: glyph address - )
 show:
 dw 0 ; link to previous dict word or null
 db 4, 'show' ; forth counted name
 show.x:
 pop dx ; balance return ip
 pop si ; pointer to 1st byte of glyph
 push dx
 xor bx, bx ; bx := 0 bh := 0 so no background colours
 mov cx, 16 ; 16 bytes in the glyph
 cld ; make lodsb go forwards ("clear direction flag")
 .nextrow:
 lodsb ; get next byte (glyph row) to al
 mov bl, al ; save glyph row to bl
 ;mov ah, 0eh ; bios type char function 
 ;int 10h ; do it with a bios interrupt
 push cx ; save current row number
 mov cx, 8 ; print the 8 bits
 .nextbit:
 mov dl, bl ; 
 and dl, 0b10000000 ; check if bit set
 cmp dl, 0
 jne .fill
 mov ah, 0eh ; bios type char function 
 mov al, ' ' ; fill character
 int 10h ; 
 jmp .next
 .fill:
 mov ah, 0eh ; bios type char function 
 mov al, block ; fill character
 int 10h ; 
 .next:
 rol bl, 1
 loop .nextbit

 mov ah, 0eh ; bios type char function 
 mov al, 13 ; <return> char
 int 10h ; 
 mov al, 10 ; <form feed> 
 int 10h ; 
 pop cx ; restore current row number 
 loop .nextrow
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 push glyph 
 call show.x 
 jmp \\$ ; loop forever

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,


 * define a custom 8x16 pixel 'A' glyph in assembly language
 -----------------
 glyph db 00000000b
 db 00000000b
 db 01111111b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01111111b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01100011b
 db 01100011b
 db 00000000b
 db 00000000b
 db 00000000b
 ,,,,


 * How to redefine vga ega font map
 ---------

 INT 10H 1110H: Load and Activate User-Defined Font

 Compatibility: EGA VGA
 Expects:
 AX 1110H
 BH height of each character (bytes per character definition)
 BL font block to load (EGA: 0-3; VGA: 0-7)
 CX number of characters to redefine
 DX ASCII code of the first character defined at ES:BP
 ES:BP address of font-definition information
 ,,,,


 * make a series of chess glyphs to replace \\#\\$\\%\\^\\&* etc 
 ---------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 ; The chess glyphs to insert in bios font map at \\#

 pawn:
 db 0b00000000
 db 0b00000000
 db 0b00000000
 db 0b00000000
 db 0b00011000
 db 0b00111100
 db 0b00111100
 db 0b01111110
 db 0b00111100
 db 0b00111100
 db 0b01111110
 db 0b11111111
 db 0b11111111
 db 0b00000000
 db 0b00000000
 db 0b00000000

 knight:
 db 0b00000000
 db 0b00000000
 db 0b00000000
 db 0b01110000
 db 0b11111000
 db 0b11111100
 db 0b11011100
 db 0b11011110
 db 0b00011100
 db 0b00011100
 db 0b00011110
 db 0b00111111
 db 0b01111111
 db 0b00000000
 db 0b00000000
 db 0b00000000

 castle:
 db 0b00000000
 db 0b00000000
 db 0b11011011
 db 0b11111111
 db 0b11111111
 db 0b00111100
 db 0b00111100
 db 0b00111100
 db 0b00111100
 db 0b00111100
 db 0b01111110
 db 0b11111111
 db 0b11111111
 db 0b00000000
 db 0b00000000
 db 0b00000000

 ; (stack: char -- segment address, pointer -> bios glyph font address )
 chessglyph.doc:
 db 'inserts some custom chess glyphs into bios font map', 13, 10
 db 'starting somewhere at \\#'
 dw \\$-chessglyph.doc
 chessglyph:
 dw 0 ; link to previous dict word or null
 db 9, 'chessglyph' ; forth counted name
 chessglyph.x:
 ; for int 10h redefine font
 ; AX 1110H
 ; BH height of each character (bytes per character definition)
 ; CX number of characters to redefine
 ; DX ASCII code of the first character defined at ES:BP
 ; ES:BP address of font-definition information

 mov bl, 0 ; font block ??
 mov bh, 16 ; height of font (bytes per character)
 mov cx, 3 ; number of chars to redefine
 mov dx, '\\#' ; redefine glyphs starting at \\# 
 mov bp, pawn ; es:bp points to start font data to use 
 mov ax, 0x1110 ; redefine bios font
 int 10h
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 mov ah, 0
 mov al, 0 ; video mode 40x25 text, big text
 int 10h ; set video mode big text 
 call chessglyph.x
 mov ah, 0x0e
 mov cx, 3
 mov al, '@'
 .again:
 mov al, '\\#'-1 ; print modified \\#\\$\\%\\^\\& etc
 add al, cl 
 int 10h
 mov al, ' '
 int 10h
 loop .again

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 

 * set a modified bios glyph for an asci character
 ---------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 ; The custom glyph to insert in bios font map
 castle 
 db 0b00000000
 db 0b00000000
 db 0b11011011
 db 0b11111111
 db 0b11111111
 db 0b00111100
 db 0b00111100
 db 0b00111100
 db 0b00111100
 db 0b00111100
 db 0b01111110
 db 0b11111111
 db 0b11111111
 db 0b00000000
 db 0b00000000
 db 0b00000000

 ; (stack: char -- segment address, pointer -> bios glyph font address )
 biosglyph.doc:
 db 'sets letter a to a custom glyph', 13, 10
 db 'eg: biosglyph'
 db 'stack: char-to-mod, ptr->8x16data '
 dw \\$-biosglyph.doc
 biosglyph:
 dw 0 ; link to previous dict word or null
 db 9, 'biosglyph' ; forth counted name
 biosglyph.x:
 ; for int 10h redefine font
 ; AX 1110H
 ; BH height of each character (bytes per character definition)
 ; CX number of characters to redefine
 ; DX ASCII code of the first character defined at ES:BP
 ; ES:BP address of font-definition information

 mov bl, 0 ; font block ??
 mov bh, 16 ; height of font (bytes per character)
 mov cx, 1 ; only redefine one char
 mov dx, '\\#' ; redefine only 'a' glyph
 mov bp, castle ; es:bp points to font data 
 mov ax, 0x1110 ; redefine bios font
 int 10h
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 mov ah, 0
 mov al, 0 ; video mode 40x25 text, big text
 int 10h ; set video mode big text 
 call biosglyph.x
 mov ah, 0x0e
 mov cx, 5
 .again:
 mov al, '\\#'
 int 10h
 mov al, ' '
 int 10h
 loop .again

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
PIXELS AND DRAWING

BITMAPS ....

 * render a bitmap in standard text mode 
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 block equ 0xFE ; ascii code for small block
 bigblock equ 219 ; ascii code for big block
 alpha equ 224 ; Greek letter alpha 
 beta equ 225 ; Greek letter beta
 gamma equ 226 ; Greek letter gamma
 char db '*'
 ;char db [gamma]

 ship:
 db 8, 8 ; width x height bytes
 db 0b00100100 ; glyph data
 db 0b01111110
 db 0b11100111
 db 0b00111100
 db 0b01100110
 db 0b01110010
 db 0b00000010
 db 0b00001110

 ; (stack: segment address, glyph address - )
 bitmap.glyphpointer dw 0
 bitmap.render db ' ', block
 bitmap.doc:
 db 'Displays a bitmap in text/graphics mode at a given point '
 db 'on the screen.'
 db 'The 1st byte of the bitmap=width, 2nd=height, then data'
 db ' [stack: pointer to bitmap, x, y :tos -- ] '
 dw \\$-bitmap.doc
 bitmap:
 dw 0 ; link to previous dict word or null
 db 6, 'bitmap' ; forth counted name
 bitmap.x:
 mov ax, 0xB800
 mov es, ax ; es -> start of video memory (for stosw and video print)
 mov di, 20 ; start printing address

 pop dx ; balance return ip
 pop bx ; x coordinate
 pop ax ; y coordinate
 pop si ; pointer to 1st byte of glyph
 push dx ; restore fn pointer
 mov dx, 160 ; 
 mul dx ; ax * 160 to get y position (80 cols, 2 bytes/char)
 shl bx, 1 ; bx := bx * 2
 add ax, bx ; but! need to bx * 2 (since 2 bytes/ char)
 mov di, ax ; screen printing position 

 xor bx, bx ; bx := 0 bh := 0 so no background colours
 xor ax, ax ; ax := 0
 xor cx, cx
 mov [bitmap.glyphpointer], si ; save si in a pointer var 
 mov cl, [si+1] ; height bytes in the glyph (2nd byte of glyph) 
 cld ; make lodsb go forwards ("clear direction flag")
 add si, 2 ; skip width+height bytes
 .nextrow:
 lodsb ; get next byte (glyph row) to al
 mov dl, al ; save glyph row to dl
 push cx ; save current row number
 push di ; save screen print position
 xor cx, cx
 mov bx, [bitmap.glyphpointer]
 mov cl, byte [bx] ; width bytes in glyph (1st byte)
 .nextbit:
 rol dl, 1 ; print mirror image with ror bl, 1 
 mov bl, dl
 and bl, 0b00000001 ; check if bit is set
 mov al, [bitmap.render+bx] ; get foreground or background character

 ;mov ah, '/' ; white on green colour
 mov ah, 0b00001110 ; yellow on black
 stosw ; print to screen es:di++:=ax, al==char, ah==colour
 loop .nextbit ; next pixel on same row 
 pop di ; restore screen print pos
 add di, 160 ; down 1 row (8 cols * 2 bytes/char)
 pop cx ; restore current row number in glyph
 loop .nextrow ; render the next row of bits in the glyph or sprite
 .exit:
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 push ship ; pointer to glyph
 push 10 ; y pos
 push 10 ; x pos
 call bitmap.x
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 

 * display a bit map in video mode 13H 
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 alpha equ 224 ; Greek letter alpha 
 beta equ 225 ; Greek letter beta
 gamma equ 226 ; Greek letter gamma

 ship:
 db 8, 8 ; width x height bytes
 db 0b00100100 ; glyph data
 db 0b10000000
 db 0b10100111
 db 0b11001100
 db 0b01000010
 db 0b01000010
 db 0b00000000
 db 0b10101010

 ; (stack: segment address, glyph address - )
 pixmap.glyphpointer dw 0
 pixmap.render db 0, 0b00001111 ; white on black, overwritten by param
 pixmap.doc:
 db 'Displays a bitmap in graphics mode 13h at a given point '
 db 'on the screen.'
 db 'The 1st byte of the bitmap=width, 2nd=height, then data'
 db ' [stack: pointer to bitmap, x, y :tos -- ] '
 dw \\$-pixmap.doc
 pixmap:
 dw 0 ; link to previous dict word or null
 db 6, 'bitmap' ; forth counted name
 pixmap.x:
 mov ax, 0xA000 ; address of 1st pixel in display memory (mode 13H)
 mov es, ax ; es -> start of video memory (for stosw and video print)

 pop dx ; balance return ip
 pop bx ; x coordinate
 pop ax ; y coordinate
 pop cx ; colour of sprite foreground (in low byte)
 pop si ; pointer to 1st byte of glyph
 push dx ; restore fn pointer
 mov [pixmap.render+1], cl ; save colour 
 mov dx, 320 ; 
 mul dx ; ax * 320 to get y position (320 cols * 1 byte)
 add ax, bx ; add x offset to y offset 
 mov di, ax ; screen printing position 

 xor bx, bx ; bx := 0 bh := 0 
 xor ax, ax ; ax := 0
 xor cx, cx
 mov [pixmap.glyphpointer], si ; save si in a pointer var 
 mov cl, [si+1] ; height bytes in the glyph (2nd byte of glyph) 
 cld ; make lodsb go forwards ("clear direction flag")
 add si, 2 ; skip width+height bytes
 .nextrow:
 lodsb ; get next byte (glyph row) to al
 mov dl, al ; save glyph row to dl
 push cx ; save current row number
 push di ; save screen print position
 xor cx, cx
 mov bx, [pixmap.glyphpointer]
 mov cl, byte [bx] ; width bytes in glyph (1st byte)
 .nextbit:
 rol dl, 1 ; print mirror image with ror bl, 1 
 mov bl, dl
 and bl, 0b00000001 ; check if bit is set
 mov al, [pixmap.render+bx] ; get foreground or background character

 stosb ; pixel to screen es:di++:=al, al==colour
 loop .nextbit ; next pixel on same row 
 pop di ; restore screen print pos
 add di, 320 ; down 1 row (8 cols * 2 bytes/char)
 pop cx ; restore current row number in glyph
 loop .nextrow ; render the next row of bits in the glyph or sprite
 .exit:
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push ship ; pointer to glyph
 push 14 ; colour of sprite (yellow)
 push 60 ; y pos
 push 80 ; x pos
 call pixmap.x
 
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
WITH INTERRUPTS ....

 video mode 13h has the highest number of colours (for vga)
 int 10h functions for drawing pixels are considered slow.
 Pixels can only be read and written in graphics modes

 * draw 1 white pixel at (10,10) 
 --------------------
 start:
 mov ax, 07C0h
 mov ds, ax
 .setmode:
 mov ah, 0 ; set graphics display mode function.
 mov al, 13h ; mode 13h = 320x200 at 8 bits/pixel.
 int 10h ; set it!
 .draw:
 mov cx, 10 ; x-coordinate 
 mov dx, 10 ; y-coordinate 
 mov al, 15 ; white
 mov ah, 0ch ; put pixel
 int 10h ; draw pixel
 jmp \\$
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * draw a diagonal line with x86 bios interrupts
 --------------------
 [org 0]
 jmp 07C0h:start

 hextable db "0123456789ABCDEF" ; digit translation table
 glyph equ 'a' 
 line:
 dw 0
 db 4, 'line'
 line.x:
 mov cx, 0x0040 ; draw diagonal line 64 pixels long

 .draw:
 mov dx, cx ; y-coordinate in dx
 mov al, 15 ; white
 mov ah, 0ch ; put pixel
 int 10h ; draw pixel
 loop .draw
 ret

 ; sets the video mode from stack
 ; 
 vmode:
 dw line
 db 5, 'vmode'
 vmode.x:
 pop dx ; juggle return pointer
 pop ax ; video mode into al
 xor ah, ah ; set ah := 0 (int 10h set display mode)
 push dx
 int 10h ; set it!
 ret

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 push 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 call vmode.x
 call line.x
 ; push 0x0003 ; default text mode 

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * attempt to write text in a graphics mode, doesnt work!!
 --------------------
 start:
 mov ax, 07C0h
 mov ds, ax
 .setmode:
 mov ah, 0 ; set graphics display mode function.
 mov al, 13h ; mode 13h = 320x200 at 8 bits/pixel.
 int 10h ; set it!
 .text:
 mov ah, 0eh
 mov al, 'Q'
 int 10h
 .draw:
 mov cx, 10 ; column
 mov dx, 10 ; row
 mov al, 15 ; white
 mov ah, 0ch ; put pixel
 int 10h ; draw pixel
 jmp \\$
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


SPRITES ....

 A sprite is just a visual glyph that may move by some animation
 or not. Here we will start with simple monochrome bitmaps such
 as in 'space invaders'

 This "pix" word should take the pixel position from the stack
 Also a colour would be nice. Draw a circle etc.


 * write a pixel to video memory in mode 13h
 ---------------
 [org 0]
 jmp 07C0h:start

 pix:
 dw 0
 db 3, 'pix'
 pix.x:
 ; DisplayMode 13h
 ; screen size.x = 0x0140, (320x200 pixels)
 ; screen size.y = 0x00C8 (200 pixels high)
 ; number of colors = 0x0100
 ; address of pixel 0 = A000

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax
 mov cx, 0x0140 ; screenSize.x = 320 pixels
 mov ax, 30 ; posY
 mul cx ; ax *= cx
 add ax, 10 ; ax += posX 
 mov di, ax ; di = offset of pixel 
 mov dx, 0x000A ; dl = color of pixel, 256 colours
 mov [es:di], dl ; write pixel to memory
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 call pix.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


 Perhaps this code should check for the boundaries of 
 video memory, otherwise it is going to overwrite stuff 
 that it shouldnt.

 * display a diagonal line at a point given on stack (x y length)
 ---------------
 [org 0]
 jmp 07C0h:start

 diag.doc db 'Display a 45 angle line, 1 pixel wide in video mode 13h'
 dw \\$-diag.doc
 diag:
 dw 0
 db 4, 'diag'
 diag.x:
 ; DisplayMode 13h (320w x 200h pixels)
 ; number of colors = 0x0100
 ; address of pixel 0 = A000

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; line length
 pop ax ; y coordinate
 pop bx ; x coordinate
 push dx ; restore return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov dl, 0x0A ; dl = color of pixel, 256 colours
 mov di, ax ; di = offset of pixel 
 .line:
 mov [es:di], dl ; write pixel to memory
 add di, 321 ; 1 row (320) + 1 horizontal (x) pixel
 ; mov [es:di], cl ; creates a multicoloured line 
 ; add ax, cx ; this actually creates a curve effect
 loop .line
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 0 ; x coordinate
 push 40 ; y coordinate
 push 150 ; line length
 call diag.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * a demonstration of a few line drawing techniques 
 ---------------
 [org 0]
 jmp 07C0h:start

 demo.doc 
 db 'display different line types in video mode 13hex'
 dw \\$-demo.doc
 demo:
 dw 0
 db 4, 'demo'
 demo.x:
 ; DisplayMode 13h (320w x 200h pixels) number of colors:256 

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop ax ; y coordinate
 pop bx ; x coordinate
 push dx ; restore return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di = offset of pixel 
 mov dl, 0x0A ; al = color of pixel, 256 colours
 mov cx, 30 ; line length
 push di ; save start pixel co-ordinates

 .dotted:
 mov [es:di], dl ; write colour pixel to memory
 add di, 4 ; creates a dotted line
 loop .dotted

 pop di ; go back to start position
 add di, 960 ; 2 rows down
 push di ; save start again
 mov cx, 100 

 .rainbow:
 mov [es:di], cl ; multi colour pixel to memory
 add di, 1 
 loop .rainbow

 pop di ; go back to start position
 add di, 960 ; 3 rows down
 push di ; save start again
 mov cx, 20 

 .curve:
 mov [es:di], dl ; 
 mov ax, di
 add ax, cx 
 mov di, ax
 loop .curve

 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 10 ; x coordinate
 push 10 ; y coordinate
 call demo.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * display a vertical line at a point given on stack (x y length)
 ---------------
 [org 0]
 jmp 07C0h:start

 vline.doc 
 db 'Display a vertical line, 1 pixel wide in video mode 13h'
 dw \\$-vline.doc
 vline:
 dw 0
 db 5, 'vline'
 vline.x:
 ; DisplayMode 13h (320w x 200h pixels) number of colors:256 

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; line length
 pop ax ; y coordinate
 pop bx ; x coordinate
 push dx ; restore return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di = offset of pixel 
 mov dl, 0x0A ; al = color of pixel, 256 colours
 .line:
 mov [es:di], dl ; write colour pixel to memory
 add di, 320 ; down one row 
 loop .line

 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 10 ; x coordinate
 push 10 ; y coordinate
 push 50 ; line length
 call vline.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * display a horizontal line at a point given on stack (x y length)
 ---------------
 [org 0]
 jmp 07C0h:start

 hline.doc:
 db 'Display a horizontal line, 1 pixel wide in video mode 13h'
 dw \\$-hline.doc
 hline:
 dw 0
 db 5, 'hline'
 hline.x:
 ; DisplayMode 13h (320w x 200h pixels) number of colors:256 

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; line length
 pop ax ; y coordinate
 pop bx ; x coordinate
 push dx ; restore return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di = offset of pixel 
 mov al, 0x0A ; al = color of pixel, 256 colours
 rep stosb ; store colour in video memory, loop while cx > 0

 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 10 ; x coordinate
 push 70 ; y coordinate
 push 150 ; line length
 call hline.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


 The code below needs to be refined. When the end of the whirl
 vector is reached (4) then it should be reset to 0 so that we 
 can draw spirals of any length.

 * display a boxy spiral 
 ---------------
 [org 0]
 jmp 07C0h:start

 ; the offset vectors for drawing the sides of the box
 whirl.vector dw 1, 320, -1, -320, 1, 320, -1, -320
 whirl.doc:
 db 'Display a spiral'
 dw \\$-whirl.doc
 whirl:
 dw 0
 db 5, 'whirl'
 whirl.x:
 ; DisplayMode 13h (320w x 200h pixels) number of colors:256 

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; spiral start width 
 pop ax ; y coordinate (top left corner)
 pop bx ; x coordinate (top left corner)
 push dx ; restore return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di = offset of pixel 
 mov al, 0x0A ; al = color of pixel, 256 colours

 mov dx, cx ; save box width/height
 xor bx, bx ; bx := 0, bx is a loop counter and table offset

 .nextside:
 ;cmp word [whirl.vector+bx], -1
 ;jne .same
 sub dx, 2
 .same:
 mov cx, dx ; restore side length counter
 .nextpixel:
 mov [es:di], al ; write colour pixel to memory
 add di, [whirl.vector+bx] 
 ;add di, ax 
 loop .nextpixel
 add bx, 2 ; point to next pixel vector
 cmp bx, 16 
 jne .nextside

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 40 ; x coordinate of top left corner
 push 10 ; y coordinate of top left corner
 push 30 ; width and height of box 
 call whirl.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 In qemu the box created below is not actually square funnily
 enough


 The code below uses a loop and a pixel offset vector to 
 simplify (or make more concise) the code for drawing the box.
 The good thing about this is that it is applicable for drawing
 any series of lines.

 * a more concise square box at a point given on stack (x y width)
 ---------------
 [org 0]
 jmp 07C0h:start

 ; the offset vectors for drawing the sides of the box
 box.vector dw 1, 320, -1, -320
 box.doc:
 db 'Display a square box in video mode 13'
 dw \\$-box.doc
 box:
 dw 0
 db 3, 'box'
 box.x:
 ; DisplayMode 13h (320w x 200h pixels) number of colors:256 

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; box width and height 
 pop ax ; y coordinate (top left corner)
 pop bx ; x coordinate (top left corner)
 push dx ; restore return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di = offset of pixel 
 mov al, 0x0A ; al = color of pixel, 256 colours

 mov dx, cx ; save box width/height
 xor bx, bx ; bx := 0, bx is a loop counter and table offset

 .nextside:
 mov cx, dx ; restore side length counter
 .nextpixel:
 mov [es:di], al ; write colour pixel to memory
 add di, [box.vector+bx] ; down one row 
 loop .nextpixel
 add bx, 2 ; point to next pixel vector
 cmp bx, 8 
 jne .nextside

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 40 ; x coordinate of top left corner
 push 10 ; y coordinate of top left corner
 push 50 ; width and height of box 
 call box.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 The code below is very pedestrian. see code above for a 
 better and more concise way to do this.

 * display a square box at a point given on stack (x y width)
 ---------------
 [org 0]
 jmp 07C0h:start

 box.doc:
 db 'Display a square box in video mode 13'
 dw \\$-box.doc
 box:
 dw 0
 db 3, 'box'
 box.x:
 ; DisplayMode 13h (320w x 200h pixels) number of colors:256 

 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; box width and height 
 pop ax ; y coordinate (top left corner)
 pop bx ; x coordinate (top left corner)
 push dx ; restore return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di = offset of pixel 
 mov al, 0x0A ; al = color of pixel, 256 colours

 mov dx, cx ; save box width/height

 .top:
 cld ; go forwards (from left to right on screen)
 rep stosb ; write the colour pixel in al to video
 mov cx, dx ; restore box dimension

 .right:
 mov [es:di], al ; write colour pixel to memory
 add di, 320 ; down one row 
 loop .right
 mov cx, dx ; restore box dimension

 .bottom:
 std
 rep stosb ; [es:di]++ <- AL register (al is the colour)
 mov cx, dx ; restore box dimension

 .left:
 mov [es:di], al ; write colour pixel to memory
 sub di, 320 ; down one row 
 loop .left

 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 40 ; x coordinate of top left corner
 push 10 ; y coordinate of top left corner
 push 50 ; width and height of box 
 call box.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * display a hexagon starting at point (x y) with line length l
 ---------------
 [org 0]
 jmp 07C0h:start

 ; the offset vectors for drawing the sides of the hexagon
 hexagon.vector dw 1, 321, 319, -1, -321, -319
 hexagon.doc:
 db 'Display a hexagon in video mode 13'
 dw \\$-hexagon.doc
 hexagon:
 dw 0
 db 7, 'hexagon'
 hexagon.x:
 mov ax, 0xA000 ; address of 1st pixel in display memory (mode 13H)
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; hexagon side length 
 pop ax ; y coordinate of starting point 
 pop bx ; y coordinate 
 push dx ; restore function return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di := offset of pixel in video memory
 mov al, 0x0A ; al = color of pixel, 256 colours

 mov dx, cx ; save hexagon side length
 xor bx, bx ; bx := 0, bx is a loop counter and table offset

 .nextside:
 mov cx, dx ; restore side length counter
 .nextpixel:
 mov [es:di], al ; write colour pixel to memory
 add di, [hexagon.vector+bx] ; down one row 
 loop .nextpixel
 add bx, 2 ; point to next pixel vector
 cmp bx, 12 
 jne .nextside

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 40 ; x coordinate of top left corner
 push 60 ; y coordinate of top left corner
 push 5 ; width and height of box 
 call hexagon.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * display a octagon starting at point (x y) with line length l
 ---------------
 [org 0]
 jmp 07C0h:start

 ; the offset vectors for drawing the sides of the hexagon
 octagon.vector dw 1, 321, 320, 319, -1, -321, -320, -319
 octagon.doc:
 db 'Display an octagon in video mode 13'
 dw \\$-octagon.doc
 octagon:
 dw 0 ; link to previous fn 
 db 7, 'octagon'
 octagon.x:
 mov ax, 0xA000 ; address of 1st pixel in display memory (mode 13H)
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop cx ; octagon side length 
 pop ax ; y coordinate of starting point 
 pop bx ; y coordinate 
 push dx ; restore function return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di := offset of pixel in video memory
 mov al, 0x0A ; al = color of pixel, 256 colours

 mov dx, cx ; save octagon side length
 xor bx, bx ; bx := 0, bx is a loop counter and table offset

 .nextside:
 mov cx, dx ; restore side length counter
 .nextpixel:
 mov [es:di], al ; write colour pixel to memory
 add di, [octagon.vector+bx] ; down one row 
 loop .nextpixel
 add bx, 2 ; point to next pixel vector
 cmp bx, 16
 jne .nextside

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 push 40 ; x coordinate of top left corner
 push 60 ; y coordinate of top left corner
 push 3 ; width and height of box 
 call octagon.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * display a line to any of the compass points starting at (x y) 
 ---------------
 [org 0]
 jmp 07C0h:start

 ; offsets by dir: no, ne, etc
 compass.vector dw -320, -319, 1, 321, 320, 319, -1, -321
 compass.doc:
 db 'Displays a line from (x y) length |l| to north, north east, east'
 db ' south-east, west, etc. 1=north, 2=north east, 3=north west ...' 
 db ' stack parameters (TOS: dir, length, y, x)'
 dw \\$-compass.doc
 compass:
 dw 0
 db 7, 'compass'
 compass.x:
 mov ax, 0xA000 ; address of 1st pixel in display memory (mode 13H)
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop si ; compass direction (1-8) 1=n, 2=ne, 3=e, 4=se etc
 pop cx ; line length 
 pop ax ; y coordinate of starting point 
 pop bx ; y coordinate 
 push dx ; restore function return pointer
 mov dx, 320 ; video screensize(x) = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di := offset of pixel in video memory
 mov al, 0x0A ; al = color of pixel, 256 colours
 mov bx, si ; get compass direction into dx

 dec bx
 cmp bx, 8 ; only 8 directions (1-8)
 jg .exit ; not quite working
 add bx, bx ; bx := bx*2, since offset is word pointer

 .nextpixel:
 mov [es:di], al ; write colour pixel to video memory
 add di, [compass.vector + bx]
 loop .nextpixel

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 mov cx, 8 ; try all eight directions

 .nextdir:

 push cx
 push 100 ; x coordinate of top left corner
 push 100 ; y coordinate of top left corner
 push 10 ; line length 
 push cx ; next direction
 call compass.x
 pop cx

 loop .nextdir

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
 * display an arrow to any of the compass points starting at (x y) 
 ---------------
 [org 0]
 jmp 07C0h:start

 ; offsets by dir: no, ne, etc
 arrow.offset dw -320, -319, 1, 321, 320, 319, -1, -321
 arrow.doc:
 db 'Displays an arrow from (x y) length |l| to north, north east, east'
 db ' south-east, west, etc. 1=north, 2=north east, 3=north west ...' 
 db ' stack parameters (TOS: dir, length, y, x)'
 dw \\$-arrow.doc
 arrow:
 dw 0 ; link to previous
 db 5, 'arrow'
 arrow.x:
 mov ax, 0xA000 ; address of 1st pixel in display memory (mode 13H)
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop si ; compass direction (1-8) 1=n, 2=ne, 3=e, 4=se etc
 pop cx ; line length 
 pop ax ; y coordinate of starting point 
 pop bx ; y coordinate 
 push dx ; restore function return pointer
 mov dx, 320 ; video screensize(x) = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di := offset of pixel in video memory
 mov al, 0x0A ; al = color of pixel, 256 colours
 mov bx, si ; get compass direction into dx
 
 dec bx
 cmp bx, 7 ; only 8 directions (1-8)
 jg .exit
 add bx, bx ; bx := bx*2, since offset is word pointer
 mov bx, [arrow.offset + bx]

 .nextpixel:
 mov [es:di], al ; write colour pixel to memory
 add di, bx
 loop .nextpixel

 ; try to add the arrow head
 sub di, 322 
 mov [es:di], al ; write colour pixel to memory
 sub di, 321 
 mov [es:di], al ; write colour pixel to memory

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 mov cx, 8 ; try all eight directions

 .nextdir:

 push cx
 push 100 ; x coordinate of top left corner
 push 100 ; y coordinate of top left corner
 push 10 ; line length 
 push cx ; next direction
 call arrow.x
 pop cx

 loop .nextdir

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * make lines of colour in video mode 13h
 ---------------
 [org 0]
 jmp 07C0h:start

 ray:
 dw 0
 db 3, 'ray'
 ray.x:
 ; DisplayMode 13h
 ; screen size.x = 0x0140, (320x200 pixels)
 ; screen size.y = 0x00C8 (200 pixels high)
 ; number of colors = 0x0100
 ; address of pixel 0 = A000

 mov cx, 0xFFFF ; draw lots of pixels
 mov ax, 0xA000 ; address of 1st pixel in display memory
 mov es, ax
 .pixel:
 mov ax, cx ; next pixel 
 mov di, ax ; di = offset of pixel 
 mov dl, cl ; dl = color of pixel, 256 colours
 mov [es:di], dl ; write pixel to memory
 loop .pixel
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode
 call ray.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,


FILLED SHAPES ....

 We can draw shapes started at a point and continueing along
 a line, but for filled shapes another technique is required.

ANIMATION

 The basic technique for animation is. Display something, wait,
 erase it, display something else, and so on

 cx=0 and dx=a2c2 gives about 24 frames per second. Good.

 * wait for about 1 second (cx:dx == 1000000 seconds)
 ---------
 mov cx, 0fh ; cx:dx microseconds to wait
 mov dx, 4240h
 mov ah, 86h
 int 15h
 ,,,

 cx=0x000F approx 1 second
 cx=0x0008 approx 1/2 second
 cx=0x0004 approx 1/4 seconds
 cx=0x0002 approx 1/8 seconds
 cx=0x0001 approx 1/16 seconds

 * a delay for about 24 frames per second
 ---------
 xor cx, cx 
 mov dx, 0xa2c2 
 mov ah, 86h
 int 15h
 ,,,

 * display an animated octagon 
 ---------------
 [org 0]
 jmp 07C0h:start

 ; the offset vectors for drawing the sides of the hexagon
 octagon.vector dw 1, 321, 320, 319, -1, -321, -320, -319
 octagon.doc:
 db 'Display an octagon in video mode 13'
 dw \\$-octagon.doc
 octagon:
 dw 0 ; link to previous fn 
 db 7, 'octagon'
 octagon.x:
 mov ax, 0xA000 ; address of 1st pixel in display memory (mode 13H)
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop si ; colour
 pop cx ; octagon side length 
 pop ax ; y coordinate of starting point 
 pop bx ; y coordinate 
 push dx ; restore function return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di := offset of pixel in video memory

 mov ax, si ; colour in which to display 
 ;mov al, 0x0A ; al = color of pixel, 256 colours

 mov dx, cx ; save octagon side length
 xor bx, bx ; bx := 0, bx is a loop counter and table offset

 .nextside:
 mov cx, dx ; restore side length counter
 .nextpixel:
 mov [es:di], al ; write colour pixel to memory
 add di, [octagon.vector+bx] ; down one row 
 loop .nextpixel
 add bx, 2 ; point to next pixel vector
 cmp bx, 16
 jne .nextside

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode

 mov cx, 200
 .nextframe: 

 mov ax, 10
 add ax, cx
 mov bp, ax ; save x pos in bp
 push cx

 push ax ; x coordinate of top left corner
 push 60 ; y coordinate of top left corner
 push 10 ; width and height of box 
 push 0x000B ; hexagon colour 
 ;push cx ; changing colour hexagon (256 colours)
 
 call octagon.x
 xor cx, cx ; cx := 0
 mov dx, 0xa2c2 ; which seems good for animation ie DX=0xA2C2
 mov ah, 86h ; wait for timer function
 int 15h ; bios interrupt 

 mov ax, bp
 push ax ; x coordinate of top left corner
 push 60 ; y coordinate of top left corner
 push 10 ; width and height of box 
 push 0x00 ; hexagon colour (black to erase)
 
 call octagon.x

 pop cx
 loop .nextframe

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
 We can use the colour black to erase the sprite in very
 simple animations.

 * display a static sprite
 ---------------
 [org 0]
 jmp 07C0h:start

 ; data in format: width, height, pixel colour data row by row 
 sprite.data db 3, 3, 10, 0, 10, 0, 10, 0, 11, 0, 11 
 sprite.doc:
 db 'Displays a sprite on the screen at given coordinates'
 db ' takes on stack: TOS: colour pointer-to-sprite y x'
 dw \\$-sprite.doc
 sprite:
 dw 0 ; link to previous fn 
 db 6, 'sprite'
 sprite.x:
 mov ax, 0xA000 ; address of 1st pixel in display memory (mode 13H)
 mov es, ax ; extended segment set to video memory
 pop dx ; juggle return address
 pop bp ; colour
 pop si ; pointer to sprite data 
 pop ax ; y coordinate of starting point 
 pop bx ; x coordinate 
 push dx ; restore function return pointer
 mov dx, 0x0140 ; screenSize.x = 320 pixels
 mul dx ; ax *= dx, y offset of pixel
 add ax, bx ; add x offset of pixel
 mov di, ax ; di := offset of pixel in video memory

 mov ax, bp ; colour in which to display 
 ;mov al, 0x0A ; al = color of pixel, 256 colours

 mov bp, si ; save data pointer
 add si, 2 ; si now points to data
 
 ;mov cx, [bp+1]
 mov cx, 9
 
 .nextrow
 ;push cx
 .nextpixel:
 ;mov [es:di], byte [ds:si] ; write colour pixel to memory
 movsb ; write pixel at ds:si to es:di (video memory
 loop .nextpixel
 ;loop .nextrow

 .exit:
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 mov ax, 0x0013 ; mode 13h = 320x200 at 8 bits/pixel.
 int 0x10 ; bios int 10h, ah=0, al=video mode

 push 60 ; x coord 
 push 10 ; y coord 
 push sprite.data ; pointer to pixel data 
 push 0x000B ; sprite colour, not used 
 
 call sprite.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

MEMORY MAPPED GRAPHICS ....
 
 much faster than the bios interrupt approach. Writes
 values directly to video memory, provided that these are 
 in standard places.

 https://thiscouldbebetter.wordpress.com/2011/03/17/vga-mode-13h-in-assembly-with-direct-memory-writes/
 A good complete example of writing graphics into memory

 The code below could be greatly simplified by removing the 
 generality of the code and making it specific to mode 13h. 
 Also the code demonstrates another way of writing a bootloader
 but doesnt seem to set up the stack properly.

 * display all colours in video mode 13 using memory accessed graphics
 -------------------------
 
 use16 ; 16-bit mode

org 0x7C00 ; address of the boot sector

 BootStageOne:
 ;
 mov ah,0x00 ; reset disk
 mov dl,0 ; drive number
 int 0x13 ; call BIOS interrupt routine
 ;
 ; load sectors from disk using BIOS interrupt 0x13
 mov ah,0x02 ; function number: read sectors into memory
 mov al,0x10 ; number of sectors to read (more than we need)
 mov dl,0 ; drive number
 mov ch,0 ; cylinder number
 mov dh,0 ; head number
 mov cl,2 ; starting sector number
 mov bx,Main ; memory location to load to 
 int 0x13 ; call BIOS interrupt routine
 ;
 jmp Main ; now that it's been loaded
 ;

PadOutSectorOneWithZeroes:
 ; pad out all but the last two bytes of the sector with zeroes
 times ((0x200 - 2) - (\\$ - \\$\\$)) db 0x00

BootSectorSignature:
 dw 0xAA55 ; these must be the last two bytes in the boot sector

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

 Main:
 ; set mode to VGA 13h and draw the default palette
 ;
 push DisplayModeInstance13h
 call DisplayModeSet
 ;
 mov ax,0 ; pixel x
 mov bx,0 ; pixel y
 mov cx,[NumberOfColors]

DrawEveryColorInPalette:
 ;
 push ax ; pixel x
 push bx ; pixel y
 mov dx,[NumberOfColors]
 sub dx,cx
 push dx ; pixel color index
 call DisplayPixelDrawXY 
 ;
 inc ax
 cmp ax,[ColorsPerRow]
 jb NewRow
 mov ax,0
 inc bx
 NewRow:
 loop DrawEveryColorInPalette
 ;
 ret
 ;
 NumberOfColors: dw 0x0100
 ColorsPerRow: dw 0x0010

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

DisplayMode:
; +0 = number
; +2 = screen size in pixels
; +4 = number of colors
; +6 = address of pixel 0

DisplayModeCurrent:
 dw 0x0000

DisplayModeInstance13h:
dw 0x0013, DisplayModeInstance13hSize, 0x0100, 0xA000
DisplayModeInstance13hSize: dw 0x0140, 0x00C8 ; 320x200

DisplayModeSet:
 ; (displayModeToSet)
 ;
 push bp
 mov bp,sp
 push ax
 push si
 ;
 mov si,[bp+4] ; displayModeToSet
 ;
 mov ax,[si+0] ; displayModeToSet.number
 int 0x10
 ;
 mov [DisplayModeCurrent],si
 ;
 pop si
 pop ax
 pop bp
 ret 2

DisplayPixelDrawXY:
 ; (posX, posY, color)
 ;
 push bp
 mov bp,sp
 ;
 push ax
 push cx
 push dx
 push si
 push di
 push es
 ;
 mov si,[DisplayModeCurrent]
 ;
 mov es,[si+6] ; address of display memory
 ;
 mov di,[si+2] ; bx = displayModeCurrent.screenSize
 mov cx,[di+0] ; cx = screenSize.x
 mov ax,[bp+6] ; ax = posY
 mul cx ; ax *= cx
 add ax,[bp+8] ; ax += posX
 mov di,ax ; di = offset of pixel 
 ;
 mov dx,[bp+4] ; dl = color of pixel
 ;
 mov [es:di],dl ; write pixel to memory
 ;
 pop es
 pop di
 pop si
 pop dx
 pop cx
 pop ax
 ;
 pop bp
 ret 6

 PadOutSectorsAllWithZeroes:
 times (0x2000 - (\\$ - \\$\\$)) db 0x00

 ,,,,

CURSOR SHAPE

 * set text-mode cursor shape.
 >> int 10h, ah=01h 

 input:
 CH = cursor start line (bits 0-4) and options (bits 5-7).
 CL = bottom cursor line (bits 0-4).

 when bit 5 of CH is set to 0, the cursor is visible. when bit 5 is 1, the
 cursor is not visible.

 * hide blinking text cursor: 
 ----------------------------
 mov ch, 32
 mov ah, 1
 int 10h
 ,,,

 * show standard blinking text cursor: -------------------------------------
 mov ch, 6
 mov cl, 7
 mov ah, 1
 int 10h
 ,,,

 * show box-shaped blinking text cursor: 
 ---------------------------------------
 mov ch, 0
 mov cl, 7
 mov ah, 1
 int 10h
 jmp \\$ ; keep looping! 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * show a box cursor while reading keys 
 ---------------------------------------------------------
 start:
 mov ch, 0 ; set up the cursor
 mov cl, 7
 mov ah, 1
 int 10h ; display the box cursor
 .repeat: 
 mov ah, 0
 int 16h ; read a key
 mov ah, 0eH
 int 10H ; display the last key pressed
 jmp .repeat 

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
 note: some bioses required CL to be >=7,
 otherwise wrong cursor shapes are displayed. 

COLOURS 

 16 colour mode uses a 4 bit encoding
 * * * *
 : I R G B
 where I=Intensity (eg dark or light green) and RGB means
 red, green, blue. The Intensity bit is also the 'blinking'
 bit in some modes (to make text blink)

 So we can change the intensity of a colour (from light to
 dark or vice-versa) by toggling the 1st bit of the nibble.

 This is not working when included in a bootloaded program,
 A very strange problem... when in a big program the colour print
 did not work. It seems to have to do with how far away code
 is from mov bx, colourcode.x, call bx Once I repositioned 
 the word in the dictionary the colourprints started to work again.
 But normal ah=0x0E printing worked anyway. Very odd...
 ???...

 * print colour values in hex and binary 
 -------------
 [org 0]
 jmp 07C0h:start

 hextable db "0123456789ABCDEF" ; digit translation table
 glyph equ 'a' 
 colourcode:
 dw 0
 db 10, 'colourcode'
 colourcode.x:
 mov cx, 0x000F ; loop through all asci chars

 .nextchar:
 mov ah, 0x0E ; x86 int 0x10 type char function
 mov al, cl ; high nibble of ascii code to print 
 mov bx, hextable ; pointer to digit translation table

 ; or just shr, since that fills with zeros???
 rol al, 4 ; print first digit
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 int 10h ; invoke bios 

 mov al, cl ; lower nibble of ascii code 
 and al, 0x0F ; print high byte first
 xlatb ; replace al with hex digit al := [bx+al]
 int 10h ; invoke bios 

 mov ah, 0x0E ; separate with a space 
 mov al, ' ' 
 int 10h

 ; white on background colour
 mov ah, 09h ; x86 bios colour print function
 mov al, glyph ; the char to print 
 mov bl, cl ; 
 shl bl, 4 ; make it a back colour (high nibble) 
 or bl, 0b00001111 ; foreground white
 push cx
 mov cx, 24 ; print chars, cursor stays at beginning
 int 10h ; 
 pop cx ; restore counter

 mov ah, 09h ; x86 bios colour print function
 mov al, glyph ; spades char 
 mov bl, cl ; 
 shl bl, 4 ; make it a back colour (high nibble) 
 or bl, cl ; foreground and back
 and bl, 0b01111111 ; make background dull 
 push cx
 mov cx, 20 ; print chars, cursor stays at beginning
 int 10h ; 
 pop cx ; restore counter

 mov ah, 09h ; x86 bios colour print function
 mov al, glyph ; spades char 
 mov bl, cl ; 
 shl bl, 4 ; make it a back colour (high nibble) 
 or bl, cl ; foreground and back
 and bl, 0b11110111 ; make foreground dull 
 push cx
 mov cx, 16 ; print chars, cursor stays at beginning
 int 10h ; 
 pop cx ; restore counter

 mov ah, 09h ; x86 bios colour print function
 mov al, glyph ; spades char 
 mov bl, cl ; 
 shl bl, 4 ; make it a back colour (high nibble) 
 or bl, cl ; foreground and back
 push cx
 mov cx, 12 ; print chars, cursor stays at beginning
 int 10h ; 
 pop cx ; restore counter

 ; color IRGBIRGB
 ; bl color bits: intensity,red,green,blue,intensity,red,green,blue
 ; 16 foreground colours are printed first, and then 8 background
 ; on top of them, covering the 1st 8, but not the second 8

 mov ah, 09h ; colour print function
 mov al, glyph ; the character to print
 mov bl, cl ; cl is forground colour 0-15 
 push cx
 mov cx, 8 ; print chars, cursor stays at beginning
 int 10h ; 
 pop cx ; restore counter

 mov ah, 09h ; x86 bios colour print function
 mov al, glyph ; spades char 
 mov bl, cl ; 
 shl bl, 4 ; make it a back colour (high nibble) 
 push cx
 mov cx, 4 ; print chars, cursor stays at beginning
 int 10h ; 
 pop cx ; restore counter

 mov ah, 0x0E ; print a new line after each colour
 mov al, 13
 int 10h
 mov al, 10
 int 10h

 .end: 
 cmp cx, 0
 je .exit
 dec cx
 jmp .nextchar
 ; loop .nextchar ; loop is out of range, short jump
 .exit:
 ret

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax ; stosw uses es segment reg
 call colourcode.x
 jmp \\$

 times 510-(\\$-\\$\\$) db 0 ; Pad boot sector with 0s
 dw 0xAA55 ; MBR boot signature

 ,,,,

 * turn on the blink/intensity bit for a coloured character
 ------------
 mov ah, 9
 mov al, 'a'
 mov bh, 0
 mov bl, colour
 or bl, 10000000b
 mov cx, 1
 int 10h
 ,,,

COLOUR AND TEXT ....

 If we are in a graphics mode eg 13h then we can use bl to determine
 the colour of the text and we can write text to the screen!!!
 For some reason I assumed that it was not possible to write text 
 to the screen in graphic video modes!!!!

 The bios in text mode is able to display text in 16 different
 colours.

 Write character and attribute at cursor position
 int 10h, ah=09h, 
 al=character, bh=page number, bl=color, 
 cx=number of times to print character 

 The character colour attribute is 8 bit value in the BL register
 the low 4 bits set forground color,
 the high 4 bits set background color. 

 * print intense red (fg) on intense blue (background)
 --------------
 IRGBIRGB 
 mov BL, 0b10101100
 ,,,

 The cursor position is not changed after writing the characters

 * print "====" in green at the current cursor position
 -----------------------------------------------------
 mov ah, 09h ; the 'function' number
 mov al, '=' ; the character to print
 ; color IRGBIRGB
 ; bl color bits: intensity,red,green,blue,intensity,red,green,blue

 mov bl, 0b00000010 ; green on black at first page (bh=0)
 mov cx, 4 ; do it 4 times (cursor stays where it was)
 int 10h ; do it with a bios interrupt
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print 2 characters advancing cursor 
 -----------------------------------------------------
 mov cx, 1 ; number of characters to print 
 mov ah, 09h ; bios function colour print 
 mov al, '=' ; the character to print
 ; color IRGBIRGB
 mov bl, 0b00000010 ; green on black
 int 10h ; do it with a bios interrupt
 mov ah, 03h ; bios function: get cursor position into dx 
 int 10h ; invoke bios
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dl ; increment cursor column by 1
 int 10h ; invoke bios
 mov cx, 1 ; number of characters to print 
 mov ah, 09h ; bios function colour print 
 mov bl, 0b01101111
 int 10h ; colour print another =
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 The code below relies on the fact that the bios function
 AH=09h,BL=colour,CX=char-count does not update the cursor
 position after printing to the screen. So each iteration of the loop actually 
 overwrites n-1 characters of the previous iteration

 * print digits 0-9 in 9 different colours 
 -----------------------------------------
 start:
 mov cx, 0x0009
 .again
 mov ah, 09h ; the 'function' number
 mov al, cl ; the digit to print
 add al, '0' ; convert the digit to ascii 
 mov bl, cl ; use the CX counter to cycle thru 9 colours
 int 10h ; do it with a bios interrupt
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print digits 0-9 in 9 different colours in graphics mode
 -----------------------------------------
 start:

 mov ah, 0 ; set graphics display mode function.
 mov al, 13h ; mode 13h 
 int 10h ; set it!

 mov cx, 0x0009
 .again
 mov ah, 0Eh ; the 'function' number
 mov al, cl ; the digit to print
 add al, '0' ; convert the digit to ascii 
 mov bl, cl ; use the CX counter to cycle thru 9 colours
 int 10h ; do it with a bios interrupt
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 The code below uses a trick: on the 1st iteration 16 stars
 are printed in a colour, on the 2nd iteration 15 stars in a 
 different colour, but at the same location, thus overwriting
 all but the last of the 16 previous, and so on.

 * print 16 stars in 16 colours, or 15
 -----------------------------------------
 bigblock equ 219 ; ascii code for big block
 start:
 mov cx, 0x000F
 .again
 mov ah, 09h ; the 'function' number
 mov al, '*' ; the character to print a star 
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 int 10h ; do it with a bios interrupt
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print a whole screen of colourfull blocks
 -----------------------------------------

 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 bigblock equ 219 ; ascii code for big block
 blocks:
 dw 0
 db 5, 'blocks'
 blocks.x:
 mov cx, 0x0FEF
 .again:
 mov ah, 09h ; the 'function' number
 mov al, bigblock ; just a colour block 
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 ;mov bx, cx
 ;mov bl, [bx] ; get random data from memory for random colours 
 int 10h ; do it with a bios interrupt
 loop .again
 ret

 start:
 mov ax, cs
 mov ds, ax
 call blocks.x
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,



 The word 'type' in forth is so named because it automatically
 advances the text cursor like a teletyper. The code uses a 
 trick of writing the last letter first etc. The alternative to
 this is to update the cursor position after every character which
 is more work. In the code below if the string lenght is > 16 then
 background colours start to get printed. But this typer doesnt
 advance cursor

 * type some text in rainbow colours, forthstyle
 -----------------------------------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 buffer db 14, 'rainbowrainbow'

 ; (stack: text buffer addr - )
 typer:
 dw 0 ; link to next dict word or null
 db 5, 'typer' ; fn counted name
 typer.h:
 pop ax ; balance return ip
 pop si
 push ax
 xor bx, bx ; bx := 0 bh := 0 so no background colours
 xor cx, cx ; set cx:=0 
 mov cl, [si] ; the character count, used by loop and colours
 add si, cx ; set pointer to last char in string
 std ; make lodsb go in reverse
 .again:
 mov ah, 09h ; the 'function' number
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 lodsb ; get next char to al
 int 10h ; do it with a bios interrupt
 loop .again
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 push buffer
 call typer.h
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,


 * type some text in rainbow colours, forthstyle
 -----------------------------------------
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 buffer db 14, 'rainbowrainbow'

 ; (stack: text buffer addr - )
 typer:
 dw 0 ; link to next dict word or null
 db 5, 'typer' ; fn counted name
 typer.h:
 xor bx, bx ; bx := 0
 xor cx, cx ; set cx:=0
 mov cl, [buffer] ; the character count, used by loop and colours
 .again:
 mov ah, 09h ; the 'function' number
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 mov al, [buffer+bx] ; print last char first n times, then overwrite
 int 10h ; do it with a bios interrupt
 loop .again

 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 push buffer
 call typer.h
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,


\title{} 
\author{bumble.sourceforge.net} 
\maketitle 
\tableofcontents Basic Bios Colours 
 .. HEX BIN COLOUR
 .. 0, 0000 black
 .. 1 0001 blue
 .. 2 0010 green
 .. 3 0011 cyan
 .. 4 0100 red
 .. 5 0101 magenta
 .. 6 0110 brown
 .. 7 0111 light gray
 .. 8 1000 dark gray
 .. 9 1001 light blue
 .. A 1010 light green
 .. B 1011 light cyan
 .. C 1100 light red
 .. D 1101 light magenta
 .. E 1110 yellow
 .. F 1111 white
 ,,,


 * print "\\#\\#" light blue on white at the current cursor position
 -----------------------------------------------------
 mov ah, 0x09 ; bios function colour print 
 mov al, '\\#' ; character to print 
 mov bl, 0b11110001 ; blue on white background
 mov cx, 2 ; how many times to print character 
 int 10h ; invoke bios function 
 jmp \\$ ; infinite loop 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print a triangle of stars in 16 different colours
 -----------------------------------------------------
 start:
 mov cx, 0xF ; 16 colours
 .again
 mov ah, 09h ; the 'function' number for colour print
 mov al, '*' ; the character to print
 mov bx, cx ; colour in cx counter at first page (bh=0)
 int 10h ; do it with a bios interrupt
 mov ah, 0eH ; teletype function 
 mov al, 10 ; a form-feed 
 int 10h ; do it
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print a triangle of characters in 16 different colours
 -----------------------------------------------------

 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 ; stack: - 
 tri.doc db 'a triangle of colourful characters'
 dw \\$-tri.doc
 tri:
 dw 0 ; link to next dict word or null
 db 3, 'tri' ; counted name of function
 tri.x:
 mov cx, 0xF ; 16 colours
 .again
 mov ah, 09h ; the 'function' number for colour print
 mov al, cl ; the character to print
 add al, 'A'-1 ; convert al to ascii A-O letters
 mov bx, cx ; bl:1234 = bg colour, bl:5678 = fg colour 
 ; colours bits: Intensity, Red, Green, Blue
 ; bh: page number (?)
 int 10h ; do it with a bios interrupt
 mov ah, 0eH ; teletype function 
 mov al, 10 ; a form-feed goes to beginning of next line 
 int 10h ; x86 real-mode bios interrupt 
 loop .again ; loop while cx > 0
 ret

 start:

 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 call tri.x

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print a triangle of characters in 16 different colours
 -----------------------------------------------------
 start:
 mov cx, 0xF ; 16 colours
 .again
 push cx
 mov ah, 09h ; the 'function' number for colour print
 mov al, 'Z'
 sub al, cl ; the character to print
 mov bx, cx ; colour in cx counter at first page (bh=0)
 mov cx, 1
 int 10h ; do it with a bios interrupt
 mov dx, 0
 ;mov bh, 00h ; assume page 0
 mov ah, 03h ; get cursor position into dx 
 int 10h
 mov ah, 02h ; set cursor position specified in dx
 inc dl 
 int 10h
 ;mov ah, 0eH ; teletype function 
 ;mov al, 10 ; a form-feed 
 ;int 10h ; do it
 pop cx
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * change whole screen colours to white on blue
 -----------------------------------------------------

 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 ; stack: - 
 blue.doc db 'change video colours to white on blue'
 dw \\$-blue.doc
 blue:
 dw 0 ; link to next dict word or null
 db 4, 'blue' ; fn counted name
 blue.x:
 mov ah, 0Bh ; 
 mov bh, 0 
 mov bl, 11110001b ; bl: white:blue (Intensity Red Green Blue)
 int 10h 
 mov ah, 0eH ; print some character
 mov al, '\\#' 
 int 10H
 ret

 start:

 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 call blue.x

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,

 * print blue then green, no !!! not working
 -----------------------------------------------------
 mov ah, 0Bh 
 mov bh, 0 
 mov bl, 00010000b ; blue on black
 int 10h 
 mov ah, 0eH ; print the character
 mov al, '\\#' 
 int 10h
 mov ah, 0Bh 
 mov bh, 0 
 mov bl, 00100000b ; green on black
 int 10h 
 mov ah, 0eH ; print the character
 mov al, '\\#' 
 int 10h
 jmp \\$ 
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,


BACKGROUND COLOURS ....


 * print ribbons of background colours
 -----------------------------------------

 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 ; stack: - 
 patch.doc db 'displays a columns of colour'
 dw \\$-patch.doc
 patch:
 dw 0 ; link to next dict word or null
 db 5, 'patch' ; fn counted name
 patch.x:
 mov cx, 0x05FF
 .again:
 mov ah, 09h ; the 'function' number
 mov al, ' ' ; the character to print a space
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 shl bl, 4 ; the 4 top bits are background colour
 int 10h ; x86 bios interrupt
 loop .again
 ret

 start:

 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 call patch.x

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,


 * print 16 background colours
 -----------------------------------------
 start:
 mov cx, 0x00FF
 .again
 mov ah, 09h ; the 'function' number
 mov al, ' ' ; the character to print a space
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 int 10h ; do it with a bios interrupt
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 
ARTFUL COLOURS ....

 * print 16 colours, with 16 backgrounds
 -----------------------------------------
 start:
 mov cx, 0x00FF
 .again
 mov ah, 09h ; the 'function' number
 mov al, '*' ; the character to print a star 
 mov bl, cl ; use the CX counter to cycle thru 16 colours
 int 10h ; do it with a bios interrupt
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print some ascii digit in colour by advancing cursor 
 -----------------------------------------------------
 jmp start
 start:
 mov cx, 9
 .again:
 push cx
 mov ah, 09h ; bios function colour print 
 mov al, cl ; the digit to print
 add al, '0' ; convert digit to ascii
 mov bl, cl ; colour in counter CX 
 mov cx, 1 ; number of characters to print 
 int 10h ; invoke bios 
 mov ah, 03h ; bios function: get cursor position into dx 
 int 10h ; invoke bios
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dl ; increment cursor column by 1
 int 10h ; invoke bios
 pop cx
 loop .again
 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 * print all ascii chars in colour by advancing cursor 
 -----------------------------------------------------

 [ORG 0]
 jmp 07C0h:start ; Goto segment 07C0

 ; stack: - 
 asci.doc db 'colourful ascii in 16 columns'
 dw \\$-asci.doc
 asci:
 dw 0 ; link to next dict word or null
 db 4, 'asci' ; fn counted name
 asci.x:
 mov cx, 0x00FF 
 .again:
 push cx
 mov ah, 09h ; bios function colour print 
 mov al, 0xFF ; print ascending order
 sub al, cl ; the ascii char to print
 mov bl, cl ; colour in counter CX 
 and bl, 0x0F ; only print foreground colours
 mov cx, 1 ; number of characters to print 
 int 10h ; invoke bios 
 mov ah, 03h ; bios function: get cursor position into dx 
 int 10h ; invoke bios
 mov ah, 02h ; bios function: set cursor position specified in dx
 inc dl ; increment cursor column by 1
 int 10h ; invoke bios
 pop cx
 test cl, 0b00001111 ; 32 characters to a line
 jne .here
 mov ah, 0eH ; bios 'teletype' function
 mov al, 10 ; form feed char
 int 10H ; invoke bios 
 mov al, 13 ; return char
 int 10H ; invoke bios 
 .here:
 loop .again
 ret

 start:

 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax

 call asci.x

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 ,,,,

CHESS 

 The start of a chess engine written in a forth style

 * print one chess square algebraically 
 ---------------
 [org 0]
 jmp 07C0h:start

 chess.board:
 db 0, 0, 0, 0, 0, 0, 0, 0
 db 0, 0, 0, 0, 0, 0, 0, 0
 db 0, 0, 0, 0, 0, 0, 0, 0
 db 0, 0, 0, 0, 0, 0, 0, 0
 db 0, 0, 0, 0, 0, 0, 0, 0
 db 0, 0, 0, 0, 0, 0, 0, 0
 db 0, 0, 0, 0, 0, 0, 0, 0
 db 0, 0, 0, 0, 0, 0, 0, 0

 printsquare.doc:
 db 'Prints in algebraic notation one chess square, given an offset'
 db 'takes offset on stack as parameter. Squares are 0-63, with '
 db 'a1==0, b1==1, a2==8 ...h8=63 '
 dw \\$-printsquare.doc
 printsquare:
 dw 0 ; link to previous
 db 11, 'printsquare'
 printsquare.x:
 pop dx ; juggle return fn ip
 pop ax ; get offset into ax
 push dx ; restore return
 mov bl, 8 ; could be done faster with AND 0b00000111 etc
 ; but printing squares isnt time critical
 div bl ; al:=quotient, ah:=remainder eg 1r2
 push ax ; save quotient/remainder
 mov al, ah ; remainder -> al for printing
 add al, 'a' ; convert column to chess column (a-h)
 mov ah, 0x0e ; int 10h 'print char' function
 int 10h ; print char in al
 pop ax ; restore quotient/remainder
 mov ah, 0x0e ; int 10h 'print char' function
 add al, '1' ; convert row to ascii digit
 int 10h ; print char in al
 ret

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 push 63 ; eg h8 
 call printsquare.x
 push 0 ; eg a1 
 call printsquare.x
 push 11 ; eg d2 
 call printsquare.x

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
 * a sketch of how to write a chess engine in a forth style
 ---------------

 BITS 16
 [ORG 0]

 jmp 07C0h:load ; Goto segment 07C0
 drive db 0 ; a variable to hold boot drive number
 load:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax
 mov [drive], dl ; save the boot drive number
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax ; with a 4K gap between stack and code
 mov sp, 4096

 ; save the DL register or else dont modify it
 ; it contains the number of the boot medium (hard disk,
 ; usb memory stick etc)
 ; The 'floppy' Drive is NOT necesarily 0!!!

 reset: ; Reset the floppy drive
 mov ax, 0 ; 
 mov dl, [drive] ; the boot drive number (eg for usb 128)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ; es:bx determines where data loaded to
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 8 ; Load 5 sectors ie 512 bytes * 5 == 2.5K 
 ; try mov cx, 0x0002 ; cylinder 0, sector 2
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2 (sector 1 is the boot sector)
 mov dh, 0 ; Head=0
 mov dl, [drive] ; 
 int 13h ; Read!
 jc read ; ERROR => Try again

 jmp 1000h:0000 ; Jump to the loaded code 

 times 510-(\\$-\\$\\$) db 0 ; pad out the boot sector (512 bytes)
 dw 0AA55h ; end with standard boot signature

 ; this below is the magic line to make the new memory offsets
 ; work. Or compile the 2 files separately
 ; https://forum.nasm.us/index.php?topic=2160.0 

 section stage2 vstart=0

 jmp start

 ; the code to be loaded and executed
 ; cs is ok because of far jump
 ; is ds and es ok ? no, but stack seems ok
 
 ; a list of chars which can be used to print the pieces for moves
 piece.char db '0PNBRQK |' ; first is empty, 10 is off the side
 ; characters for text board
 ;piece.render db '.PNBRQK |' ; first is empty, 10 is off the side
 piece.render db '.', 0x1E, 0xA4, 0x06, 0xCB, 'Q', 0x0B
 db ' |' 

 pawnglyph equ 0x1E
 kingglyph equ 0x0C
 rookglyph equ 0x01

 ; symbolic constants for pieces. same or as vectors
 ; we can get the offset from "pieces:" to get its appropriate "vector"

 pieces:
 empty equ 0
 pawnw equ 1
 knightw equ 2
 bishopw equ 3
 rookw equ 4 
 queenw equ 5
 kingw equ 6
 side equ 10

 ; pawnb - 8 will give pawnw, if this is useful
 pawnb equ 9
 knightb equ 10 
 bishopb equ 11 
 rookb equ 12 
 queenb equ 13 
 kingb equ 14 

 ; The vectors are what we can add to SI from-square
 ; to get DI to-square
 ; we need the vector table to look up the right vector
 ; the first element of the vectors is a count

 vectortable 
 dw 6 ; a count, makes piece symbolic value match table
 dw pawn.vector, knight.vector, bishop.vector 
 dw rook.vector, queen.vector, king.vector

 ; vectors are zero terminated for convenience offsets start at "north" and 
 ; go clockwise. These offset are for a 12x8 board. The board being 12 bytes
 ; wide makes the "off side of board" check very efficient.

 pawn.vector dw 12, 0 ; and '20' but only on rank 2
 knight.vector dw 25, 14, -10, -23, -25, -14, 10, 23, 0 
 bishop.vector dw 13, -11, -13, 11, 0
 rook.vector dw 12, 1, -12, -1, 0
 queen.vector dw 12, 13, 1, -11, -12, -13, -1, -11, 0 ; 1st is count
 king.vector dw 12, 13, 1, -11, -12, -13, -1, -11, 0 ; 1st is count

 
 board.doc:
 db 'puts a pointer to the board data structure onto the stack '
 db ' board contains, turn, history, score and squares'
 dw \\$-board.doc
 board:
 dw 0 ; link to previous
 db 5, 'board'
 board.x:
 pop dx ; juggle fn return pointer
 push board.data
 push dx
 ret

 board.data:
 turn: dw 0 ; who to play, white 0 or black 1
 history: ; this is the depth search
 dw 0 ; count of moves
 dw 0,0,0,0,0 ; array of from-square, to-square
 score: dw 0 ; current board score
 squares:
 db side, side, 0, 0, rookw, 0, 0, 0, pawnw, 0, side, side
 db side, side, 0, 0, 0, 0, 0, 0, 0, 0, side, side
 db side, side, 0, 0, knightw, 0, 0, 0, 0, 0, side, side
 db side, side, 0, 0, 0, 0, 0, 0, 0, 0, side, side
 db side, side, 0, 0, 0, 0, 0, 0, 0, 0, side, side
 db side, side, 0, 0, 0, 0, knightw, 0, 0, 0, side, side
 db side, side, 0, 0, 0, 0, 0, 0, 0, 0, side, side
 db side, side, 0, 0, pawnw, 0, kingw, 0, 0, 0, side, side

 ;1E triangle pawn
 ;0C female simbol king

 printpiece.doc:
 db 'just prints asci version of piece value for debug'
 dw \\$-printpiece.doc
 printpiece:
 dw board ; link to previous
 db 10, 'printpiece'
 printpiece.x:
 pop dx
 pop si ; piece symbolc value (1-7 etc)
 push dx ; restore fn

 add si, piece.char ; 0=empty, 1=pawn ..., 10=side
 lodsb
 mov ah, 0x0e
 int 10h
 ret

 printvector.doc:
 db 'just print the piece vectors for debugging' 
 db ' takes symbolic piece value on the stack '
 db ' - modify to loop until zero termination '
 dw \\$-printvector.doc
 printvector:
 dw printpiece ; link to previous
 db 11, 'printvector'
 printvector.x:
 ; just print each vector
 pop dx
 pop bx ; piece symbolic value
 push dx
 shl bx, 1 ; bl := bl*2 , since vectortable pointer is word not byte
 mov si, [vectortable+bx] ; 

 ;xor cx, cx ; set cx = 0, so that count in cl works
 ;lodsb ; get count into al. al := [di]++ 
 ;mov cl, al ; vector count into cl

 mov ah, 0x0e ; int 10h 'print char' function
 mov al, '>' ; some start char
 int 10h ; print char in al
 .next:
 lodsw ; get next vector into al
 cmp ax, 0 ; zero terminated vectors
 je .exit ; exit if at end of piece vector
 test ax, ax ; see if ax is negative
 jns .print ; if not negative just skip
 push ax ; save move offset
 mov ah, 0x0e ; int 10h 'print char' function
 mov al, '-' ; print a negative indicator
 int 10h ; print char in al
 pop ax ; restore move offset 
 neg ax ; make positive for printing 
 .print:
 ; piece jumps are at most 2 decimal digits so we 
 ; will assume here that the quotient after div 10
 ; is the 1st digit
 mov bl, 10 ; divide by 10
 ;xor ah, ah ; this is necessary !! not sure why
 div bl ; do ax/10, ah=remainder, al=quotient
 mov bl, ah ; just save ah
 mov ah, 0x0e ; int 10h 'print char' function
 cmp al, 0 ; if 1st digit is zero, dont print it
 je .second
 add al, '0' ; convert 1st digit to ascii
 int 10h ; print char in al (quotient)
 .second:
 mov al, bl ; print second digit
 add al, '0' ; convert 2nd digit to ascii
 int 10h ; print char in al
 mov al, ' ' ; separator for vectors
 int 10h ; print char in al
 jmp .next
 .exit:
 ret

 printsquare.doc:
 db 'Prints in algebraic notation one chess square, given an offset'
 db 'takes offset on stack as parameter. valid Squares are 0-95 with'
 db ' valid squares 2-8, 14-20, 26-32 etc, '
 db ' all other offsets being off the side of the board. '
 db 'also prints piece on the square '
 dw \\$-printsquare.doc
 printsquare:
 dw printvector ; link to previous
 db 11, 'printsquare'
 printsquare.x:
 pop dx ; juggle return fn ip
 pop ax ; get offset into ax
 push dx ; restore return
 push ax ; save square offset
 
 ; this is hacked together but demostrates an important 
 ; technique. ie. getting the piece by value on a square on 
 ; the board
 xor bx, bx ; set bx = 0.
 mov bx, ax ; get square offset into bx
 mov bl, [squares+bx] ; get the piece value on square into bx
 mov al, [piece.char+bx]
 mov ah, 0x0e ; int 10h 'print char' function
 int 10h ; print char in al

 pop ax ; restore square offset

 ; valid squares 2-8, 14-20, 26-32 etc, 
 ; technique is subtract 12 repeatedly from offset, counting
 ; loops. the count is the rank and 2-9 offset is the file

 xor cx, cx ; set loop counter = 0
 .again:
 inc cx
 cmp ax, 10
 jb .print
 sub ax, 12
 jmp .again
 .print:
 add al, 'a'-2 ; convert column to chess column (a-h)
 mov ah, 0x0e ; int 10h 'print char' function
 int 10h ; print char in al
 mov al, cl ; board rank (1-8)
 add al, '0' ; convert to asci digit
 int 10h ; print char in al

 ret

 printmove.doc:
 db 'Prints in algebraic notation one chess move, given square offsets'
 db 'on the stack as parameters. [tos: to, from] Squares are 0-95, with '
 db 'a1==2, b1==3, h1==8 ...h8=63 '
 db ' need to add capture X to move printout'
 dw \\$-printmove.doc
 printmove:
 dw 0 ; link to previous
 db 9, 'printmove'
 printmove.x:
 pop dx ; juggle return fn ip
 pop bx ; get to-square offset (0-63) into ax
 pop ax ; get from-square offset (0-63) into bx
 push dx ; restore return

 push bx ; save bx (to-square)
 push ax
 xor bx, bx ; set bx = 0.
 mov bx, ax ; get square offset into bx
 mov bl, [squares+bx] ; get the piece value on square into bx
 mov al, [piece.char+bx]
 mov ah, 0x0e ; int 10h 'print char' function
 int 10h ; print char in al
 pop ax
 pop bx ; restore to-square

 xor cx, cx ; set loop counter = 0
 .again:
 inc cx
 cmp ax, 10 ; 9 is last square on 1st rank
 jb .print
 sub ax, 12
 jmp .again
 .print:
 add al, 'a'-2 ; convert column to chess column (a-h)
 mov ah, 0x0e ; int 10h 'print char' function
 int 10h ; print char in al
 mov al, cl ; board rank (1-8)
 add al, '0' ; convert to asci digit
 int 10h ; print char in al

 mov ax, bx ; get to-square into ax
 push ax ; save ax (to-square offset)

 xor bx, bx ; set bx = 0.
 mov bx, ax ; get square offset into bx
 mov bl, [squares+bx] ; get the piece value on square into bx
 
 mov al, '-' ; default move separator (no capture) 
 cmp bl, side ; check for move off the side of the board
 jne .empty
 mov al, '?' ; illegal move indicator
 jmp .separator
 .empty:
 cmp bl, 0 ; is to-square empty?
 je .separator
 mov al, 'x' ; capture indicator
 .separator:
 mov ah, 0x0e ; int 10h bios 'print char' function
 int 10h ; print square separator al
 pop ax ; restore to-square offset

 xor cx, cx ; set loop counter = 0
 .againx:
 inc cx
 cmp ax, 10 ; 9 is last square on 1st rank
 jb .printx
 sub ax, 12
 jmp .againx
 .printx:
 add al, 'a'-2 ; convert column to chess column (a-h)
 mov ah, 0x0e ; int 10h 'print char' function
 int 10h ; print char in al
 mov al, cl ; board rank (1-8)
 add al, '0' ; convert to asci digit
 int 10h ; print char in al

 mov al, ' ' ; print - as separator 
 int 10h ; print char in al
 
 .exit:
 ret

 
 textboard.doc:
 db 'Prints an asci chess board with a1 in the lower left hand'
 db 'corner. So print a8 -> h8, then a7 -> h7 etc '
 db 'maybe make custom glyphs and write them into bios asci '
 db 'memory to make a nicer text board'
 db ' also an asci box around this would be nice'
 dw \\$-textboard.doc
 textboard:
 dw printmove ; link to previous
 db 9, 'textboard'
 textboard.x:
 mov dx, 86
 .nextrow: 
 mov cx, 8 ; 8 squares per row
 mov bx, piece.render ; translation table
 mov si, squares ; square a8 on 12x8 board
 add si, dx ; start of next row, eg 86, 74, 62, ... 14, 2
 mov ah, 0x0e ; print char
 .nextsquare:
 lodsb ; get 1st square into al
 xlatb ; replace al with char in piece.chars table
 int 10h
 loop .nextsquare
 mov al, 13 ; print new line
 int 10h
 mov al, 10
 int 10h
 sub dx, 12
 cmp dx, 0
 jg .nextrow 
 .exit:
 mov cx, 8 ; print algebraic file labels a-h 
 .letters:
 mov al, 'i' ; 'h'+1, last file
 sub al, cl
 mov ah, 0x0e ; print char
 int 10h
 loop .letters

 ret 

 twodigit.doc:
 db 'just prints a signed 2 digit number in decimal'
 dw \\$-twodigit.doc
 twodigit:
 dw textboard ; link to previous
 db 6, '2digit'
 twodigit.x:
 pop dx ; balance return pointer
 pop ax ; get signed 2 digit number into ax 
 push dx ; restore
 .next:
 cmp ax, 99 ; if its > 99 we cant show it here
 jg .toobig
 cmp ax, -99 ; if its < -99 we cant show it here
 jl .toosmall
 test ax, ax ; see if al is negative
 jns .print ; if not negative just skip
 push ax ; save ah and al
 mov ah, 0x0e ; int 10h 'print char' function
 mov al, '-' ; print a negative indicator
 int 10h ; print char in al
 pop ax ; restore ah and al (the digit)
 neg ax ; make positive for printing 
 .print:
 ; we will assume here that the quotient after div 10
 ; is the 1st digit
 mov bl, 10 ; divide by 10
 ;xor ah, ah ; this is necessary !! not sure why
 div bl ; do ax/10, ah=remainder, al=quotient
 mov bl, ah ; just save ah
 mov ah, 0x0e ; int 10h 'print char' function
 cmp al, 0 ; if 1st digit is zero, dont print it
 je .second
 add al, '0' ; convert 1st digit to ascii
 int 10h ; print char in al (quotient)
 .second:
 mov al, bl ; print second digit
 add al, '0' ; convert 2nd digit to ascii
 int 10h ; print char in al
 mov al, ' ' ; separator for vectors
 int 10h ; print char in al
 .exit:
 ret
 .toobig:
 mov ah, 0x0e ; int 10h 'print char' function
 mov al, '!' ; show a message indicating number to big 
 int 10h ; print char in al
 mov al, '>' ; 
 int 10h ; print char in al
 mov al, ' ' ; 
 int 10h ; print char in al
 ret
 .toosmall:
 mov ah, 0x0e ; int 10h 'print char' function
 mov al, '!' ; a message that number is too small (< -99) 
 int 10h ; print char in al
 mov al, '<' ; 
 int 10h ; print char in al
 mov al, ' ' ; 
 int 10h ; print char in al
 ret

 onepiece.doc:
 db 'finds all legal moves for one piece on a chess board'
 db 'Use bx as vector pointer. si as start square, di as end square.' 
 db ' still need to deal with black/white logic '
 dw \\$-onepiece.doc
 onepiece:
 dw twodigit ; link to last fn
 db 6, '1piece'
 onepiece.x:
 ; now for next piece logic here, but put it in a 
 ; different function eg "allmoves". scan squares to find next
 ; piece of white or black. could use bp as a square pointer?
 
 pop dx 
 pop si ; the piece square (0-95)
 push dx ; restore fn return pointer
 xor bx, bx

 mov bl, [squares+si] ; get the piece value on square into bx
 cmp bl, 0 ; empty square so find a piece 
 je .exit
 ;push bx
 ;call printvector.x
 ;ret
 shl bl, 1 ; bl := bl*2 , since vectortable pointer is word not byte
 mov bx, [vectortable+bx] ; get piece vector

 sub bx, 2 ; otherwise will skip the first vector 
 .turn: ; next direction for piece moves
 add bx, 2 ; bx is word pointer in move offset vector
 push si
 pop di ; set si := di (from-square == to-square)
 mov dx, [bx] ; get move offset from piece vector
 cmp dx, 0 ; vectors are zero terminated 
 je .exit ; no more turns, so exit or nextpiece

 .nextmove:
 add di, dx ; eg 2 + 8 (rook move)

 ; find out what di is
 ;pusha 
 ;push di
 ;call twodigit.x
 ;popa
 
 ; check legality etc 
 cmp di, 95 ; is move off top of board?
 jg .turn ; move off board so try next move offset 

 cmp di, 0 ; is move off bottom of board?
 jl .turn ; move off board so try next move offset 

 mov al, [squares+di] ; get piece on to-square
 cmp al, 0 ; if yes move legal no turn
 je .showmove 
 cmp al, side ; off the side of the board so illegal 
 je .turn ; 
 cmp al, 8
 jl .turn ; own piece so illegal move

 ; else opposition piece so legal move, but must turn
 ; again

 .showmove:
 inc cx ; increment a move counter
 pusha ; conserve all general purpose regs, since printmove mods them
 push si ; from-square
 push di ; to-square
 call printmove.x
 popa

 mov al, [squares+di] ; get piece on from-square
 cmp al, 8 ; if destination square is opposition then must turn
 jg .turn

 ; logic to turn when it is a knight or king etc
 ; that is a piece that can only move one square

 mov al, [squares+si] ; get the piece value on square into al
 cmp al, knightw ; knights must turn 
 je .turn
 cmp al, kingw ; kings must turn 
 je .turn
 cmp al, pawnw ; pawns must turn ?
 je .turn

 jmp .nextmove 

 .exit:
 ;push si
 ;call printsquare.x
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax

 ;mov [bos.n], sp ; set up stack

 call textboard.x
 ;push 4 ; c1 - from-square 
 ;push 28 ; g2 - to-square. eg h1-g2
 ;call printmove.x

 push 16 ; c1 (with a rook on it )
 ;call printsquare.x
 push 17 ; d1 (with a rook on it )
 ;call printsquare.x

 push 4 ; c1 - from-square 
 ;call onepiece.x

 jmp \\$ ; halt here

 ,,,


USEFULL PROCEDURES

 This section contains a set of hopefully useful proceedures

 * print a zero terminated string with address in the SI register 
 -----------------
 BITS 16

 jmp start
 message db 'A function to print',13,10,0
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov si, message ; Put string position into SI
 call prints ; Call our string-printing routine

 hang: jmp hang ; Jump here - infinite loop!

 ;\\# prints
 ; output zero terminated string in SI to screen
 prints: 
 mov ah, 0Eh ; int 10h 'print char' function

 .again:
 lodsb ; Get character from string
 cmp al, 0
 je .done ; If char is zero, end of string
 int 10h ; Otherwise, print it
 jmp .again

 .done:
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 * a proceedure to print a character in colour and advance cursor 
 -----------------------------------------------------
 start:
 mov al, 'G'
 mov cx, 0x002F ; start at white on green 
 .again:
 mov bl, cl ; some colour
 call putcc
 loop .again
 jmp \\$ ; loop forever

 ; proc: print a coloured character (in AL) and colours (in BL)
 putcc:
 push ax
 push bx
 push cx
 push dx
 ; mov al, 7
 mov bh, 0 ; assume we are working in the first page 
 mov ah, 09h ; the 'function' number for colour print
 mov cx, 1 ; print the character once
 int 10h ; do it with a bios interrupt
 mov ah, 03h ; get cursor position into dx 
 int 10h
 mov ah, 02h ; set cursor position function 
 inc dl ; increment the column position 
 int 10h
 pop dx
 pop cx
 pop bx
 pop ax
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

 The code below needs to perform a modulus on BL to make
 the colours cycle through the 16 allowable text mode colours

 * a procedure to 'rainbow' print some text (each letter a new colour) 
 -----------------------------------------------------
 jmp start
 message db '8086 in realmode rainbow!@\\#\\$\\%\\^\\&*', 0
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096

 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov si, message ; Put string position into SI
 call printcolour 
 jmp \\$ ; loop forever

 ; proc: print a string in rainbow colour 
 ; string address in SI
 printcolour:
 push bx
 .resetcolour:
 mov bl, 1 ; colours start from 1 because 0 is black
 .repeat:
 lodsb ; Get character from string
 cmp al, 0 ; is the character byte 0
 je .done ; If char is zero, end of string
 call putcc ; Otherwise, print it in colour
 cmp bl, 15 ; if bl is at the last colour reset it
 je .resetcolour
 inc bl 
 jmp .repeat
 .done:
 pop bx
 ret

 ; print a coloured character (in AL) and colours (in BL)
 putcc:
 push ax ; save registers to the stack
 push bx
 push cx
 push dx
 mov bh, 0 ; assume we are working in the first page 
 mov ah, 09h ; the 'function' number for colour print
 mov cx, 1 ; print the character once
 int 10h ; do it with a bios interrupt
 mov ah, 03h ; get cursor position into dx 
 int 10h
 mov ah, 02h ; set cursor position function 
 inc dl ; increment the column position 
 int 10h
 pop dx
 pop cx
 pop bx
 pop ax
 ret
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,,

READING FROM DISKS

 The AH=02h, int 13h function allows reading from a 'floppy'
 (or usb emulating a floppy) or a hard drive. This is probably
 the most important function of a 'bootloader', that is, it
 must load something (code) from the disk in order to overcome
 the 1 sector (512byte) limit of the bootsector.

 Some pundits say that resetting and reading should be tried
 3 times. In the case of a real floppy the first read may not
 work because the device takes some time to 'spin up' etc.
 These factors should not apply to a usb memory stick.

 * read and print some text which is in the 2nd sector 
 -----------------
 BITS 16

 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 reset: ; Reset the floppy drive
 mov ax, 0 ;
 mov dl, 0 ; Drive=0 (=A)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ; es:bx determines where data loaded to
 mov bx, 0 ;
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 5 ; Load 5 sectors
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2
 mov dh, 0 ; Head=0
 mov dl, 0 ; Drive=0, 'floppy' (or usb key)
 int 13h ; Read!
 jc read ; ERROR => Try again

 mov al, [es:bx] ; print 2 characters loaded
 mov ah, 0eh
 int 10h
 mov al, [es:bx+1]
 int 10h
 
 hang: jmp hang
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 data db 'some sample data',0 
 ,,,

 * read 1 sector into a variable in the data sector 
 -----------------
 BITS 16
 jmp start
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 reset: ; Reset the floppy drive
 mov ax, 0 ;
 mov dl, 0 ; Drive=0 (=A)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov ax, ds ; ES:BX = this data segment, message variable
 mov es, ax ; es:bx determines where data loaded to
 mov bx, message ; load into the 'message' variable buffer
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 1 ; Load 1 sector, 512 bytes
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2
 mov dh, 0 ; Head=0
 mov dl, 0 ; Drive=0, 'floppy' (or usb key)
 int 13h ; Read!
 jc read ; ERROR => Try again

 mov ah, 0eh
 mov al, [message] ; print 2 characters loaded
 int 10h
 mov al, [message+1]
 int 10h
 
 hang: jmp hang
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 data db 'loaded data',0 
 message times 512 db 0
 ,,,

 * read 1 sector and print out the string data
 -----------------
 BITS 16
 jmp start
 message.reset db 'resetting the floppy',13,10,0
 message.read db 'reading 1 sector',13,10,0
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 reset: ; Reset the floppy drive
 mov si, message.reset
 call prints
 mov ax, 0 ;
 mov dl, 0 ; Drive=0 (=A)
 int 13h ;
 jc reset ; ERROR => reset again
 read:
 mov si, message.read
 call prints
 mov ax, ds ; ES:BX = this data segment, message variable
 mov es, ax ; es:bx determines where data loaded to
 mov bx, message ; load into the 'message' variable buffer
 mov ah, 2 ; Load disk data to ES:BX
 mov al, 1 ; Load 1 sector, 512 bytes
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2
 mov dh, 0 ; Head=0
 mov dl, 0 ; Drive=0, 'floppy' (or usb key)
 int 13h ; Read!
 jc read ; ERROR => Try again

 mov si, message
 call prints

 hang: jmp hang
 \\%include 'prints.asm'
 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h
 data db 'loaded data',0 
 message times 512 db 0

 ,,,

WRITING TO DISKS

 The bios contains functions (under INT 13h) for writing
 to the 'floppy' disk (nowdays a usb memory stick which
 is emulating a floppy) or to a hard disk. We must be VERY
 thoughtful when writing to a hard disk, or we will end
 up with the computer completely unusable!!!!!. 
 The same applies to the floppy
 but perhaps the consequences are less catastrophic.

 * write to hard disk, dont do it!!! you wont have a working comp
 ----------
 xor ax, ax
 mov es, ax ; ES <- 0
 mov cx, 1 ; cylinder 0, sector 1
 mov dx, 0080h ; DH = 0 (head), drive = 80h (0th hard disk)
 mov bx, 5000h ; segment offset of the buffer
 mov ax, 0301h ; AH = 03 (disk write), AL = 01 (number of sectors to write)
 ;int 13h

 ,,,

 The code below should check that we are not writing to 
 a hard disk (eg DL=80h) because doing so will probably 
 render the computer unusable at all!

 * write to the boot medium (a usb stick hopefully) 
 ----------
 ; see the read disk section for some better code
 ; to do this
 xor ax, ax
 mov es, ax ; ES := 0
 mov cx, 1 ; cylinder 0, sector 1
 mov dh, 0 ; head 0
 mov dl, 0 ; 1st floppy but not usb memory stick
 mov bx, 5000h ; segment offset of the buffer
 mov ah, 03 ; disk write
 mov al, 01 ; write only 1 sector (512 bytes)
 int 13h
 ,,,

WRITE TO FLOPPY OR USB ....

 The happy answer is that a simple technique allows the same boot sector
 code to access a floppy disk image on a USB flash drive whether it was
 booted with floppy disk emulation or hard drive emulation. If dl=80h
 (hard drive emulation)

 * get drive parameters
 -------------
 int 13h, ah=8
 Return:
 ch=maximum sector number (same as number of sectors per track)
 dh=maximum head number (just add 1 to get number of heads)
 ,,,

 This returned information describes the geometry of the emulated device
 (if dl=0 then it's standard floppy disk geometry - 18 sectors per track
 and 2 heads). This can be used to calculate the required Cylinder Head
 Sector information required for:

 READ SECTOR(S)
 int 13h, ah=2

 WRITE SECTOR(S)
 int 13h, ah=3

CMOS

 \url{http://wiki.osdev.org/CMOS}
 good cmos and realtime clock information

 \url{http://vitaly\\_filatov.tripod.com/ng/asm/asm\\_029.3.html}
 more timer info.

REAL TIME CLOCK RTC ....

 \url{http://stackoverflow.com/questions/3215878/what-are-in-out-instructions-in-x86-used-for}
 excellent low level device examples in assembler

 https://github.com/cirosantilli/x86-bare-metal-examples/blob/9a24f92f36a45abb3f8c37aafc0c3ee9b15563ab/in\\_rtc.S
 complete assembler example

 The real time clock keeps track of the time even when the 
 computer is turned off. It is located on the cmos chip of
 x86 computers.

 Reading the rtc information from the cmos is simple. just send a 
 register select to port address 0x70 and read the answer from 0x71.
 
 But we should check that an update is not in progress. So we can
 block while the highest bit of 0x0A register is set

 * eg
 -------
 while
 out\\_byte(0x70, 0x0A);
 in\\_byte(0x71) \\& 0x80 
 / eg in 0x71; test 
 is true, block
 ,,,


 Another trick is to check the values twice in succession and 
 wait for equal results to be returned to make sure garbage is
 not being read.

 * check if the cmos rtc uses binary code decimal results
 ----------
 out\\_byte(0x70, 0x0B);
 in\\_byte(0x71) \\& 0x40 
 is true, then not BCD, otherwise must convert
 ,,,

 * converting BCD binary coded decimal to binary
 ------------------
 second = (second \\& 0x0F) + ((second / 16) * 10);
 all the same, except hour
 hour = ( (hour \\& 0x0F) + (((hour \\& 0x70) / 16) * 10) ) | (hour \\& 0x80);
 ,,,

 * get rtc info from the cmos
 --------------
 out\\_byte(0x70, 0x00); second
 in\\_byte(0x71);
 out\\_byte(0x70, 0x02); minute
 in\\_byte(0x71);
 out\\_byte(0x70, 0x04); hour 
 in\\_byte(0x71);

 etc
 day = get\\_RTC\\_register(0x07);
 month = get\\_RTC\\_register(0x08);
 year = get\\_RTC\\_register(0x09);

 ,,,, 


 Or just get the number of seconds from rtc for testing
 
 The seconds and hour appear to be binary coded decimal
 Check if the real time clock is set to UTC or local time.

 Gotcha! dothexbyte is modifying the cx register 

 We can use dothexbyte to print the time because the cmos clock
 is often in BCD format. 
 
 * a very terse way to print the time, but harder to read
 -------------------
 mov cx, 3 ; 3 components of the time (hour:minute:seconds)
 .next:
 mov al, cl ; al loops 321: 0x04 hour, 0x02 minute, 0x00 seconds
 sub al, 1 ; al loops 210
 shl al, 1 ; al loops 420, which is correct for cmos select reg
 out 0x70, al ; address rtc minute register
 in al, 0x71 ; get data from cmos data reg
 push cx ; gotcha! .hexbyte modifies cx counter
 push ax
 call dothexbyte.x ; print bcd hour:minute:seconds
 pop cx ; restore cx counter
 mov al, ':'
 mov ah, 0x0E
 int 0x10
 loop .next
 ,,,

 * print the current time from the cmos real time clock 
 ------------------
 BITS 16
 [ORG 0]
 jmp 07C0h:start 
 
 hextable db "0123456789ABCDEF" ; digit translation table

 ; **
 dw 0 
 dothexbyte:
 dw 0 ; link
 db 8, '.hexbyte'
 dothexbyte.x:
 pop bx ; fn return address
 pop dx ; the number to print (parameter on stack)
 push bx ; restore return address
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 2 ; number of digits to print
 .again:
 rol dl, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again
 ret
 ; *

 ; **
 time.doc:
 db 'Displays the time from the cmos real time clock', 13, 10
 db 'eg: time /displays current time', 13, 10
 db 'See also: clock', 13, 10
 dw \\$-time.doc
 time:
 dw dothexbyte ; link
 db 4, 'time'
 time.x:

 mov al, 0x0A ; check if an update in progress ? necessary?
 out 0x70, al ; address reg
 in al, 0x71 ; get data from cmos data reg
 test al, 0x80 ; is high bit set?
 
 mov al, 0x04 ; select Hour
 out 0x70, al ; address rtc minute register
 in al, 0x71 ; get data from cmos data reg
 push ax
 call dothexbyte.x ; print bcd hour 
 mov al, ':'
 mov ah, 0x0E
 int 0x10

 mov al, 0x02 ; select minutes
 out 0x70, al ; address rtc minute register
 in al, 0x71 ; get data from cmos data reg
 push ax
 call dothexbyte.x ; print minutes
 mov al, ':'
 mov ah, 0x0E
 int 0x10

 mov al, 0x00 ; select seconds
 out 0x70, al ; address reg
 in al, 0x71 ; get data from data reg
 xor ah, ah ; set ah := 0
 push ax ; put seconds on stack
 call dothexbyte.x ; print number of seconds

 ret
 ; *

 start:

 mov ax, cs
 mov ds, ax
 mov es, ax
 push 0x140F ; show clock at row 20, column 15
 call time.x

 jmp \\$ ; loop forever or hlt ?
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

 
 * get real time clock info from the cmos chip, working code
 ------------------
 BITS 16
 [ORG 0]
 jmp 07C0h:start 
 
 selectRegister equ 0x70 ; cmos rtc select register 
 dataRegister equ 0x71 ; cmos rtc data register 

 hextable db "0123456789ABCDEF" ; digit translation table
 dothexbyte.doc:
 db 'displays a 1 byte number in hex format', 13, 10
 db 'eg: 255 .hexbyte /displays FF', 13, 10
 db 'Stack: (n  )', 13, 10
 db 'See also .hex .s . .byte ...', 13, 10
 dw \\$-dothexbyte.doc
 dothexbyte:
 dw 0
 db 8, '.hexbyte'
 dothexbyte.x:
 pop bx ; fn return address
 pop dx ; the number to print (parameter on stack)
 push bx ; restore return address
 mov ah, 0x0E ; bios teletype function 
 mov bx, hextable ; translation table
 mov cx, 2 ; number of digits to print
 .again:
 rol dl, 4 ; rotate left 4 bits (print highest first)
 mov al, dl ; bits to convert to hex digit
 and al, 0x0F ; only lower 4 bits relevant
 xlatb ; replace al with hex digit in translation table
 int 10H ; invoke bios print function
 loop .again
 ret


 sec db 0 ; saved seconds
 position dw 0 ; row, column where clock will show 
 ; ie high byte= row, low byte = column

 clock.doc:
 db 'Displays updating time and date from the cmos real time clock', 13, 10
 db 'at x,y row column position ', 13, 10
 db 'eg: 0902 clock /displays clock at row 9, col 2', 13, 10
 dw \\$-clock.doc
 clock:
 dw 0
 db 5, 'clock'
 clock.x:

 pop dx ; juggle return fn
 pop word [position] ; parameter where clock will be shown
 push dx

 .updating:
 mov al, 0x0A ; check if an update in progress 
 out 0x70, al ; address reg
 in al, 0x71 ; get data from cmos data reg
 test al, 0x80 ; is high bit set?

 ; push ax
 ; call dothex.x
 ; jne .updating ; makes an infinite loop in qemu

 mov ah, 02h ; x86 bios: set cursor position specified in dx
 mov dx, [position] 
 int 10h ; bios interrupt 

 mov al, 0x00 ; select seconds
 out 0x70, al ; address reg
 in al, 0x71 ; get data from data reg
 cmp al, [sec] ; only print if seconds have changed
 je .updating
 mov [sec], al ; save current seconds

 mov al, 0x04 ; select Hour
 out 0x70, al ; address rtc minute register
 in al, 0x71 ; get data from cmos data reg
 ; print hour in al in hex format
 push ax
 call dothexbyte.x
 mov al, ':'
 mov ah, 0x0E
 int 0x10

 mov al, 0x02 ; select minutes
 out 0x70, al ; address rtc minute register
 in al, 0x71 ; get data from cmos data reg
 push ax
 call dothexbyte.x ; print minutes
 mov al, ':'
 mov ah, 0x0E
 int 0x10

 mov al, [sec] ; get saved seconds
 xor ah, ah ; set ah := 0
 push ax ; put seconds on stack
 call dothexbyte.x ; print number of seconds

 mov al, ' ' ; print a space between time and date
 mov ah, 0x0E
 int 0x10

 mov al, 0x07 ; Day of month
 out 0x70, al ; cmos select reg
 in al, 0x71 ; cmos data reg 
 push ax
 call dothexbyte.x ; print day of month
 mov al, '/'
 mov ah, 0x0E
 int 0x10

 mov al, 0x08 ; Month
 out 0x70, al ; cmos select reg
 in al, 0x71 ; cmos data reg 
 push ax
 call dothexbyte.x ; print month 
 mov al, '/'
 mov ah, 0x0E
 int 0x10

 mov al, 0x09 ; Year
 out 0x70, al ; cmos select reg
 in al, 0x71 ; cmos data reg 
 push ax
 call dothexbyte.x ; print year in hex
 mov al, ' '
 mov ah, 0x0E
 int 0x10

 mov ah, 0x01 ; x86 bios check if keypress available
 int 0x16 
 jz .updating ; loop if no keypress
 .exit:
 ret

 start:

 mov ax, cs
 mov ds, ax
 mov es, ax
 push 0x140F ; show clock at row 20, column 15
 call clock.x

 jmp \\$ ; loop forever or hlt ?
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

SHUTDOWN COMPUTER APM

 \url{http://wiki.osdev.org/Shutdown}

 \url{http://stackoverflow.com/questions/21463908/x86-instructions-to-power-off-computer-in-real-mode}
 \url{http://stackoverflow.com/questions/678458/shutdown-the-computer-using-assembly}
 \url{http://stackoverflow.com/questions/3145569/how-to-power-down-the-computer-from-a-freestanding-environment}

 https://github.com/cirosantilli/x86-bare-metal-examples/blob/master/apm\\_shutdown.S
 good gas example of shutting down

TIME AND TIMERS AND PIT

 https://github.com/cirosantilli/x86-bare-metal-examples/blob/9a24f92f36a45abb3f8c37aafc0c3ee9b15563ab/in\\_pit.S
 complete example of pit usage in assembler

 Pit is the programmable interrupt timer

 INT 08H is a timer interrupt generated I think every 42milliseconds
 but see below for a easier way to time code

 INT 1Ah / AH = 00h - get system time.
 return:
 CX:DX = number of clock ticks since midnight.

 You can use interrupt 1Ah / function 00h (GET SYSTEM TIME) to get the number of clock ticks (1/18.2 s) since midnight in CX:DX.

 If you don’t actually need to use the timer tick interrupt directly,
 there is a much easier alternative. You could code the program to
 poll the count of the timer ticks since midnight that the BIOS
 maintains in the DWORD at offset address 6Ch in the BIOS data area,
 located at segment address 40h. The polling loop could compare the
 count to the value saved on the previous loop, and if the count had
 changed, indicating that a timer tick had occurred, save the count
 (for use in the next loop), call Interrupt 1Ah, etc, and continue
 looping.

CLOCK TICKS ....

 INT 1Ah / AH = 00h - get system time.
 return:
 CX:DX = number of clock ticks since midnight.

 AL = midnight counter, advanced each time midnight passes.
 notes:
 there are approximately 18.20648 clock ticks per second,
 and 1800B0h per 24 hours. 
 AL is not set by the emulator. Back to Top 


 There is some problem with the code below...
 But it would be handy for getting random numbers

 * wait for 1 second 
 ---------------
 [org 0]
 jmp 07C0h:start

 ticks.doc:
 db 'Clock ticks since midnight (18.2 ticks per seconds)'
 dw \\$-ticks.doc
 ticks:
 dw 0 ; link to previous
 db 5, 'ticks'
 ticks.x:
 mov ah, 00h
 int 1Ah
 ; result in cx:dx
 ret 

 start:
 mov ax, cs
 mov ds, ax
 mov es, ax
 ;mov cx, 10

 .next:
 ;push cx ; save counter 
 ;call ticks.x
 mov ah, 00h
 int 1Ah ; this interrupt is causing some prob, no stack???
 mov ax, dx
 mov bl, 10
 div bl ; al:=quotient, ah:=remainder
 mov al, ah ; get ready to print last digit of ticks
 add al, '0' ; convert to digit
 ;mov al, 'Z' ; convert to digit
 mov ah, 0eH ; bios print char in al
 int 10H ; print it

 ;pop cx ; restore counter
 ;loop .next

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

TIMING CODE ....


 * time some code 
 -------------
 [ORG 0]
 jmp 07C0h:start ; start label in segment 07C0

 tick.doc:
 db ' Pushes number of clock ticks on stack'
 dw \\$-tick.doc
 tick:
 dw 0 ; link to previous dict word or null
 db 4, 'tick' ; forth counted name
 tick.x:
 mov ah, 00h ; interrupts to get system time 
 int 1Ah ; cx:dx now holds number of clock ticks since midnight 
 pop bx ; balance return pointer
 push dx ; push result clock ticks on stack
 push bx ; restore return pointer

 .exit:
 ret

 start:
 mov ax, cs ; the code segment is already correct (?!)
 mov ds, ax ; set up data and extended segments
 mov es, ax ; print with stosw

 call tick.x
 mov cx, -1 ; loop lots of times and time it
 .again:
 push cx
 pop cx
 loop .again

 call tick.x
 pop ax ; last timer
 pop bx ; first timer
 sub ax, bx ; last - first is time taken to do code
 mov ah, 0x0e ; print char func
 add al, '0' ; convert to asci digit
 int 10h

 jmp \\$ ; loop forever
 times 510-(\\$-\\$\\$) db 0 
 dw 0xAA55 
 ,,,
 
WAITING AND DOING NOTHING FOR SOME TIME ....

 * wait one second (1000000 microseconds)
 ----------
 mov cx, 0fh
 mov dx, 4240h
 mov ah, 86h
 int 15h
 ,,,,

 * wait for 1 second 
 ---------------
 [org 0]
 jmp 07C0h:start

 sleep.doc:
 db 'Just waits for some time'
 dw \\$-sleep.doc
 sleep:
 dw 0 ; link to previous
 db 4, 'wait'
 sleep.x:
 mov cx, 0fh ; cx:dx microseconds to wait
 mov dx, 4240h
 mov ah, 86h
 int 15h
 ret 

 start:

 mov ax, cs
 mov ds, ax
 mov es, ax

 mov cx, 10
 .next:
 mov al, 'Z' ; print something
 mov ah, 0eH ; teletype AL bios function
 int 10H
 call sleep.x
 loop .next

 jmp \\$ ; halt here
 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature
 ,,,

BIOS

 In real mode, the bios provides all sorts of useful functions
 for reading and writing and displaying
 
 * get information about the current bios
 ----------------------
 mov ah, C0h
 int 15h
 ;this returns a table of information
 ,,,, GOTCHAS

 * watch out, dividend is only 1 byte! so AH? is undefined!
 ------------
 mov AX, [dividend]
 jmp \\$
 dividend db 54
 ,,,

FASM
 
 Fasm is the 'free assembler' and appears to be less actively
 maintained than 'nasm'

ASSEMBLY AND NASM 

 Nasm is the 'netwide assembler' and appears actively maintained.
 Assembly language programming has the reputation as a egregious
 wrongheaded persuit, the domain of casino card counters and their
 ilk. But its really not that bad.

LABELS ....

 labels may be local (starting with a dot) or non local.
 local ones for some reason need a non local one before them.

 * error, nasm doesnt like this
 -----
 .again
 jmp .again
 ,,,,

 * ok nasm is happy 
 -----
 start:
 .again
 jmp .again
 ,,,,

ASSEMBLING AND ORGANISING WITH NASM ....

 The program below seems to work even without initializing
 the stack, which is odd, since a procedure needs to use it.

 * a program which includes a proceedure in a separate file
 --------
 jmp start
 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096
 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov al, 0xEE 
 mov bl, 2 
 call printi8
 hang: jmp hang

 \\%include 'printi8.asm'
 times 510-(\\$-\\$\\$) db 0 ; Fill the file with 0's
 dw 0AA55h ; End the file with AA55
 ,,,

VARIABLES ....

 Variables in assembly language a just buffers of initialised
 or reserved but uninitialised data. No more no less. The rest
 is up to you

 * move the value in the DL register into memory 
 ------------
 jmp start
 drive db 0 
 start:
 mov ax, 07C0h ; first set data-segment=code-segment
 mov ds, ax ; so that [drive] points to where it should
 mov [drive], dl
 mov ah, 0eh
 mov al, [drive] ; prints the value as an ascii character
 int 10h ; not useful but better than nothing
 hang: jmp hang

 ,,,

 * print the first two characters of a string
 ------------
 jmp start
 message db 'hello!'
 start:
 mov ah, 0eh
 mov al, [message] 
 int 10h
 mov al, [message+1]
 int 10h
 hang: jmp hang

 ,,,

BASH AND ASM

 * A couple of aliases to make it easier to compile and run
 simulated bootable operating system in qemu
 >> alias cos='nasm -fbin -o os.bin os.asm; rm os.flp; sudo mkdosfs -C os.flp 1440; dd status=noxfer
 >> alias os='qemu-system-i386 -fda os.flp'

VIM AND ASM

 Vim can be used to compile and run bootable assembly code
 with qemu. Use control alt F to exit full screen in qemu
 Use left control alt to get the mouse back from qemu

 * a command to write the next assembly proceedure to its own file 
 >> command! -nargs=1 Asp /\\^ *[a-z0-9]\\$\\backslash\\$+:/,/\\^ *ret *\\$/w <args>.asm

 * map the key sequence ';as' to compile the whole file with nasm 
 >> map ;as :!nasm -f bin \\% -o \\%:r.bin<cr>

 In the examples below, the complete assembly program is 
 supposed to be within 2 'markers' within a document. The markers
 are '---' on a line by itself and ',,,' on a line by itself. These
 2 markers mark the beginning and end of the assembly program
 within the document.

 * just compile an assembly program within a document 
 >> map ;cc :?\\^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; nasm -fbin -o test.bin test.asm; 
 
 * compile and run a fragment of boot assembly with nasm and qemu, fullscreen
 >> map ;aa :?\\^ *---?+1,/,,,/-1w ! ( cat - ) > test.asm; nasm -fbin -o test.bin test.asm; mkdosfs -C test.flp 1440; dd status=noxfer conv=notrunc if=test.bin of=test.flp; qemu-system-i386 -full-screen -noframe -fda test.flp
 
 * compile and run a fragment inserting bootload code
 >> map ,B :?\\^ *---?+1,/,,,/-1w ! ( cat - ) \\$\\backslash\\$| sed '/\\$\\backslash\\$[bootload\\$\\backslash\\$]/r bootload.asm' > test.asm; nasm -fbin -o test.bin test.asm; mkdosfs -C test.flp 1440; dd status=noxfer conv=notrunc if=test.bin of=test.flp; qemu-system-i386 -noframe -fda test.flp
 
 ; eg: sed '/[bootload]/r bootload.txt' 
 * compile and run whole file with qemu 
 >> map ,f :!nasm -fbin -o \\%:r.bin \\%; sudo mkdosfs -C test.flp 1440; dd status=noxfer conv=notrunc if=\\%:r.bin of=test.flp; qemu-system-i386 -no-frame -fda test.flp

 * build fragments and compile
 >> map ,B :!./build.pl > all.asm; nasm -fbin -o all.bin all.asm; sudo mkdosfs -C test.flp 1440; dd status=noxfer conv=notrunc if=all.bin of=test.flp; qemu-system-i386 -no-frame -fda test.flp

 * no qemu window decorations, stop with control-c
 >> qemu-system-i386 -no-frame test.flp

 * qemu fullscreen, stop with control-c
 >> qemu-system-i386 -full-screen test.flp

 The command line above may have a problem if the test.flp file
 already exists and is no good since mkdosfs will not overwrite it.

 The following is useful for determining how much space is 
 left within a boot file (which is limited to 512 bytes)

 * see how big a compiled file is without 512 byte padding 
 >> map ;bb :?\\^ *---?+1,/,,,/-1w ! ( sed -n '/times/\\$\\backslash\\$!p' ) > test.asm; nasm -fbin -o test.bin test.asm; ls -la
 
 
DOCUMENT NOTES

 This section contains some meta information about the 
 document.

DANIELS NASM BOOT TIPS xxx

 \url{http://home.swipnet.se/smaffy/asm/info/nasmBoot.txt}
 author: Daniel Marjamäki (daniel.marjamaki@home.se)

 The basics
 ----------
 These are the rules that you must follow:
 - The BIOS will load your bootloader at address 07C00h.
 Sadly, the segment and offset varies.
 - Bootstraps must be compiled as plain binary files.
 - The filesize for the plain binary file must be 512
 bytes.
 - The file must end with AA55h.


A minimal bootstrap
-------------------
This bootstrap just hangs:

 ; HANG.ASM
 ; A minimal bootstrap

 hang: ; Hang!
 jmp hang

 times 510-(\\$-\\$\\$) db 0 ; Fill the file with 0's
 dw 0AA55h ; End the file with AA55

 note: 
 \\$ means the current memory offset in
 the assembled machine code.
 \\$\\$ the beginning memory offset 

The last instruction puts AA55 at the end of the file. 

To compile the bootstrap, use this command:
 nasm hang.asm -o hang.bin

If you want to test the bootstrap, you must first put it on the first
sector on a floppy disk. You can for example use 'dd' or 'rawrite'.
When the bootstrap is on the floppy, test it by restarting your
computer with the floppy inserted. The computer should hang then.


The memory problem
------------------
 There is a memory problem.
 As I've written bootstraps are always loaded to address
 07C00. We don't know what segment and offset the BIOS has
 put us in. The segment can be anything between 0000 and 
 07C0. This is a problem when we want to use variables.
 The solution is simple. Begin your bootstrap by jumping
 to your bootstrap, but jump to a known segment.

Here is an example:

 ; JUMP.ASM
 ; Make a jump and then hang

 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 start:
 ; Update the segment registers
 mov ax, cs
 mov ds, ax
 mov es, ax

 hang: ; Hang!
 jmp hang

 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h

If you compile and test this bootstrap, there will be no
visible difference to the minimal bootstrap presented
earlier. The computer will just hang.


Solutions to the exercises
--------------------------

1. 

 ; 1.ASM
 ; Print "====" on the screen and hang

 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 start:
 ; Update the segment registers
 mov ax, cs
 mov ds, ax
 mov es, ax

 mov ah, 9 ; Print "===="
 mov al, '=' ;
 mov bx, 7 ;
 mov cx, 4 ;
 int 10h ;

 hang: ; Hang!
 jmp hang

 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h


2. 

 ; 2.ASM
 ; Print "Hello Cyberspace!" on the screen and hang

 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 ; Declare the string that will be printed
 msg db 'Hello Cyberspace!'


 start:
 ; Update the segment registers
 mov ax, cs
 mov ds, ax
 mov es, ax


 mov si, msg ; Print msg
 print:
 lodsb ; AL=memory contents at DS:SI

 cmp al, 0 ; If AL=0 then hang
 je hang

 mov ah, 0Eh ; Print AL
 mov bx, 7
 int 10h

 jmp print ; Print next character


 hang: ; Hang!
 jmp hang


 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h


3.

 ; 3.ASM
 ; Load a program off the disk and jump to it

 ; Tell the compiler that this is offset 0.
 ; It isn't offset 0, but it will be after the jump.
 [ORG 0]

 jmp 07C0h:start ; Goto segment 07C0

 start:
 ; Update the segment registers
 mov ax, cs
 mov ds, ax
 mov es, ax


 reset: ; Reset the floppy drive
 mov ax, 0 ;
 mov dl, 0 ; Drive=0 (=A)
 int 13h ;
 jc reset ; ERROR => reset again


 read:
 mov ax, 1000h ; ES:BX = 1000:0000
 mov es, ax ;
 mov bx, 0 ;

 mov ah, 2 ; Load disk data to ES:BX
 mov al, 5 ; Load 5 sectors
 mov ch, 0 ; Cylinder=0
 mov cl, 2 ; Sector=2
 mov dh, 0 ; Head=0
 mov dl, 0 ; Drive=0
 int 13h ; Read!

 jc read ; ERROR => Try again


 jmp 1000h:0000 ; Jump to the program


 times 510-(\\$-\\$\\$) db 0
 dw 0AA55h



This is a small loadable program.

 ; PROG.ASM

 mov ah, 9
 mov al, '='
 mov bx, 7
 mov cx, 10
 int 10h

 hang:
 jmp hang


This program creates a disk image file that contains both
the bootstrap and the small loadable program.

 ; IMAGE.ASM
 ; Disk image

 \\%include '3.asm'
 \\%include 'prog.asm'




MIKEOS SIMPLE OS GUIDE

How to write a simple operating system

 This document shows you how to write and build your first operating
 system in x86 assembly language. It explains what you need, the
 fundamentals of the PC boot process and assembly language, and how to
 take it further. The resulting OS will be very small (fitting into a
 bootloader) and have very few features, but it's a starting point for
 you to explore further.

 After you have read the guide, see [6]the MikeOS project for a bigger
 x86 assembly language OS that you can explore to expand your skills.

Requirements

 Prior programming experience is essential. If you've done some coding
 in a high-level language like PHP or Java, that's good, but ideally
 you'll have some knowledge of a lower-level language like C, especially
 on the subject of memory and pointers.

 For this guide we're using Linux. OS development is certainly possible
 on Windows, but it's so much easier on Linux as you can get a complete
 development toolchain in a few mouse-clicks/commands. Linux is also
 really good for making floppy disk and CD-ROM images - you don't need
 to install loads of fiddly programs.

 Installing Linux is very easy thesedays; grab Ubuntu and install it in
 VMware or VirtualBox if you don't want to dual-boot. When you're in
 Ubuntu, get all the tools you need to follow this guide by entering
 this in a terminal window:

sudo apt-get install build-essential qemu nasm

 This gets you the development toolchain (compiler etc.), QEMU PC
 emulator and the NASM assembler, which converts assembly language into
 raw machine code executable files.

PC PRIMER ....

 If you're writing an OS for x86 PCs (the best choice, due to the huge
 amount of documentation available), you'll need to understand the
 basics of how a PC starts up. Fortunately, you don't need to dwell on
 complicated subjects such as graphics drivers and network protocols, as
 you'll be focusing on the essential parts first.

 When a PC is powered-up, it starts executing the BIOS (Basic
 Input/Output System), which is essentially a mini-OS built into the
 system. It performs a few hardware tests (eg memory checks) and
 typically spurts out a graphic (eg Dell logo) or diagnostic text to the
 screen. Then, when it's done, it starts to load your operating system
 from any media it can find. Many PCs jump to the hard drive and start
 executing code they find in the Master Boot Record (MBR), a 512-byte
 section at the start of the hard drive; some try to find executable
 code on a floppy disk (boot sector) or CD-ROM.

 This all depends on the boot order - you can normally specify it in the
 BIOS options screen. The BIOS loads 512 bytes from the chosen media
 into its memory, and begins executing it. This is the bootloader, the
 small program that then loads the main OS kernel or a larger boot
 program (eg GRUB/LILO for Linux systems). This 512 byte bootloader has
 two special numbers at the end to tell the OS that it's a boot sector -
 we'll cover that later.

 Note that PCs have an interesting feature for booting. Historically,
 most PCs had a floppy drive, so the BIOS was configured to boot from
 that device. Today, however, many PCs don't have a floppy drive - only
 a CD-ROM - so a hack was developed to cater for this. When you're
 booting from a CD-ROM, it can emulate a floppy disk; the BIOS reads the
 CD-ROM drive, loads in a chunk of data, and executes it as if it was a
 floppy disk. This is incredibly useful for us OS developers, as we can
 make floppy disk versions of our OS, but still boot it on CD-only
 machines. (Floppy disks are really easy to work with, whereas CD-ROM
 filesystems are much more complicated.)

 So, to recap, the boot process is:
 1. Power on: the PC starts up and begins executing the BIOS code.
 2. The BIOS looks for various media such as a floppy disk or hard
 drive.
 3. The BIOS loads a 512 byte boot sector from the specified media and
 begins executing it.
 4. Those 512 bytes then go on to load the OS itself, or a more complex
 bootloader.

 For MikeOS, we have the 512-byte bootloader, which we
 write to a floppy disk image file (a virtual floppy). We
 can then inject that floppy image into a CD, for PCs that
 only have CD-ROM drives. Either way, the BIOS loads it as
 if it was on a floppy, and starts executing it. We have
 control of the system!

Assembly language primer

 Most modern operating systems are written in C/C++. That's
 very useful when portability and code-maintainability are
 crucial, but it adds an extra layer of complexity to the
 proceedings. For your very first OS, you're better off
 sticking with assembly language, as used in MikeOS. It's
 more verbose and non-portable, but you don't have to worry
 about compilers and linkers. Besides, you need a bit of
 assembly to kick-start any OS.

 Assembly language (or colloquially "asm") is a textual way
 of representing the instructions that a CPU executes. For
 instance, an instruction to move some memory in the CPU
 may be 11001001 01101110 - but that's hardly memorable! So
 assembly provides mnemonics to substitute for these
 instructions, such as mov ax, 30. They correlate directly
 with machine-code CPU instructions, but without the
 meaningless binary numbers.

 Like most programming languages, assembly is a list of
 instructions followed in order. You can jump around
 between various places and set up subroutines/functions,
 but it's much more minimal than C\\# and friends. You can't
 just print "Hello world" to the screen - the CPU has no
 concept of what a screen is! Instead, you work with
 memory, manipulating chunks of RAM, performing arithmetic
 on them and putting the results in the right place. Sounds
 scary? It's a bit alien at first, but it's not hard to
 grasp.

 At the assembly language level, there is no such thing as
 variables in the high-level language sense. What you do
 have, however, is a set of registers, which are on-CPU
 memory stores. You can put numbers into these registers
 and perform calculations on them. In 16-bit mode, these
 registers can hold numbers between 0 and 65535. Here's a
 list of the fundamental registers on a typical x86 CPU:

 AX, BX, CX, DX General-purpose registers for storing
 numbers that you're using. For instance, you may use AX to
 store the character that has been pressed on the keyboard,
 while using CX to act as a counter in a loop. (Note: these
 16-bit registers can be split into 8-bit registers such as
 AH/AL, BH/BL etc.) SI, DI Source and destination data
 index registers. These point to places in memory for
 retrieving and storing data. SP The Stack Pointer
 (explained in a moment). IP (sometimes CP) The
 Instruction/Code Pointer. This contains the location in
 memory of the instruction being executed. When an
 instruction has finished, it is incremented and moves on
 to the next instruction. You can change the contents of
 this register to move around in your code.

 So you can use these registers to store numbers as you
 work - a bit like variables, but they're much more fixed
 in size and purpose. There are a few others, notably
 segment registers. Due to limitations in old PCs, memory
 was handled in 64K chunks called segments. This is a
 really messy subject, but thankfully you don't have to
 worry about it - for the time being, your OS will be less
 than a kilobyte anyway! In MikeOS, we limit ourselves to a
 single 64K segment so that we don't have to mess around
 with segment registers.

 The stack is an area of your main RAM used for storing
 temporary information. It's called a stack because numbers
 are stacked one-on-top of another. Imagine a Pringles
 tube: if you put in a playing card, an iPod Shuffle and a
 beermat, you'll pull them out in the reverse order
 (beermat, then iPod, and finally playing card). It's the
 same with numbers: if you push the numbers 5, 7 and 15
 onto the stack, you will pop them out as 15 first, then 7,
 and lastly 5. In assembly, you can push registers onto the
 stack and pop them out later - it's useful when you want
 to store temporarily the value of a register while you use
 that register for something else.

 PC memory can be viewed as a linear line of pigeon-holes
 ranging from byte 0 to whatever you have installed
 (millions of bytes on modern machines). At byte number
 53,634,246 in your RAM, for instance, you may have your
 web browser code to view this document. But whereas we
 humans count in powers of 10 (10, 100, 1000 etc. -
 decimal), computers are better off with powers of two
 (because they're based on binary). So we use hexadecimal,
 which is base 16, as a way of representing numbers. See
 this chart to understand: Decimal 0 1 2 3 4 5 6 7 8 9
 10 11 12 13 14 15 16 17 18 19 20 Hexadecimal 0 1 2 3 4 5 6
 7 8 9 A B C D E F 10 11 12 13 14

 As you can see, whereas our normal decimal system uses 0 -
 9, hexadecimal uses 0 - F in counting. It's a bit weird at
 first, but you'll get the hang of it. In assembly
 programming, we identify hexadecimal (hex) numbers by
 tagging a 'h' onto the end - so 0Ah is hex for the number
 10. (You can also denote hexadecimal in assembly by
 prefixing the number with 0x - for instance, 0x0A.)

 Let's finish off with a few common assembly instructions.
 These move memory around, compare them and perform
 calculations. They're the building blocks of your OS -
 there are hundreds of instructions, but you don't have to
 memorise them all, because the most important handful are
 used 90\\% of the time. mov Copies memory from one location
 or register to another. For instance, mov ax, 30 places
 the number 30 into the AX register. Using square brackets,
 you can get the number at the memory location pointed to
 by the register. For instance, if BX contains 80, then mov
 ax, [bx] means "get the number in memory location 80, and
 put it into AX". You can move numbers between registers
 too: mov bx, cx. add / sub Adds a number to a register.
 add ax, FFh adds FF in hexadecimal (255 in our normal
 decimal) to the AX register. You can use sub in the same
 way: sub dx, 50. cmp Compares a register with a number.
 cmp cx, 12 compares the CX register with the number 12. It
 then updates a special register on the CPU called FLAGS -
 a special register that contains information about the
 last operation. In this case, if the number 12 is bigger
 than the value in CX, it generates a negative result, and
 notes that negative in the FLAGS register. We can use this
 in the following instructions... jmp / jg / jl... Jump to
 a different part of the code. jmp label jumps (GOTOs) to
 the part of our source code where we have label: written.
 But there's more - you can jump conditionally, based on
 the CPU flags set in the previous command. For instance,
 if a cmp instruction determined that a register held a
 smaller value than the one with which it was compared, you
 can act on that with jl label (jump if less-than to
 label). Similarly, jge label jumps to 'label' in the code
 if the value in the cmp was greater-than or equal to its
 compared number. int Interrupt the program and jump to a
 specified place in memory. Operating systems set up
 interrupts which are analogous to subroutines in
 high-level languages. For instance, in MS-DOS, the 21h
 interrupt provides DOS services (eg as opening a file).
 Typically, you put a value in the AX register, then call
 an interrupt and wait for a result (passed back in a
 register too). When you're writing an OS from scratch, you
 can call the BIOS with int 10h, int 13h, int 14h or int
 16h to perform tasks like printing strings, reading
 sectors from a floppy disk etc.

 Let's look at some of these instructions in a little more detail.
 Consider the following code snippet:
 mov bx, 1000h
 mov ax, [bx]
 cmp ax, 50
 jge label
 ...

label:
 mov ax, 10

 In the first instruction, we move the number 1000h into the BX
 register. Then, in the second instruction, we store in AX whatever is
 in the memory location pointed to by BX. This is what the [bx] means:
 if we just did mov ax, bx it'd simply copy the number 1000h into the AX
 register. But by using square brackets, we're saying: don't just copy
 the contents of BX into AX, but copy the contents of the memory address
 to which BX points. Given that BX contains 1000h, this instruction
 says: find whatever is at memory location 1000h, and put it into AX.

 So, if the byte of memory at location 1000h contains 37, then that
 number 37 will be put into the AX register via our second instruction.
 Next up, we use cmp to compare the number in AX with the number 50 (the
 decimal number 50 - we didn't suffix it with 'h'). The following jge
 instruction acts on the cmp comparison, which has set the FLAGS
 register as described earlier. The jge label says: if the result from
 the previous comparison is greater than or equal, jump to the part of
 the code denoted by label:. So if the number in AX is greater than or
 equal to 50, execution jumps to label:. If not, execution continues at
 the '...' stage.

 One last thing: you can insert data into a program with the db (define
 byte) directive. For instance, this defines a series of bytes with the
 number zero at the end, representing a string:
 mylabel: db 'Message here', 0

 In our assembly code, we know that a string of characters, terminated
 by a zero, can be found at the mylabel: position. We could also set up
 single byte to use somewhat like a variable:
 foo: db 0

 Now foo: points at a single byte in the code, which in the case of
 MikeOS will be writable as the OS is copied completely to RAM. So you
 could have this instruction:
 mov byte al, [foo]

 This moves the byte pointed to by foo into the AL register.

 That's the essentials of x86 PC assembly language, and enough to get
 you started. When writing an OS, though, you'll need to learn much more
 as you progress, so see the [7]Resources section for links to more
 in-depth assembly tutorials.
 \\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_

Your first OS

 Now you're ready to write your first operating system kernel! Of
 course, this is going to be extremely bare-bones, just a 512-byte boot
 sector as described earlier, but it's a starting point for you to
 expand further. Paste the following code into a file called myfirst.asm
 and save it into your home directory - this is the source code to your
 first OS.

 BITS 16

 start:
 mov ax, 07C0h ; Set up 4K stack space after this bootloader
 add ax, 288 ; (4096 + 512) / 16 bytes per paragraph
 mov ss, ax
 mov sp, 4096

 mov ax, 07C0h ; Set data segment to where we're loaded
 mov ds, ax

 mov si, text\\_string ; Put string position into SI
 call print\\_string ; Call our string-printing routine

 jmp \\$ ; Jump here - infinite loop!


 text\\_string db 'This is my cool new OS!', 0


 print\\_string: ; Routine: output string in SI to screen
 mov ah, 0Eh ; int 10h 'print char' function

 .repeat:
 lodsb ; Get character from string
 cmp al, 0
 je .done ; If char is zero, end of string
 int 10h ; Otherwise, print it
 jmp .repeat

 .done:
 ret

 times 510-(\\$-\\$\\$) db 0 ; Pad remainder of boot sector with 0s
 dw 0xAA55 ; The standard PC boot signature

 Let's step through this. The BITS 16 line isn't an x86 CPU instruction;
 it just tells the NASM assembler that we're working in 16-bit mode.
 NASM can then translate the following instructions into raw x86 binary.
 Then we have the start: label, which isn't strictly needed as execution
 begins right at the start of the file anyway, but it's a good marker.
 From here onwards, note that the semicolon (;) character is used to
 denote non-executable text comments - we can put anything there.

 The following six lines of code aren't really of interest to us - they
 simply set up the segment registers so that the stack pointer (SP)
 knows where our handy stack of temporary data is, and where the data
 segment (DS) is located. As mentioned, segments are a hideously messy
 way of handling memory from the old 16-bit days, but we just set up the
 segment registers and forget about them. (The references to 07C0h are
 the equivalent segment location at which the BIOS loads our code, so we
 start from there.)

 The next part is where the fun happens. The mov si, text\\_string line
 says: copy the location of the text string below into the SI register.
 Simple enough! Then we use call, which is like a GOSUB in BASIC or a
 function call in C. It means: jump to the specified section of code,
 but prepare to come back here when we're done.

 How does the code know how to do that? Well, when we use a call
 instruction, the CPU increments the position of the IP (Instruction
 Pointer) register and pushes it onto the stack. You may recall from the
 previous explanation of the stack that it's a last-in first-out memory
 storage mechanism. All that business with the stack pointer (SP) and
 stack segment (SS) at the start cleared a space for the stack, so that
 we can drop temporary numbers there without overwriting our code.

 So, the call print\\_string says: jump to the print\\_string routine, but
 push the location of the next instruction onto the stack, so we can pop
 it off later and resume execution here. Execution has jumped over to
 print\\_string: - this routine uses the BIOS to output text to the
 screen. First we put 0Eh into the AH register (the upper byte of AX).
 Then we have a lodsb (load string byte) instruction, which retrieves a
 byte of data from the location pointed to by SI, and stores it in AL
 (the lower byte of AX). Next we use cmp to check if that byte is zero -
 if so, it's the end of the string and we quit printing (jump to the
 .done label).

 If it's not zero, we call int 10h (interrupt our code and go to the
 BIOS), which reads the value in the AH register (0Eh) we set up before.
 Ah, says the BIOS - 0Eh in the AH register means "print the character
 in the AL register to the screen!". So the BIOS prints the first
 character in our string, and returns from the int call. We then jump to
 the .repeat label, which starts the process again - lodsb to load the
 next byte from SI (it increments SI each time), see if it's zero and
 decide what to do. The ret at the end of our string-printing routine
 means: "we've finished here - return back to the place where we were
 called by popping the code location from the stack back into the IP
 register".

 So there we have a demonstration of a loop, in a standalone routine.
 You can see that the text\\_string label is alongside a stream of
 characters, which we insert into our OS using db. The text is in
 apostrophes so that NASM knows it's not code, and at the end we have a
 zero to tell our print\\_string routine that we're at the end.

 Let's recap: we start off by setting up the segment registers so that
 our OS knows where the stack pointer and executable code resides. Then
 we point the SI register at a string in our OS binary, and call our
 string-printing routine. This routine scans through the characters
 pointed to by SI and displays them until it finds a zero, at which
 point it returns back into the code that called it. Then the jmp \\$ line
 says: keep jumping to the same line. (The '\\$' in NASM denotes the
 current point of code.) This sets up an infinite loop, so that the
 message is displayed and our OS doesn't try to execute the following
 string!

 The final two lines are interesting. For a PC to recognise a valid
 floppy disk boot sector, it has to be exactly 512 bytes in size and end
 with the numbers AAh and 55h (the boot signature). So the first of
 these lines says: pad out our resulting binary file to be 510 bytes in
 size. Then the second line uses dw (define a word - two bytes)
 containing the aforementioned boot signature. Voila: a 512 byte boot
 file with the correct numbers at the end for the BIOS to recognise.

 Let's build our new OS. In a terminal window, in your home directory,
 enter:

nasm -f bin -o myfirst.bin myfirst.asm

 Here we assemble the code from our text file into a raw binary file of
 machine-code instructions. With the -f bin flag, we tell NASM that we
 want a plain binary file (not a complicated Linux executable - we want
 it as plain as possible!). The -o myfirst.bin part tells NASM to
 generate the resulting binary in a file called myfirst.bin.

 Now we need a virtual floppy disk image to which we can write our
 bootloader-sized kernel. Copy mikeos.flp from the disk\\_images/
 directory of the MikeOS bundle into your home directory, and rename it
 myfirst.flp. Then enter:

dd status=noxfer conv=notrunc if=myfirst.bin of=myfirst.flp

 This uses the 'dd' utility to directly copy our kernel to the first
 sector of the floppy disk image. When it's done, we can boot our new OS
 using the QEMU PC emulator as follows:

qemu -fda myfirst.flp

 And there you are! Your OS will boot up in a virtual PC. If you want to
 use it on a real PC, you can write the floppy disk image to a real
 floppy and boot from it, or generate a CD-ROM ISO image. For the
 latter, make a new directory called cdiso and move the myfirst.flp file
 into it. Then, in your home directory, enter:

mkisofs -o myfirst.iso -b myfirst.flp cdiso/

 This generates a CD-ROM ISO image called myfirst.iso with bootable
 floppy disk emulation, using the virtual floppy disk image from before.
 Now you can burn that ISO to a CD-R and boot your PC from it! (Note
 that you need to burn it as a direct ISO image and not just copy it
 onto a disc.)

 Next you'll want to improve your OS - explore the MikeOS source code to
 get some inspiration. Remember that bootloaders are limited to 512
 bytes, so if you want to do a lot more, you'll need to make your
 bootloader load a separate file from the disk and begin executing it,
 in the same fashion as MikeOS.

Going further

 So, you've now got a very simple bootloader-based operating system
 running. What next? Here are some ideas:
 * Add more routines -- You already have print\\_string in your kernel.
 You could add routines to get strings, move the cursor etc. Search
 the internet for BIOS calls which you can use to achieve these.

 * Load files -- The bootloader is limited to 512 bytes, so you don't
 have much room. You could make the bootloader load subsequent
 sectors on the disk into RAM, and jump to that point to continue
 execution. Or you could read up on FAT12, the filesystem used on
 floppy drives, and implement that. (See
 source/bootload/bootload.asm in the MikeOS .zip for an
 implementation.)


DOCUMENT-NOTES:
 
 \\# this section contains information about the document and
 \\# will not normally be printed.

 \\# A small (16x16) icon image to identify the book
 document-icon:

 \\# A larger image to identify or illustrate the title page
 document-image:

 \\# what sort of document is this
 document-type: book

 \\# in what kind of state (good or bad) is this document 
 document-quality: just begun
 
 document-history:
 @@ 10 nov 2011
 started this booklet after seeing the 'mikeos' site
 and realising that writing a bootable program in realmode
 x86 code should not be that difficult. Also found a good page
 describing how to load a program from a floppy image and
 execute it (to overcome the 512 byte boot sector limit)
 @@ 17 march 2015
 a little bit more work, trying to amplify the forth section
 @@ july 2016
 The mikeos site seems to be unmaintained now, but I have
 made progress. I started writing simple forth words and 
 have progressed to 'find' which is the most complicated 
 so far. My interest is in total minimalism. In 400bytes
 we can have something useful- eg a hex converter.
 * 28 july 2016 
 Finally got a working extensible forth-like system 
 bootloading correctly, with an absolute minimum of 
 words like find, accept, exec, type, count. Just enough
 to enter and execute commands. No compiling words yet.
 * august 2016
 On going work. Building up forth style functions. Thinking
 about token-threaded (opcode) virtual machine, for portability
 Universal naming for functions, eg code.debug.dump with the 
 prefix put in a table header.
 * oct 2016
 a few animations etc.
 * november 2016
 created a separate bootloader file so as not to clutter up
 examples with bootloading code.
 * 24 november 2016
 extensive forth coding this month. created a simple byte
 code system. worked on the "core" forth system (a non-compiling)
 system. discovered a tricky bug where an extra zero was 
 being put on the stack by the interp: word. hence plus and dump
 were not working. realised that mixing the data stack with the
 machine stack is tricky... on the stack the first and last items
 are return function pointers put there by x86 "call" instructions.
 Found another even trickier bug involving the bstack word (removed)
 causing list: to fail ...
 * may 2018
 a lot more work on os.asm which is a forth bytecode booting system.
 colon compiler more or less working.

 \\# who wrote this
 authors: mjbishop
 \\# a short description of the contents, possible used for doc lists
 short-description:
 \\# A computer language which is contained in the document, if any
 code-language: forth, asm, nasm, x86
 \\# the script which will be used to produce html (a webpage)
 make-html: ./book-html.sh
 \\# the script which will produce 'LaTeX' output (for printing, pdf etc)
 make-latex: ./booktolatex.cgi


\end{document}
%end generated latex
