02 мая 2018

                       Проект языка Listh
                        by Alone Coder

Lisp и Forth позволяют быстро развернуть среду программирования
на новой машине или под новым окружением. Для этого сам язык
сделан очень простой, но гибкий.

Forth реализуется значительно проще, чем Lisp, но это тянет
большие недостатки:
- параметры функции лежат в том же стеке, что результат функции,
и недоступны по именам - неудобно их разгребать;
- если какая-то функция написана с ошибкой в глубине стека, то
это свалит всю программу;
- не читаются сложные математические операции, нельзя написать
скобки;
- динамические списки система не предоставляет, как и вообще
динамическую память;
- по-видимому, нельзя передать указатель на функцию или
замыкание.

Lisp также реализует среду редактирования собственными силами,
то есть её, в принципе, всегда можно поменять на свою. Причём
это можно сделать прямо во время работы программы.

Я решил устранить недостатки Forth'а, при этом не реализуя самые
запутанные вещи в Lisp - контексты, сборщик мусора и
интерпретация параметров при вызове функций. А также избегая
лисповской типизации ячеек списка - это не настоящая система
типов, а при этом мешает эффективно хранить и исполнять
программу. А как мы помним, одним из достоинств Lisp'а является
то, что он может сам себя редактировать. То есть по уму
программа должна исполняться непосредственно из исходного
текста.

Моя идея была в том, что базовой структурой данных остаётся
список, а параметры и результат функций передаются в
(псевдо)стеке, то есть при вызове не нужно их интерпретировать.
Но при этом ещё:
- функции имеют возможность читать термы исходного текста, то
есть получать параметры, записанные от них справа;
- можно предусмотреть сохранение глубины стека при вызове, чтобы
функции по умолчанию возвращали старую глубину (но я пока не
стал так делать);
- тип вложенного списка определяется по его первому элементу.
Терм - это тоже список, ведь у него есть символьное имя, а это
имя надо где-то хранить для возможности показа. Чтобы не
интерпретировать этот тип в первом элементе, просто пишем туда
адрес обработчика этого типа: переменная, константа, функция...
(Для встроенных функций язык там будут специфические
обработчики.) Во втором элементе списка лежат полезные данные
(содержимое переменной, адрес функции...), а дальше - имя по
одному символу.

Выражения на таком языке будут выглядеть примерно так:
(a + (x))
(a + 0) //операции типа "+" читают+вычисляют свой правый операнд
((x) + a)
(f (x) + a)
(a + f (x))
(fquote (x y z)) //эта функция не вычисляет свой операнд
(a b f2) //эта функция читает операнды из стека

Ниже приведён код интерпретатора и пары обработчиков встроенных
функций (реально их должно быть гораздо больше). Базовая функция
называется count. Псевдостек растёт вверх, навстречу реальному
стеку.

        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
;читаем data в hl
;читаем pnext в 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:
;интерпретатор списка
;вызывает функцию по указателю в de, она идёт снова на count или
;на endcount
        GETNEXT ;читаем pfunc в hl, pnext в de
        ld a,(hl)
        inc l
        ld h,(hl)
        ld l,a    ;*pfunc (в списке-описании функции первый
                  ;элемент - адрес для вызова)
        jp (hl) ;при вызове асмофункции стек не двигается

PushValue:
;асмофункция, лежащая в начале списка-описателя
;переменной/константы
;вернуть value из текущего списка - лежит в (de)
        ex de,hl
        ld e,(hl)
        inc l
        ld d,(hl) ;value
        PSEUDOPUSHDE
endcount:
;вызывается как последний элемент списка
        ret ;endcount

Lisp:
;асмофункция, лежащая в начале списка-описателя функции
;прочитать value в текущем списке - лежит в (de), это адрес
;списка, который надо исполнить
        ex de,hl
        ld e,(hl)
        inc l
        ld d,(hl) ;value
        jp count ;вычисление списка, где описана лиспофункция,
;выходим в endcount

plus:
;вызывается из лиспа, поэтому не содержит ret
;на псевдостеке лежит левый операнд
;читает по указателю функцию/переменную/константу/список
;(и двигает указатель), вычисляет её, кладёт результат
;в псевдостек, складывает 2 числа на псевдостеке
        GETNEXT ;читаем plist в hl, pnext в de
         push de ;текущий указатель
        ex de,hl ;указатель = pdata
;de = указатель на функцию/переменную/константу/список, т.е. по
;сути всегда на список
;если функция/переменная/константа, то достаточно call ((de))
;(там выход на endcount)
;если список, то call count (с движением de и выходом на
;endcount в конце списка)
        call count ;вызвали первый терм списка и далее по списку
                   ;до endcount
        PSEUDOPOPDE ;теперь de=правый операнд
        PSEUDOPOPHL ;теперь hl=левый операнд
        add hl,de
         pop de ;текущий указатель
        PSEUDOPUSHHL
        jp count

_readnext:
;вызывается из лиспа, поэтому не содержит ret
;читает по указателю адрес терма, кладёт его в псевдостек (не
;вычисляя), двигает указатель
        GETNEXT ;читаем term в hl, pnext в de
        PSEUDOPUSHHL
        jp count

_eval:
;вызывается из лиспа, поэтому не содержит ret
;берёт из псевдостека адрес терма, вычисляет его, кладёт
;результат в псевдостек
         push de ;текущий указатель
        PSEUDOPOPDE ;теперь de = адрес терма
;de = указатель на функцию/переменную/константу/список, т.е. по
;сути всегда на список
;если функция/переменная/константа, то достаточно call ((de))
;(там выход на endcount)
;если список, то call count (с движением de и выходом на
;endcount в конце списка)
        call count ;вызвали первый терм списка и далее по списку
                   ;до endcount
         pop de ;текущий указатель
        jp count

_POPVAR:
;вызывается из лиспа, поэтому не содержит ret
        GETNEXT ;читаем pvar в hl, pnext в de
        inc l
        inc l
        ld a,(hl)
        inc l
        ld h,(hl)
        ld l,a    ;pvar.next (в списке-описании переменной
;первый элемент - адрес для вызова, второй - полезные данные)
         push de
        PSEUDOPOPDE
        ld (hl),e
        inc l
        ld (hl),d
         pop de
        jp count

_DUP:
;вызывается из лиспа, поэтому не содержит ret
        PSEUDOPOPHL
        PSEUDOPOPHL
        PSEUDOPUSHHL
        jp count

_SWAP:
;вызывается из лиспа, поэтому не содержит 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

как будем делать циклы и условия:
(1 2 ifeq (...) (..else..)) - как её телу передать вычисленные
параметры? через стек и глобальные переменные?
loop (...) - как её телу передать вычисленные параметры? как
выйти? (можно предусмотреть хак loop-exit, но в стеке не
получится хранить адрес выхода, т.к. exit может быть во
вложенной скобке)
или отказаться от локальных переменных, будут только глобальные?
и для них в лиспе предусмотреть команды push и pop с параметром
справа (вместо push можно просто написать имя переменной)

whilenz (...) //выполняет, пока последний результат не NIL (сам
не удаляет со стека)
на нём можно сделать обычный while:
cond
whilenz (... cond)

редактор:
DEFUN "brackets2list_delstring"
(
    eat //"("
    pushnil
    whilez (
        gettoken_del
        isopenbracket ifz (
            string2list_delstring //ест (...)
        )
        ...pushtolist... //так может добавить и )
        isclosebracket ifz //а что если закрывающей скобки нет?
           //или ввод сразу сделать прямо в скобках без ентера?
    )
    ...popfromlist...
    ...swaplist...
)
DEFUN "readlist" (readbrackets brackets2list_delstring)
DEFUN "eval_dellist" (DUP eval SWAP dellist)
loop (readlist eval_dellist printlist_dellist)

                             * * *

Очевидный недостаток - программа на таком языке занимает как
минимум вдвое больше, чем фортовская, именно из-за хранения в
списке. А структура данных "список" редко нужна для реальных
спектрумовских задач, она ведь была выбрана для того, чтобы
можно было редактировать программу в процессе её работы.

Если проект будет доделан, то можно будет использовать его и для
программы бесконечного размера, если все указатели сделать
дальними (3 байта вместо 2). Но для таких больших программ надо,
чтобы и программировать было удобно...



Other articles:


Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Similar articles:
B.B.S. News - The work B.B.S. 'ca.
Programming - a procedure in Basic Calendar.

В этот день...   23 November