Listh Language Project - Forth is much simpler to implement than Lisp, but this comes with big drawbacks.

ACNews #70
Listh Language Project
 by Alone Coder

Lisp and Forth allow you to quickly set up a programming environment
on a new machine or under a new environment. For this purpose the language itself
made very simple but flexible.

Forth is much simpler to implement than Lisp, but it drags
big disadvantages:
- the function parameters are on the same stack as the function result,
and are inaccessible by name - it’s inconvenient to sort them out;
- if some function is written with an error deep in the stack, then
it will crash the whole program;
- complex mathematical operations cannot be read, cannot be written
parentheses;
- the system does not provide dynamic lists, as in general
dynamic memory;
- apparently you can't pass a function pointer or
short circuit

Lisp also implements the editing environment in-house,
that is, in principle, you can always change it to your own. Moreover
this can be done directly while the program is running.

I decided to eliminate Forth's shortcomings without implementing the most
confusing things in Lisp - contexts, garbage collector and
interpretation of parameters when calling functions. And also avoiding
Lisp's typing of list cells is not a real system
types, and at the same time interferes with efficient storage and execution
program. And as we remember, one of the advantages of Lisp is
that it can edit itself. Thateat according to your mind
the program must be executed directly from the source
text.

My idea was that the basic data structure remains
list, and the parameters and result of the functions are passed to
(pseudo) stack, that is, they do not need to be interpreted when called.
But also:
- functions have the ability to read terms of the source text, then
there is to receive parameters written down from them to the right;
- you can provide for maintaining the stack depth when calling, so that
the default functions returned the old depth (but I haven't yet
began to do so);
- the type of a nested list is determined by its first element.
A term is also a list, because it has a symbolic name, and this
The name must be stored somewhere to be displayed. So as not to
interpret this type in the first element, just write there
handler address of this type: variable, constant, function...
(For built-in language functions there will be specific
handlers.) The second element of the list contains useful data
(the contents of the variable, the address of the function...), and then the name by
one character.

Expressions in such a language would look something like this:
(a + (x))
(a + 0) //operations like "+" read+evaluate their right operand
((x) + a)
(f(x)+a)
(a+f(x))
(fquote (x y z)) //this function does not evaluate its operand
(a b f2) //this function reads operands from the stack

Below is the code of the interpreter and a couple of built-in handlers
functions (in reality there should bemuch more). Basic function
called count. The pseudostack grows upward towards the real one
stack.

        macro PSEUDOPUSHHL
        ld a,l
        ld(bc),a
        inc c
        ld a,h
        ld(bc),a
        inc bc
        endm

        macro PSEUDOPUSHDE
        ld a,e
        ld(bc),a
        inc c
        ld a,d
        ld(bc),a
        inc bc
        endm

        macro PSEUDOPOPHL
        dec bc
        ld a,(bc)
        ld h,a
        dec c
        ld a,(bc)
        ld l,a
        endm

        macro PSEUDOPOPDE
        dec bc
        ld a,(bc)
        ld d,a
        dec c
        ld a,(bc)
        ld e,a
        endm

        macro GETNEXT
;read data in hl
;read pnext in de
        ex de,hl
        ld e,(hl)
        inc l
        ld d,(hl) ;data
        inc l
        ld a,(hl)
        inc l
        ld h,(hl)
        ld l,a ;pnext
        ex de,hl
        endm

count:
;list interpreter
;calls the function at the pointer in de, it goes again to count or
;at endcount
        GETNEXT ;read pfunc in hl, pnext in de
        ld a,(hl)
        inc l
        ld h,(hl)
        ld l,a ;*pfunc (in the function description list the first
                  ;element - address to call)
        jp (hl) ; when calling the asmo function the stack does not move

PushValue:
;asmofunction lying at the beginning of the descriptive list
;variable/constant
;return value from the current list - lies in (de)ex de,hl
        ld e,(hl)
        inc l
        ld d,(hl) ;value
        PSEUDOPUSHDE
endcount:
;called as the last element of the list
        ret ;endcount

Lisp:
;asmofunction lying at the beginning of the function descriptor list
;read value in the current list - lies in (de), this is the address
;list to be fulfilled
        ex de,hl
        ld e,(hl)
        inc l
        ld d,(hl) ;value
        jp count ;calculation of the list where the lisp function is described,
;exit to endcount

plus:
;called from Lisp, so does not contain ret
;the left operand is on the pseudostack
;reads a function/variable/constant/list by pointer
;(and moves the pointer), calculates it, puts the result
;to pseudostack, adds 2 numbers on pseudostack
        GETNEXT ;read plist in hl, pnext in de
         push de ;current pointer
        ex de,hl ;index = pdata
;de = pointer to a function/variable/constant/list, i.e. by
;the essence is always on the list
;if a function/variable/constant, then call ((de)) is enough
;(there is an exit to endcount)
;if a list, then call count (with movement de and exit to
;endcount at the end of the list)
        call count ;called the first term of the list and further along the list
                   ;until endcount
        PSEUDOPOPDE ;now de=right operand
        PSEUDOPOPHL ;now hl=left operand
        add hl,de
         pop de ;current pointer
        PSEUDOPUSHHL
        jp count

_readnext:;called from Lisp, so does not contain ret
;reads the address of the term from the pointer, puts it on the pseudostack (not
;calculating), moves the pointer
        GETNEXT ;read term in hl, pnext in de
        PSEUDOPUSHHL
        jp count

_eval:
;called from Lisp, so does not contain ret
;takes the address of a term from the pseudostack, calculates it, puts
;result on pseudostack
         push de ;current pointer
        PSEUDOPOPDE ;now de = term address
;de = pointer to a function/variable/constant/list, i.e. by
;the essence is always on the list
;if a function/variable/constant, then call ((de)) is enough
;(there is an exit to endcount)
;if a list, then call count (with movement de and exit to
;endcount at the end of the list)
        call count ;called the first term of the list and further along the list
                   ;until endcount
         pop de ;current pointer
        jp count

_POPVAR:
;called from Lisp, so does not contain ret
        GETNEXT ;read pvar in hl, pnext in de
        inc l
        inc l
        ld a,(hl)
        inc l
        ld h,(hl)
        ld l,a ;pvar.next (in the variable description list
;the first element is the address to call, the second is useful data)
         push de
        PSEUDOPOPDE
        ld(hl),e
        inc l
        ld(hl),d
         pop de
        jp count

_DUP:
;called from Lisp, so does not contain ret
        PSEUDOPOPHL
        PSEUDOPOPHL
        PSEUDOPUSHHL
        jp count_SWAP:
;called from Lisp, so does not contain ret
         push de
        PSEUDOPOPDE
        PSEUDOPOPHL
        PSEUDOPUSHDE
         pop de
        PSEUDOPUSHHL
        jp count

        ds (-$)&3 ;ALIGN 4
readnext:
        dw_readnext,$+2,0,$+2
        dw 'r',$+2,'e',$+2,'a',$+2,'d',$+2
        dw 'n',$+2,'e',$+2,'x',$+2,'t',NIL
eval:
        dw_eval,$+2,0,$+2
        dw 'e',$+2,'v',$+2,'a',$+2,'l',NIL
POPVAR:
        dw_POPVAR,$+2,0,$+2
        dw 'P',$+2,'O',$+2,'P',NIL
DUP:
        dw_DUP,$+2,0,$+2
        dw 'D',$+2,'U',$+2,'P',NIL
SWAP:
        dw_SWAP,$+2,0,$+2
        dw 'S',$+2,'W',$+2,'A',$+2,'P',NIL
        ...
freemem:
        dup ($-endmem)/4
        dw 0,$+2
        edup

how we will make cycles and conditions:
(1 2 ifeq (...) (..else..)) - how to transfer the calculated data to its body
parameters? via the stack and global variables?
loop (...) - how to pass the calculated parameters to its body? how
go out? (you can provide a loop-exit hack, but not on the stack
it will be possible to store the output address, because exit may be in
nested parenthesis)
or abandon local variables, will there be only global ones?
and for them in Lisp provide the push and pop commands with the parameter
on the right (instead of push, you can just write the variable name)

whilenz (...) //executes until the last result is NIL (itself
does not remove from the stack)
you can use it to do the usual while:
cond
whilenz(...cond)

editor:
DEFUN"brackets2list_delstring"
(
    eat //"("
    pushnil
    whilez (
        gettoken_del
        isopenbracket ifz (
            string2list_delstring //eats (...)
        )
        ...pushtolist... //so can add )
        isclosebracket ifz //what if there is no closing bracket?
           //or do you immediately enter the entry directly in brackets without entering?
    )
    ...popfromlist...
    ...swaplist...
)
DEFUN "readlist" (readbrackets brackets2list_delstring)
DEFUN "eval_dellist" (DUP eval SWAP dellist)
loop (readlist eval_dellist printlist_dellist)

                             * * *

An obvious drawback is that a program in such a language takes about
at least twice as much as Fort's, precisely because of storage in
list. And the "list" data structure is rarely needed for real
Spectrum tasks, it was chosen in order to
it was possible to edit the program while it was running.

If the project is completed, it will be possible to use it for
programs of infinite size if all pointers are made
distant (3 bytes instead of 2). But for such large programs it is necessary
so that programming is convenient...

Share your thoughts about the article