XXIIVV

Using cons cells and linked lists in Uxntal.

In functional programming languages, a list is the most versatile data type that can be used to store a collection of similar data items. Uxn uses singular opcodes operating on words of equal length, one might come across a problem that is better addressed with routines that operate on ordered lists of items and nested stacks. This 300 bytes implementation of cons cells gives your project that power.

A cons cell is data structure which holds both data and an address to the next cell.

The data slot is known as the CAR, and the address slot is known as the CDR. The purpose is so that ordered lists can be traversed by going from one cell to the next, it also allows one to change the order of cells without manually moving any of the cells' data.

Let us consider the following cons list:

(list cat dog owl)

Also equivalent to the expression:

(cons owl (cons dog (cons cat nil)))

A translation to uxntal assembly would be something like the following, in which POP2 is to get rid of the dangling pointer. This can obviously be made prettier with macros, but we'll keep things straightforward.

nil ;cat cons ;dog cons ;owl cons POP2

Creating a new list

The @alloc routine needs a @memory label to an address with enough space to host the lists, it returns the address of the newly created cell. A list begins with a cons cell whose CAR is a pointer to the cell's data and whose CDR is the next cell. A nil list begins with the nil function pointer.

The @cons routine creates a new cons cell, making the fn its CAR, and the next cell its CDR. It returns the address of the newly created cons cell. @cons is often used to add a single element to the front of a list. This is called consing the element onto the list.

@alloc ( -- cell* )

	[ LIT2 &next :memory ] DUP2 #0004 ADD2 ,&next STR2

JMP2r

@nil ( -- list* )

	alloc ;nil

@cons ( list* fn* -- list* )

	( car ) alloc STH2k STA2
	( cdr ) STH2kr INC2 INC2 STA2
	STH2r

JMP2r

( values ) [
	@cat =dict/cat :nil @dog =dict/dog :nil @bat =dict/bat :nil
	@ant =dict/ant :nil @owl =dict/owl :nil @cow =dict/cow :nil ]

@dict [
	&cat "cat $1 &dog "dog $1 &bat "bat $1
	&ant "ant $1 &owl "owl $1 &cow "cow $1 ]

@memory $4000

Nesting two lists

You can make a list of lists, or nested lists, using the @cons routine with two cons cells from different lists.

nil ;cat cons ;dog cons ;owl cons
nil ;ant cons ;bat cons ;cow cons
nil SWP2 cons SWP2 cons
	echo ( ( owl dog cat ) ( cow bat ant ) )

Joining two lists

Two lists of cons cells can be joined together into one by modifying the last pointer of the second list, and pointing it to the first one.

@last ( list* -- cell* )

	&w
		INC2 INC2 LDA2 LDA2k ;nil NEQ2 ?&w
	INC2 INC2

JMP2r

@join ( list-a* list-b* -- list-b* )

	STH2k last INC2 INC2 STA2 STH2r

JMP2r

Finding the length

This routine will run through a list and return its length.

@length ( list* -- length* )

	LIT2r 0000
	&w
		INC2 INC2 INC2r
		LDA2 LDA2k ;nil NEQ2 ?&w
	POP2 STH2r

JMP2r

Printing a list

Running through a list and its nested lists is a matter of itterating through each cell's CDR until a ;nil pointer is found. The following routine will run recursively. If the list is a series of function pointers, you can modify this into an evaluation routine.

@echo ( list* -- )

	#2818 DEO #2018 DEO
	&w
		LDA2k INC2 INC2 LDA2 ;nil EQU2 ?&value
			( list ) LDA2k echo !&resume
			&value LDA2k LDA2 pstr #2018 DEO
		&resume
		INC2 INC2 LDA2
		LDA2k ;nil NEQ2 ?&w
	POP2
	#2918 DEO #2018 DEO

JMP2r

@pstr ( str* -- )

	&w
		LDAk #18 DEO
		INC2 LDAk ?&w
	POP2

JMP2r