Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
||||||||||||||||||||||||||||||||||||||||||||||
1 | ||||||||||||||||||||||||||||||||||||||||||||||
Пишем свой интерпретатор языка BASIC20.06.2009, 20:03. Показов 242520. Ответов 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 Пишем свой чекер пишем свой троян с нуля |
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
17.11.2009, 10:45 [ТС] | 241 |
Я просто подумал,что можно разбить интерпретацию файла на фрагменты: 1.проверка синтаксиса 2.собственно запуск. 3.а также общая кнопочка(проверка +запуск). А если функции нужно вызывать по желанию (они же в программе basin) то IDE должна как-то знать о них.Но в принципе это не критично и можно сделать только одну кнопку.
Непонятно мне,как запускать программу из другой программы.Что,есть какая-то функция для этого (стандартная)? system как-то не очень хвалят,а как ещё можно вызвать стороннюю программу? А сейчас у меня устроено так,что если прописана опция --gui в консоли,то в main всё просто идёт по другой ветке,и запускается конструктор окна,а уже оттуда вызываются функции проверки синтаксиса и интерпретации,которые и передаются в виде указателей экземпляру окна.А сама реализация gui классов лежит в разделяемой библиотеке. Добавлено через 12 минут Я подумал: "зачем создавать два приложения,когда можно обойтись одним?" и вариант с опцией командной строки показался мне наиболее простым.Потому и сделал так.
0
|
17.11.2009, 12:04 | 242 |
> Я подумал: "зачем создавать два приложения,когда можно обойтись одним?" и вариант с опцией командной строки показался мне наиболее простым.Потому и сделал так.
Можно и одним. Но у людей принято делать так, как я тебе пояснил. Если человеку не нужна IDE, он ставит просто компилятор (интерпретатор). Когда имеется две программы, то компилятор разрабатывает одна команда разработчиков, а IDE - другая, т.е. технически такой вариант удобнее. По большому счёту разбитие большого проекта на несколько отдельных программ - это то же самое, что разбивать одну программу на несколько файлов. Только в качестве интерфейса служит запуск с разными параметрами и обработка текстовой выдачи > Я просто подумал,что можно разбить интерпретацию файла на фрагменты: 1.проверка синтаксиса 2.собственно запуск. 3.а также общая кнопочка(проверка +запуск). А если функции нужно вызывать по желанию (они же в программе basin) то IDE должна как-то знать о них.Но в принципе это не критично и можно сделать только одну кнопку. Решается просто. В интерпретатор заводишь опцию типа --syntax-only и при включенной опции после завершения разбора синтаксиса выходишь. В IDE при нажатии на кнопку "Компилирровать, но не запускать" запускаешь интерпретатор с дополнительной опцией --syntax-only. Пункты 2 и 3 для интерпретатора разделить нельзя, это есть только в компиляторах, поскольку на стадии компиляции формируется исполняемый файл, а на стадии запуска он исполняется, а у интерпретатора нет выходного файла. ПОэтому в твоём случае остайтся только два варианта: "Проверить синтаксис" и "Исполнить" > Непонятно мне,как запускать программу из другой программы.Что,есть какая-то функция для этого (стандартная)? system как-то не очень хвалят,а как ещё можно вызвать стороннюю программу? Если не забуду, на работе пример покажу готовый. А так ищи в сторону fork, exec - это строго для запуска, pipe, dup2 - это чтобы перезватывать вывод программы Как вариант ты можешь оставить то, что сделано у тебя сейчас, но это я бы крайне не рекомендовал, т.к. закопаешься довольно глубоко. Либо опять стОит попробовать по тем же причинам: чтобы понять, что так делать не стОит.
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
17.11.2009, 13:01 [ТС] | 243 |
То есть получается,что идея создания динамической разделяемой библиотеки отпадает за недадобностью.Ведь у нас будет две отдельные программы,и одна запускает другую.В этом варианте библиотека .so (или .dll) не нужна.
0
|
17.11.2009, 15:00 | 244 |
Идея динамесечкой библиотеки с точки зрения IDE - отпадает. Но в самом интерпретаторе это сделать можно. Но для порядку надо найти ещё одну графическую библиотеку помимо SDL и уже экспериментировать с двумя собственными динамическими библиотеками, работающие по одному интерфейсу
По поводу запуска программы - я уже выкладывал исходник Трубопровод на линукс Грубо говоря, можно его запустить как "./a.out ls -l" и в этом случае весь ввод-вывод запускаемой программы (в нашем случае ls) пройдёт через процедуру listener
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
||||||
26.11.2009, 02:21 [ТС] | 245 | |||||
Я вспомнил,что есть класс QProcess,и попробовал сделать с помощью него,(раз уж Qt использую).Наверное,твой пример с pipes намного более транспортабелен между unix-системами?
Вот как я делаю
Прочитал,что вроде как xterm есть на большинстве unix-систем,но точно не уверен.Ведь xterm может быть в другой папке.Просто немного сложновато в твоём примере,хотя общий смысл я понял,но там есть всякие define-ы специфические,немного усложняет понимание. Да,я всё таки вернул флаги компиляции в интерпретаторе,это стало возможным благодаря тому,что gui теперь-отдельное приложение.Makefile у меня один,просто теперь он автоматом запускает компиляцию второй программы,заходит в папку,и копирует туда скомпиленный интерпретатор,чтобы лежал рядом с gui программой.
0
|
26.11.2009, 10:18 | 246 |
Тот пример действительно именно под юникс. Что-то я не подумал про Qt. Вариант с Qt будет работать на всех платформах, где поддерживается Qt. И делать надо именно через QProcess (имею ввиду именно через высокоуровневые средства Qt)
Обычно путь до терминала выносят в настройки IDE. Если программа предварительноработает через configure, то по результату работы выставляется дефолтное значение пути до интерпретатора. Если без configure, то дефолтное значение лучше выставить пустым и при первом запуске выдавать кно с настройками, куда пользователь должен ввести параметры, которые нельзя (или нежелательно) вычислить автоматически, при этом одновременно решается проблема, что в unix'е и windows пути в принципе разные. После чего все настройки сохраняются в файл конфигурации и при следующих запусках цепляются оттуда. Примерно так выглядит нормальная user-friendly реализация
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
27.11.2009, 01:56 [ТС] | 247 |
>Обычно путь до терминала выносят в настройки IDE. Если программа предварительно работает через configure, то по результату работы выставляется дефолтное значение пути до терминала.
Ты про тот configure скрипт,который запускается до сборки проекта? Сделать что-то навроде test -e /usr/bin/xterm? Ну на на винде,я так понимаю,будет использоваться стандартная переменная %ComSpec% ? Или имелось в виду создание некой отдельной функции configure,или что-то ещё?
0
|
27.11.2009, 11:34 | 248 |
Я имел в виду, как делают люди. У людей обчно сначала запускается configure, а потом make. Имел в виду именно этот configure.
Да в общем неважно как настроить дефолтное значение. Важно лишь то, чтобы пользователь имел возможность это настроить. А так же при первом запуске знал, что есть такая вещь, которую надо настроить. Чтобы не было такого, что он надимает кнопку "Run", а в ответ тишина или сообщение об ошибке, которое без поллитры не разберёшь
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
||||||
28.11.2009, 02:34 [ТС] | 249 | |||||
Я сейчас читаю доку по autoconf,пока нашёл это http://www.gnu.org/software/li... c-Programs и это http://www.gnu.org/software/li... r-Programs. Мне нужен какой-то макрос,навроде AC_PROG_XTERM,но его там нет,и веть нужно ещё,чтобы в зависимости от проверки на существование писался define в config.h или в похожий файл.
Т.е. например,так
Я помню,ты писал,что не работал с autotools,так что это,скорее вопрос риторический.Придётся наверное отдать всё во власть пользователя и как-то создавать .ini или что-то в этом духе.
0
|
28.11.2009, 10:56 | 250 |
Ну до реальных пользователей пока далеко, так что с autoconf'ом точно можно не спешить и отложить на потому. Потому как в процессе отладки ты все настройки сам можешь сделать.
Просто "пользователь" в данном случае можно рассматривать в двух контекстах: - тот, кто программой будет пользоваться. Все возможные фичи в интерфейсе (опции командной строки в интерпретаторе и опции в gui и всевозможные кнопочки) надо закладывать сразу и всегда об этом помнить - тот, кто программу будет собирать из исходников. Грубо говоря, в этом случае все настройки технически решаются через файл config.h. Как вариант ты его создаёшь руками и тестируешь в разной комбинации опций конфигурации системы, а уже под конец пишешь configure, который в автоматическом режиме создаст файл config.h Получается так, что по второму пункту проблема локализована, и технически решается в узком месте, где вообще не нужно затрагивать исходники программы. А первая проблема - это то, что по большому счёту сильно влияет на работу всего комплекса и удобство пользования. И в сторонке не всегда это получается сделать, поэтому надо как можно раньше в процессе программирования/проектирования закладываться на эти свойства
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|||||||||||
30.11.2009, 08:06 [ТС] | 251 | ||||||||||
Подглядел,как делают define-ы с помощью configure: написал в configure.in
configure.in
Там я временно сделал вариант с ошибкой,так как не знаю,где лежит xterm на других системах по умолчанию. А программу всё-таки вызываю непосредственно,так чтобы она была под контролем IDE,а не внешнего терминала.Поэтому,скорее всего,придётся делать окошко для варианта успешного вывода (печать результатов программы),например по некому сигналу открывается окошко с текстовым полем.Меня только смущает то,что IDE по сути,толжна быть только обёрткой для программы,а тут получится вмешательство в стандартный вывод.Обычно же IDE вызывает внешнюю программу (cmd или xterm). Вызываю интерпретатор так: mainwindow.cpp
И в errorsList печатаются сообщения красным цветом.
0
|
30.11.2009, 08:43 | 252 |
А для чего? Вообще это очень плохой метод работы. С IDE и без IDE программа должна работать эквивалентно. Если работал на виндовых IDE, то во всех случаях консольной программа запускается в отдельно консоли. Понятно, что в случае юниксов это требование менее жёсткое, т.к. в отличие от виндов имеется множество разных реализаций терминалов, а в твоём случае попросту нужно сделать свою реализацию терминала (или эмулятор терминала), встроенную в IDE. Только вот не пойму, что тебе это даст
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
|
30.11.2009, 10:19 [ТС] | 253 |
Просто я не представляю,как потом считать данные из потоков ошибок и вывода,ведь если я буду запускать консоль как дочерний процесс IDE,то считываться будут только данные,которые пишет этот процесс,а не запускаемый в консоли? Или в Unix-системах не существует разграничений для процессов,и все данные идут в один общий поток? Вот это мне не понятно. Постараюсь уточнить свой вопрос: допустим,есть файл - поток вывода stdout,запускается программа-терминал,у неё есть свой вывод (свои ошибки или ещё что-то),а программа,запускаемая внутри терминала имеет свой вывод(в данном случае это интерпретатор,и выводится результат программы на QBASIC). Пишутся ли все данные в один поток вывода? С этим и было связано моё затруднение,ведь если я создаю процесс для консоли,и передаю ей в качестве параметра имя программы,то как потом считать вывод этой программы?
0
|
30.11.2009, 10:34 | 254 |
Всё, понял. Надо подумать. Если просто в терминале запустить процесс, а из него ещё один процесс, то извне они будут пользоваться одним и тем же stdout'ом, т.к. shell'ы при порождении нового процесса настраивают файловые дескрипторы таким образом, что они ссылаются на одни и те же потоки (по сути делается dup2, как в том примере про трубопровод). Правда я никогда не пытался запустить терминал, и уже перехватывать его (терминала) ввод-вывод. По логике вещей здесь уже ситуация другая. Тут надо знающих людей поспрашивать
1
|
30.11.2009, 17:34 | 255 |
В общем, знатоки утверждают, что при запуске черз нормальный терминал подобная операция перехвата ввода-вывода будет слишком геморройной. Не говоря уж о том, что кроссплатформенно сделать в принципе не получится, а потому их предложение так же сводилось к тому, что проще будет использовать самодельную эмуляцию терминала. Причём говорили, что в Qt вроде бы есть какая-то стандартная компонентя для консоли
Должно быть не вмешательство, а перехват ввывода. Т.е. интепретатор делает printf, а IDE должна перехватить то, что выдается в стандартный вывод и нарисовать это на своём терминале. В случае отладчика тебе надо будет ещё и ввод подсовывать (хотя ввод и для INPUT'а нужен) - его так же придётся моделировать в самодельном метрминале
1
|
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
|
||||||
20.12.2009, 02:45 [ТС] | 256 | |||||
Пытаюсь сделать конструкцию
А как быть с самой интерпретацией run-time? По идее нужно тоже передавать нужный стек параметром,только вот как метка вызова функции будет знать,какой стек нужно выбрать? Придётся делать общую переменную для всех stmt's,содержащую указатель на текущий стек? Или только для меток делать указатель на нужную функцию в списке..
0
|
20.12.2009, 11:35 | 257 | |||||
Как интерпретировать процедуры через промежуточное представление - я вот так с ходу и не скажу. Проблема именно в рекурсивных вызовах. Видимо надо уже над переменными строить надствройку в виде некоторой страницы (которая по сути пул памяти, хранилище). Т.е. при создании переменной ты ещё и указываешь и страницу, в которой эта переменная живёт. Страница для глобальных переменных будет одна. Для локалов и формальных параметров каждой процедуры заводится своя страница и все переменные ссылаются на эту страницу. При наличии рекурсивного вызова процедуры всю страницу можно куда-нибудь в сторону скопировать. Таким образом с точки зрения нашего представления вроде бы остаётся всё как есть: внутреннее представление каждой переменной всегда находится в одном и том же месте памяти интерпретатора. Но это - некий технический бубен, потому как наше представление было рассчитано на случай без функций.
Как вариант можно строить наше же представление. С незначительными отличиями: - вместо одной цепочки операторов будет несколько цепочек (на каждую функцию отдельная цепочка) - каким-то образом нужно отличать глобальные переменные от локальной переменной функции. Способов миллион от страниц, про которые говорил выше, до списков переменных, привязанных к каждой процедуре Оба отличия с технической точки зрения НЕ являются сложными и НЕ являются бубнами/подпорками/костылями. Получится так называемое представление высокого уровня, которое попросту отражает исходник программы в удобный вид для обработки в интерпретаторе (ну и плюс отсеиваем пользовательские ошибки). Представление верхнего уровня будет использоваться только для хранения "образа программы". Далее нужно построить ещё одно промежуточное представление, которое удобно для интерпретирования. Это будет представление низкого уровня, которое внутри себя будет читывать динамические стеки и прочую лабуду, возникающую при исполнении. Компиляторы, как правило строятся на двух промежуточных представлениях с тем же назначением: высокоуровневое представление, чтобы иметь образ программы, и низкоуровневое, чтобы с него генерировать код. Высокоуровневое представление оперирует понятиями "объект" ("переменная"). Низкоуровневое - понятиями "память" и "регистр". Для исходника
Код
ASSIGN (VAR A, PLUS (VAR B, VAR C)) Код
OBJPTR A -> r1 <-- запись адреса переменной в некий регистр r1 OBJPTR B -> r2 OBJPTR C -> r3 LOAD [r2] -> r4 <-- загрузка значения из адреса, записанного в r2, в регистр r4 LOAD [r3] -> r5 ADD r4, r5 -> r6 <-- сложение значений на регистрах r4 и r5 и запись результата в регистр r6 STORE r6 -> [r1] <-- запись значения и регистра r6 по адресу, записанному в регистре r1 Адрес глобальных переменных в run-time всегда будет одним и тем же. Адреса локальных переменных будут плавать (в зависимости от глубины стека). В этом случае понадобятся страницы (фрэймы) памяти. Все глобальные переменные будут распределены в одну страницу для глобалов. При входе (на исполнении) в каждую процедуру будет заводиться страница для локалов процедуры и туда помещаться все локалы. Адрес локальной переменной будет вычисляться на основании адреса текущей страницы локалов. Да, всё это на первый взгляд слишком непонятно. Непонятно так же, как это будет выглядеть технически. Но для начала надо хотя бы в общем виде понять концепцию двух представлений. Причём пока писал, уже понял, что низкоуровневое представление можно значительно сократить. А так же построить его на базе высокоуровневого, т.к. на первый взгляд раличие будет только при работе с переменными. Но это мысли "на потом", для начала понять концепцию. Прежде, чем это делать, тебе всё-таки нужно навести порядок во внутренностях интерпретатора, потому что закопаешься. И ещё сделать нормальную печать промежуточного представления, чтоб "глазами" можно было увидеть всё, что в нём есть и как оно друг с другом связано (как это смотреть в моей версии, я вроде бы уже писал). В gcc эти печати даже не стали прятать от пользователя (хотя во всех коммерческих компиляторах в пользовательских версиях) она отключена. Запусти "gcc t.c -da" и ты увидишь файлы "gcc t.c.*", в которых расписано представление после каждого прохода или оптимизации. Без подобной НОРМАЛЬНОЙ печати компилятор реализовать просто невозможно
1
|
591 / 357 / 16
Регистрация: 06.02.2009
Сообщений: 1,386
|
|
20.12.2009, 12:06 | 258 |
Evg, #pragma, я тут прочитал, что вы хотели бы иметь графический интерфейс к программе. Могу помочь. Предлагайте свои идеи, я попробую реализовать. От вас мне будет нужна только программа в скомпилированном виде. И некоторые нюансы можно будет обсудить потом.
1
|
20.12.2009, 13:23 | 259 |
#pragma, как вариант я поддерживаю такое предложение. Потому как в реальной жизни именно так и происходит. С другой стороны у тебя есть желание самому разобраться в Qt и самому наваять графическую часть.
Как вариант можешь делать сам, но параллельно "заказать" работу. Потому как одна программа низкого уровня (интерпретатор) и несколько программ высокого уровня (графическая оболочка) - это совершенно нормальное явление. А заодно и в чужом коде посмотришь, как делается что-то. Ещё прочувствуешь, чем принципиально отличается работа в команде от работы в одиночку: когда есть много мыслей в голове это одно, а когда эти мысли надо объяснить людям - совсем другое.
1
|
591 / 357 / 16
Регистрация: 06.02.2009
Сообщений: 1,386
|
|
20.12.2009, 13:41 | 260 |
Значит ждем решения #pragma.
Добавлено через 1 минуту Evg, ты не мог бы дать мне список ключевых слов и саму программку. Я пока поэкспериментирую.
1
|
20.12.2009, 13:41 | |
20.12.2009, 13:41 | |
Помогаю со студенческими работами здесь
260
Пишем свой класс, спецификатор доступа protected Интерпретатор небольшого языка программирования на С++ Не удается откомпилировать интерпретатор М-языка Интерпретатор музыки стандарта BASIC PLAY на С++ Написать интерпретатор программного языка -помощь Интерпретатор/компилятор ассемблер-подобного языка Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |