ACNews
#70
02 мая 2018 |
|
Проект языка Listh - Forth реализуется значительно проще, чем Lisp, но это тянет большие недостатки.
Проект языка 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). Но для таких больших программ надо, чтобы и программировать было удобно...
Другие статьи номера:
Похожие статьи:
В этот день... 30 октября