Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
||||||||||||||||||||||||||||||||||||||||||||||
1 | ||||||||||||||||||||||||||||||||||||||||||||||
Пишем свой интерпретатор языка BASIC20.06.2009, 20:03. Показов 242541. Ответов 464
Метки нет (Все метки)
Благодаря форуму и Evg в частности интерпретатор развивается, потихоньку превращаясь в простенький интерпретатор QBASIC.
Некоторые из самых старых версий сохранились в теме и ссылки на них будут добавлены в это сообщение,а также ссылки на другие темы,связанные с этой. Репозиторий с проектом находится тут, там же есть возможность в браузере посмотреть историю ревизий (английский в логах весьма примитивен,комментарии и рекомендации можете писать в личку),а также скачать самый последний архив репозитория в формате .tar.gz Если кто-то пользуется Subversion,скачать исходники можно так: Код
svn co https://basin.svn.sourceforge.net/svnroot/basin basin Технический приём для формирования согласованных данных https://www.cyberforum.ru/c-linux/thread46096.html Вопрос по svn (Subversion) Создание системы тестирования ПО. Вопрос про разные реализации бэйсиков Можно ли выразить порядковый номер элемента массива через индексы? [C++] Какие флаги указать линкеру для компиляции программы? Как можно определить переменную в файле configure.in,чтобы её можно было использовать в Makefile? Странный SIGSEGV, или что зависит от порядка написания интерфейса класса https://www.cyberforum.ru/c-linux/thread61324.html Альтернативная версия интерпретатора от Evg на C Это простая реализация разбора выражений, написанная Evg на C: Представление выражения в двоичном дереве ***************** Первое сообщение: ***************** Задание(Страуструп,из книги,по готовому коду): Введите программу калькулятора и заставьте её работать.Например,при вводе
LexicalAnalyzer.h
LexicalAnalyzer.cpp
main.cpp
Анализатор-то работает,но конечное значение не вычисляется.Более того,если вводим
Добавлено через 2 часа 5 минут 30 секунд Пришлось решать влоб с дебаггером.У Страуструпа опечатка (или намеренная ошибка,что более вероятно ) Вот в этом куске кода в функции get_token():
Добавлено через 16 минут 19 секунд И ещё опечатка была
31
|
20.06.2009, 20:03 | |
Ответы с готовыми решениями:
464
Пишем свой интерпретатор языка BASIC Пишем свой strlen Пишем свой чекер пишем свой троян с нуля |
20.08.2009, 20:43 | 101 |
Ну да, так оно и есть. Просто исходники условно можно разделить на две категории. Первые - это исхожники, описывающие некие хранимые данные. Это value, variable, сюда же добавятся expression и statement. Вторая категория - это исзодники, которые функциональные, т.е. осуществляют некоторую работу над этими данными - лексический анализатор, разбор синтаксиса, интерпретация промежуточного представления
Добавлено через 3 часа 9 минут 34 секунды Понятное дело, что ты только учишься и главное понятьпринцип и сделать хоть что-то рабочее. Но чем точнее мы поддержим оригинальный синтаксис, тем больше сможем нарыть готовых тестовых примеров. Хотя можно просто переделывать их под себя. Т.е. НЕ обязательно поддерживать всё как надо. Мне уже для себя весь этот бардак стал интересен Сейчас твоя цель - разобраться с промежуточным представлением: правильно его строить и интерпретировать. Когда это всё будет работать ннормально, чисто теоретически для интересе можно попробовать поддержать синтаксис близко к оригиналу. Но, повторюсь, это непринципиально. Задача-минимум - реализовать хоть хоть в каком-то виде. При этом желательно, чтобы поддерживались IF, FOR, WHILE (в любом синтаксическом проявлении) и крайне желательна поддержка массивов. Свой вариант я набросал в первую очередь как пример на промежуточное представление. Пока с ходу не соображу, как _аккуратно_ туда ввинтить массивы. При этом появилось некоторое понимание, что держать строковые константы внутри Value - слишком геморойно получается
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
||||||
20.08.2009, 21:22 [ТС] | 102 | |||||
Не мог бы ты прокомментировать,как именно работает данная интересная конструкция и по каким соображениям она сделана?
0
|
21.08.2009, 14:32 | 103 |
Оформил в отдельную тему https://www.cyberforum.ru/cpp/thread47863.html
Если что непонятно - спрашивай там Добавлено через 14 часов 20 минут 53 секунды В общем думал, как работать с массивами, в итоге получилась примерно следующая концепция. У нас есть две независимые вещи: Value (числовое значение, которое может быть целым или плавающим) и String (строка, которую, как и во многих бэйсиках, ограничиваем размером в 255 байт). У переменной есть признак, она хранит Value или String. При этом в Value нельзя записывать String и наоборот. Технически и в моей и в твоей реализации переменная содержит Value по значению. Думаю, что лучше делать по косвенности. Т.е. Value, хранимая в переменной, выделяется динамически (т.е. имеем поле Value_t *value). При этом выделяем фактически массив Value'ов. Если переменная скалярная (т.е. НЕ массив), то мы выделяем массив из 1 элемента, если же наша переменная массив - то мы выделяем массив из нужного количества элементов. Далее при разборе левой части присваивания мы фактически получаем указатель на нужное нам Value_t* (хоть для скалярной переменной, хоть для произвольного элемента массива). Всё аналогично делается со String'ами, а переменная внутри себя через union рядышком хранит Valut_t* и String_t*. При этом признак Value или String хранится на всю переменную. Т.е. если мы имеем массив, то нельзя в одном элементе хранить строку, а вдругом число Что касается синтаксиса, то видимо будет удобнее делать как у людей. Строковые переменные отмечать признаком $ на конце (т.е. этот символ входит в имя переменной, при этом в момент заведения новой переменной по этому символу мы понимаем, что будет храниться в переменной - строка или число) Что касается регистра букв, то проще всего это локализовать в лексическом анализаторе. При чтении токена из файла для keyword'ов и ident'ов сразу переводить их в заглавные буквы. Тогда будет работать и "int" и "INT" и всё, что пожелаешь По поводу типа переменной. У себя я предполагаю сделать примерно так: переменную можно использовать без дополнительного объявления. В этом случае тип будет "AUTO" (т.е. тип будет определяться записанным значением). Так же можно будет объявлять переменные через "DIM A AS INTEGER" (или как там правильно в синтаксисе). В этом случае тип переменной будет INT, FLOAT или STRING Добавлено через 20 минут 32 секунды Надо ещё при этом в промежуточном представлении как-то левую часть по другому описывать. Теперь там может быть не только переменная, но и элемент массива Добавлено через 2 часа 12 минут 19 секунд > Надо ещё при этом в промежуточном представлении как-то левую часть по другому описывать В общем думать тут особо и нечего. Левая часть так же описывается в виде Expression. Только нужно при разборе понимать, что там допустимо только обращение к переменной или к элементу массива. А так же при обходе в момент интерпретации должно возвращаться не значение, а указатель на Value
1
|
23.08.2009, 15:08 | 104 | |||||
Тестовый пример, на котором моя версия ведёт себя некорректно. Я работал в таком ключе, что запись в переменную (т.е. появление переменной в левой части присваивания) автоматически означает её определение (т.е. этим мы задаём новую переменную). Я полагал, что в момент разбора синтаксиса если встретили несуществующую переменную в правой части присваивания - то это ошибка (по сути использование неинициализированной переменной)
1
|
24.08.2009, 16:17 | 105 |
> У нас есть две независимые вещи: Value (числовое значение, которое может быть
> целым или плавающим) и String (строка, которую, как и во многих бэйсиках, > ограничиваем размером в 255 байт). Что-то фигово получается, когда одно делишь от другого. Буду дальше экспериментировать с тем, как делать "хорошо"
1
|
25.08.2009, 15:29 | 106 |
Итого, примерно надумал следующее. Ввожу понятие типа, который может быть целочисленный (NUMERIC), строковой (STRING) и логический (LOGICAL). Переменные могут быть только первых двух типов, промежуточное значение выражения - всех трёх. Тип изначально является свойством значения (Value), но при этом засовывается в Variable и Expression, поскольку они так же оперируют значениями. Целочисленный тип разбиваю далее на два подтипа: целый и плавающий. Любые двухаргументные операции разрешены только над одинаковыми типами (в этом отношении int и float имеют одинаковый тип NUMERIC). Присваивание разрешено только для одинаковых типов
На текущий момент есть вроде бы всё основное: переменные, арифметика, операции управления, строки, массивы (в том числе и многомерные). Для полного сачтья надо поддержать встроенные функции (обычно их называют термином "builtin functions" или сокращённо просто "builtin"). Ну и с технической стороны надо поддержать пользовательские метки, FOR'ы, нормально поддержать двухсимвольные операции сравнения (">=", "<=", "<>"), но это всё уже мелкая работа, т.к. структуру уже затрагивать не должно - просто будет допиливаться код. Что мне сейчас не нравится принципиально - это реализация строк. Она сделана в виде массива, а потому структура Value получается большой. Нормально строку надо хранить не по значению, а по косвенности, но в Си это означает мегагеморрой с динамической памятью, слежение за удалением и копированием и т.п. А вот на Си++ это делается нормально за счёт деструкторов и реализации конструктора копирования (либо ваще тупо использовать стандартный String) Собственно программу выдаю для изучения того, как всё это примерно выглядит. Понятно, что тебе не нужно пытаться всё это повторить. Делай так, как понимаешь. Но с моей прогой возможно удетнекое общее понятие того, что в итоге должно быть. Словами это фиг объяснишь Выкладываю опять в двух вариантах: basic.rar в кодировке KOI-8 и basic_w.rar в кодировке WIN-1251
1
|
25.08.2009, 16:12 | 107 | |||||||||||||||
Для такого кода
Код
-> 460 -> 3 -> 3.700000 -> 999 -> 1000 -> 100 -> 5050 -> 1 -> 4 -> 9 -> 16 -> 124 -> 114 -> ERTzxcvbnm1234567 Вот ещё что забыл сказать. При работе в качестве интерпретирующей части можно повесить модуль, который вместо интерпретации будет генерить код на Си. Например, для исходника
По такой технологии сейчас поддерживаются некоторые устаревшие языки программирования. Особенно у военных, которые ни за что не пойдут на то, чтобы переписать какие-то военные программы, написанные много лет назад, потому как писали их гении того времени, а потому в коде разобраться невозможно. И гораздо более надёжным вариантом оказывается написать интерпретатор или конвертер в Си
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
25.08.2009, 19:01 [ТС] | 108 |
Вот про конвертер в Си немного не понял идею,как именно это будет работать.Мне теперь остаётся только наблюдать за твоим ходом написания,и потихоньку ковырять свой вариант.
0
|
25.08.2009, 22:07 | 109 | |||||||||||||||
Ну... для начала надо понять, как это на уровне промежуточного представлнеия выглядит. Далее понимаешь, как это представление интерпретируется, а дпльше попросту сгенерить код, который по большому счёту работает в терминах Value и Variable. Суть в том, что из исходника на одном языке получается исходник на другом. В идеальном варианте исходник на Си для того примера будет вот таким:
Это при том, что я себе совершенно чётко представлял, что должно быть, у меня ушло на это неделя времени. Так что твои два месяца - это вполне нормально. Как говорится "тяжело в учении..." Ну... особо наблюдать больше не придётся Просто перед отпуском на работе резких телодвижений лучше не делать, а потому вот занялся интерпретатором. Да и если дальше меня не заломает, то следить там уже особо будет нечего - вся необходимая база уже вроде бы как отписана. Тебе нужно только понять принцип, по которому построено промежуточное представление и его интерпретация. Возможно не так просто будет разобрать тот низкоуровневый код на Си, когда привык работать с высокоуровневыми конструкциями типа списка и т.п. Так что если есть вопросы - задавай, до вечера пятницы я ещё тут. Добавлено через 1 час 18 минут По поводу генерации кода на Си (Си++). Относительно совтояния твоих исходников из поста #86 Допустим, имеем исходник на бэйсике:
Код
$ g++ a.cc $ ./a.out A=10 B=11 C=221 Надеюсь, так понятнее. Если непонятно - забей. Если понятно - имей в виду. Такую хрень можно будет потом привинтить к интерпретатору. Можно конечно и сразу такое ваять, но сначала написать интерпретатор будет проще.
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
25.08.2009, 22:47 [ТС] | 110 |
Да,это круто! Как я понял просто работаем с выходным файлом и записываем туда строки в зависимости от интерпретации файла на псевдо-бейсике.Затем можно дать команду типа system("g++ a.cc") (если в линукс,ну в другой среде можно команду поменять) и он скомпилируется на месте.
0
|
25.08.2009, 23:33 | 111 |
Ну как и что потом запустить - уже не важно. В общем суть главное понял. Это большой бонус удобства работы с промежуточным представлением. Хочешь - интерпретируй, хочешь - генери с него текст на Си\Си++, хочешь - можешь напрямую делать компиляцию (фактически формирование ассемблерного файла). Представление в процессе построения заведомо очищается от пользовательских ошибок и, в отличие от файла, по нему можно ходить взад и вперёд, в том числе и заниматься оптимизациями (типа "A*4" заменять на "A<<2" или "A+5+6" заменять на "A+11"). Так что после того, как ты поймёшь, что же это за зверь такой, у тебя опять появится оптимизм
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
29.08.2009, 08:35 [ТС] | 112 |
Не знаю,зачем,но сделал проект на sourceforge.net - https://sourceforge.net/projects/basin/ (basin - Basic Interpreter),теперь у кого установлена Subversion,смогут просто скачать исходники командой
Код
svn co https://basin.svn.sourceforge.net/svnroot/basin basin Всё это дело вывешено под лицензией "BSD license",надеюсь возражений не будет? Просто хотел добавить опцию для скачивания,да и чтобы не лазить по теме в поисках исходников.Заодно снять лишний трафик с форума.Предлагаю держать исходники там.Можно попросить модераторов просто отредактировать первый пост,чтобы там были все ссылки относящиеся к данной теме. Если,например,нужно просто посмотреть историю изменений,то сделать это можно отсюда http://basin.svn.sourceforge.net/viewvc/basin/
0
|
16.09.2009, 09:22 | 113 |
Оп-па. А это сообщение у меня почему-то не было отмечено, как непрочитанное
Добавлено через 16 минут Уж коли пошло на то, что начал делать как серьёзную программу, то тебе надо бы удалить признак "exec" из исходников (т.е. часть файлов имеет права на исполнение). Видимо, ты пользуешься каким-то аналогом винlузового проводника, где это не видно Ещё где-то для порядку надо описать, какие конструкции понимает твой интерпретатор. Чтобы хоть можно было поэкспериментировать и самому что-то написать. Я в репозитории храню исходник, в котором присутсвуют все образцы поддерживаемых конструкций Пока особо не смотрел, но в бэйсиках вроде бы сделано так, что переменная без суффикса по умолчанию имеет плавающий тип, а у тебя целый. Но это мелочь Добавлено через 6 минут В value у тебя присуствуют поля типа int и long. Но для 32-битных машин как правило их размеры совпадают. Более аккуратно было бы в файле с настройками host'ового компилятора завести typedef'ы с именами тиа int32_t, int64_t и везде использовать их. И в начале исполнения программы поставить run-time контроль, что "assert (sizieof(int32_t) == 4); assert (sizieof(int64_t) == 8);". Аналогично для плавающих. Вроде бы как это мелочь, но такие вещи лучше делать сразу, чтобы потом в миллион местах не исправлять Добавлено через 1 минуту И сделай при интерпретации print'а чтобы энтеры рисовались, а то лепит всё в одну кучу - неудобно. Или как-то можно самому энтер напечатать? Добавлено через 15 минут И если у тебя есть какой-то набор тестовых ситуаций - для порядка его тоже можно под cvs положить (создать отдельный каталог и свалить туда). ТОже полезно, а то когда работаешь с одним файлом, добавляешь туда и удаляешь, то интересные тесты пропадают
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
16.09.2009, 12:58 [ТС] | 114 |
Хм у меня права по умолчанию стояли и были drwxr-xr-x,ну я поменял их на drwxr--r-- а последний x не даёт убрать - тогда файлы в папке не видно,не знаю,с чем это связано :\
Да вроде кинул туда исходник,буду его обновлять В той теме,что ты сделал про бейсики я прочитал что как раз int,но сменить не проблема,если что Я написал себе заметку сделать систему типов,но то что ты говоришь-я что-то слабо представляю,о чём это и где точно прописывается..У меня в-общем при печатании слова int в среде выпадает менюшка со всякими int32_t и прочими,но я не в курсе,что это. Я прочитал про print-ы,там по-моему они не могут печатать EOL,но можно напечатать пустой print,и будет перевод строки.А если нужно пробел между значениями,то у меня можно напечатать строку с пробелом в качестве выражения .Формат записи: PRINT <выражение> [,] или [;] [<выражение>] а пустой print переводит строку. Сделаю. Добавлено через 26 минут Напиши,какая маска прав в восьмеричном формате должна быть у папок и файлов,я поставлю... Добавлено через 27 минут Как мне это сделать вообще с правами,сначала все файлы удалить из репов,а потом опять закачать с новыми правами?В репозиторий руками же не залезешь.
0
|
16.09.2009, 13:44 | 115 |
Ну, грубо говоря проблема такая, что в языке Си не определён размер базовых типов. На каждой архитектуре он определяется по своему. Но в общей своей массе эти настройки совпадают. И тем не менее, где нужен размер типа какого-то фиксированного размера, заводят typedef. Например "typedef int int32_t; typedef long long int64_t" и везде в программе используют int32_t и int64_t. Если ты отнесёшь свою программу на архитектуру с другиминастройками базового типа, то тебе нужно просто в одном месте изменить typedef, всё остальное срастётся автоматически. Обычно все эти настроки делаются через систему конфигурации, но тебе пока можно этим не заморачиваться, просто принять к сведению и работать через ручкаминаписанный typedef
Я уже плохо помню, но что-то в глубине памяти маячит, что "PRINT A" печатает A с переводом строки, а "PRINT A;" - без перевода. Но опять-таки не суть как делать, я у себя всегда принудительно влепил перевод строки, чтобы в процессе отладки глаза не ломать Каталоги 755, файлы 644 Интересный вопрос. Хз как это делать. Видимо надо читать мануал, с ходу и не скажу
0
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
||||||
16.09.2009, 14:06 [ТС] | 116 | |||||
Я просто не пойму,чем мешает такой вариант
P.S. А вообще у меня уже навроде паранойи насчёт типов везде где можно было поставил size3 для счётчиков привязанных к размерам векторов,потому что поглядел,какой у них max_size() на моей машине. Насчёт репов-боюсь попортить репозиторий,поэтому можно попробовать сделать это постепенно,по одному,просто удаляя старые и добавляя новые переименованные файлы.Потом можно попробовать переименовать их обратно.
0
|
16.09.2009, 14:28 | 117 |
Просто я смотрел revision 16 (ибо 20 у меня не компилится), а там в классе Value используются int и long
32 и 64 необязательно (просто удобно), можно назвать venik и prjanik, лишь бы не использовать напрямую базовые типы (чтобы править нужно было в одном месте)
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
16.09.2009, 14:32 [ТС] | 118 |
На что ругается компилятор?У меня всё ок,работает.Видимо,тут уже сказывается разность версий компиляторов?А-а,может надо makefile подправить,ты наверное только через командную строку работаешь?так я makefile только иногда обновляю,проверь
0
|
16.09.2009, 14:39 | 119 |
Не помню. На линковке вроде бы падало.
Вот такой код на исполнении должен зацикливаться, но он не зацикливается Код
LET B1 = 5 2: IF B1 THEN GOTO 2 PRINT B1 Код
2: LET B1 = 5 IF B1 THEN GOTO 2 PRINT B1 Код
2: LET B1 = 5 PRINT B1 IF B1 THEN GOTO 2 Я глубоко не копался, просто мысль для такого теста появилась после того, как я увидел реализацию того, как ты IF делаешь. Я ожидал увидеть переполнение стека в процессе интерпретации (из-за возникающей рекурсии в твоей схеме), но тут похоже какой-то другой косяк
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
16.09.2009, 16:00 [ТС] | 120 |
Да,косяк вроде в том,что при создании IF сначала проверяются (и создаются,конечно) его под-программы,а уже потом только сама инструкция IF,а это меняет глобальный порядок инструкций в программе.Выход-сначала создавать инструкции ,у которых есть свои подпрограммы "всухую",а уже потом создавать лист и проверять его на синтаксис,причем не меняя порядка следования,то есть сначала создаются все инструкции для THEN,а потом для ELSE,когда IF уже создан(ну в принципе это логично).А затем уже эти подпрограммы записывать в IF.Я ещё в начале заметил,что у меня в логах творится беспорядок: инструкции печатаются не по порядку,просто сначала не придал такого значения,что это баг.
Такой вариант должен исправить проблему,я полагаю. Ты видишь что неправильно реализован IF? Если я исправлю проблему, как описал выше,ты всё равно считаешь,что реализация неверная? Т.е. перегрузка стека неизбежна? Я считал,что будет верным делать подпрограммы для IF и WHILE,специально сделал функции интерпретации для закрытых блоков,надеясь что в будущем это поможет при составлении функций и т.п. P.S.Я кажется понял,почему ты так подумал.Нет-нет,у меня GOTO работает только в runtime,то есть создание IF будет только 1 раз.
0
|
16.09.2009, 16:00 | |
16.09.2009, 16:00 | |
Помогаю со студенческими работами здесь
120
Пишем свой класс, спецификатор доступа protected Интерпретатор небольшого языка программирования на С++ Не удается откомпилировать интерпретатор М-языка Интерпретатор музыки стандарта BASIC PLAY на С++ Написать интерпретатор программного языка -помощь Интерпретатор/компилятор ассемблер-подобного языка Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |