Форум программистов, компьютерный форум CyberForum.ru

Пишем свой интерпретатор языка BASIC - C++

Восстановить пароль Регистрация
 
 
Рейтинг: Рейтинг темы: голосов - 1509, средняя оценка - 4.80
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
20.06.2009, 20:03     Пишем свой интерпретатор языка BASIC #1
*****************
Благодаря форуму и Evg в частности интерпретатор развивается, потихоньку превращаясь в простенький интерпретатор QBASIC.
Некоторые из самых старых версий сохранились в теме и ссылки на них будут добавлены в это сообщение,а также ссылки на другие темы,связанные с этой.

Репозиторий с проектом находится тут, там же есть возможность в браузере посмотреть историю ревизий (английский в логах весьма примитивен,комментарии и рекомендации можете писать в личку),а также скачать самый последний архив репозитория в формате .tar.gz
Если кто-то пользуется Subversion,скачать исходники можно так:
Код
svn co https://basin.svn.sourceforge.net/svnroot/basin basin
Эти темы возникли в результате моих вопросов по ходу написания:
Технический приём для формирования согласованных данных
Makefile: как с использованием gcc строить автоматические зависимости от .h файлов?
Вопрос по svn (Subversion)
Создание системы тестирования ПО.
Вопрос про разные реализации бэйсиков
[C/C++] Можно ли выразить порядковый номер элемента массива через индексы?
[C++] Какие флаги указать линкеру для компиляции программы?
Как можно определить переменную в файле configure.in,чтобы её можно было использовать в Makefile?
Странный SIGSEGV, или что зависит от порядка написания интерфейса класса
[C++]Можно ли как-то указать в Makefile,чтобы часть файлов компилировал компилятор C?
Альтернативная версия интерпретатора от Evg на C
Это простая реализация разбора выражений, написанная Evg на C:
Представление выражения в двоичном дереве
*****************
Первое сообщение:
*****************
Задание(Страуструп,из книги,по готовому коду): Введите программу калькулятора и заставьте её работать.Например,при вводе
C++
1
2
r = 2.5
area = pi*r*r
Программа калькулятора выведет:
C++
1
2
2.5
19.635
Получили такой код:
LexicalAnalyzer.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// LexicalAnalyzer.h
#ifndef LEXICALANALYZER_H_INCLUDED
#define LEXICALANALYZER_H_INCLUDED
 
#include <cctype>
#include <string>
#include <map>
#include <iostream>
 
enum Token_value {
    NAME,       NUMBER,      END,
    PLUS = '+', MINUS = '-', MUL = '*', DIV = '/',
    PRINT = ';',ASSIGN = '=',LP = '(',  RP = ')'
};
extern Token_value curr_tok;
extern std::map<std::string,double>table;
extern int no_of_errors;
 
Token_value get_token();
 
double expr(bool);
double term (bool);
double prim (bool);
int error(const std::string&);
 
#endif // LEXICALANALYZER_H_INCLUDED

LexicalAnalyzer.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// LexicalAnalyzer.cpp
#include "LexicalAnalyzer.h"
 
 
std::map<std::string,double>table;
Token_value curr_tok=PRINT;
 
double expr (bool get)
{
    double left = term(get);
 
    for (;;)
        switch (curr_tok) {
            case PLUS:
                 left += term(true);
            break;
            case MINUS:
                 left-= term(true);
            break;
            default:
                 return left;
        }
}
 
double term (bool get)
{
    double left = prim (get);
 
    for (;;)
        switch (curr_tok) {
            case MUL:
                 left*=prim(true);
            break;
            case DIV:
                 if (double d = prim (true)) {
                     left /= prim (true);
                     break;
                 }
                 return error("Деление на ноль");
            default:
                 return left;
        }
}
 
double number_value;
std::string string_value;
 
double prim (bool get)
{
    if (get) get_token();
    switch (curr_tok){
        case NUMBER:{
            double& v = number_value;
            get_token();
            return v;
        }
        case NAME:{
            double& v = table[string_value];
            if (get_token()==ASSIGN) v = expr(true);
            return v;
        }
        case MINUS:
            return -prim(true);
        case LP:{
            double e = expr(true);
            if (curr_tok!=RP) return error("Ожидалась )");
            get_token();
            return e;
        }
        default:
            return error("Ожидалось первичное выражение");
    }
}
 
Token_value get_token()
{
    char ch = 0;
 
    do {
        if (!std::cin.get(ch)) return curr_tok = END;
    } while (ch!='\n'&&isspace(ch));
 
    switch (ch) {
        case 0:
             return curr_tok = END;
        case ';':case '\n':
             return curr_tok = PRINT;
        case '*':case'/':case '+':case '-':case '(':case ')':case '=':
             return Token_value(ch);
        case '0':case '1':case '2':case '3':case '4' :
        case '5':case '6':case '7':case '8':case '9':case '.':
             std::cin.putback(ch);
             std::cin>>number_value;
             return curr_tok=NUMBER;
        default:
             if (isalpha(ch)) {
                 string_value = ch;
                 while (std::cin.get(ch)&&isalnum(ch)) string_value.push_back(ch);
                 std::cin.putback(ch);
                 return curr_tok = NAME;
             }
             error ("Неправильная лексема");
             return curr_tok = PRINT;
    }
}
int no_of_errors=0;
int error (const std::string& s)
{
    no_of_errors++;
    std::cerr<<"Ошибка: "<<s<<'\n';
    return no_of_errors;
}

main.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.cpp
#include "LexicalAnalyzer.h"
 
 
int main()
{
    table["pi"]=3.1415926535897932385;
    table["e"]=2.7182818284590452354;
    while (std::cin) {
        get_token();
        if (curr_tok==END) break;
        if (curr_tok==PRINT) continue;
        std::cout<<expr(false)<<'\n';
    }
    return no_of_errors;
}

Анализатор-то работает,но конечное значение не вычисляется.Более того,если вводим
C++
1
a = 3 + 6
,то получаем "a", равное первому элементу в выражении,то есть 3.В чём логическая ошибка данной программы?С этими каскадными вызовами она слегка запутана.Уверен,что кто-то уже делал это задание.

Добавлено через 2 часа 5 минут 30 секунд
Пришлось решать влоб с дебаггером.У Страуструпа опечатка (или намеренная ошибка,что более вероятно ) Вот в этом куске кода в функции get_token():
C++
1
2
        case '*':case'/':case '+':case '-':case '(':case ')':case '=':
             return Token_value(ch);
Нехватает смены значения curr_tok,что и приводит к ошибочной работе.
C++
1
2
        case '*':case'/':case '+':case '-':case '(':case ')':case '=':
             return curr_tok=Token_value(ch);
Теперь всё пашет,всем спасибо,вопрос можно считать закрытым,но есть вопрос поважнее: В функциях prim и term возвращается int при ошибке,но ведь они имеют тип double,как вообще это работает?Происходит неявное преобразование типа,так?Мне интересно,почему Страуструп прибег к такому способу,это распространённая практика?

Добавлено через 16 минут 19 секунд
И ещё опечатка была
C++
1
2
3
                 if (double d = prim (true)) {
                     left /= d;// было left /= prim (true)
                     break;
Лучшие ответы (1)
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.11.2009, 10:45  [ТС]     Пишем свой интерпретатор языка BASIC #241
Цитата Сообщение от Evg Посмотреть сообщение
Какую функцию и что значит "вызвать"
Я просто подумал,что можно разбить интерпретацию файла на фрагменты: 1.проверка синтаксиса 2.собственно запуск. 3.а также общая кнопочка(проверка +запуск). А если функции нужно вызывать по желанию (они же в программе basin) то IDE должна как-то знать о них.Но в принципе это не критично и можно сделать только одну кнопку.

Непонятно мне,как запускать программу из другой программы.Что,есть какая-то функция для этого (стандартная)? system как-то не очень хвалят,а как ещё можно вызвать стороннюю программу?

А сейчас у меня устроено так,что если прописана опция --gui в консоли,то в main всё просто идёт по другой ветке,и запускается конструктор окна,а уже оттуда вызываются функции проверки синтаксиса и интерпретации,которые и передаются в виде указателей экземпляру окна.А сама реализация gui классов лежит в разделяемой библиотеке.

Добавлено через 12 минут
Я подумал: "зачем создавать два приложения,когда можно обойтись одним?" и вариант с опцией командной строки показался мне наиболее простым.Потому и сделал так.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
17.11.2009, 12:04     Пишем свой интерпретатор языка BASIC #242
> Я подумал: "зачем создавать два приложения,когда можно обойтись одним?" и вариант с опцией командной строки показался мне наиболее простым.Потому и сделал так.

Можно и одним. Но у людей принято делать так, как я тебе пояснил. Если человеку не нужна IDE, он ставит просто компилятор (интерпретатор). Когда имеется две программы, то компилятор разрабатывает одна команда разработчиков, а IDE - другая, т.е. технически такой вариант удобнее. По большому счёту разбитие большого проекта на несколько отдельных программ - это то же самое, что разбивать одну программу на несколько файлов. Только в качестве интерфейса служит запуск с разными параметрами и обработка текстовой выдачи

> Я просто подумал,что можно разбить интерпретацию файла на фрагменты: 1.проверка синтаксиса 2.собственно запуск. 3.а также общая кнопочка(проверка +запуск). А если функции нужно вызывать по желанию (они же в программе basin) то IDE должна как-то знать о них.Но в принципе это не критично и можно сделать только одну кнопку.

Решается просто. В интерпретатор заводишь опцию типа --syntax-only и при включенной опции после завершения разбора синтаксиса выходишь. В IDE при нажатии на кнопку "Компилирровать, но не запускать" запускаешь интерпретатор с дополнительной опцией --syntax-only. Пункты 2 и 3 для интерпретатора разделить нельзя, это есть только в компиляторах, поскольку на стадии компиляции формируется исполняемый файл, а на стадии запуска он исполняется, а у интерпретатора нет выходного файла. ПОэтому в твоём случае остайтся только два варианта: "Проверить синтаксис" и "Исполнить"

> Непонятно мне,как запускать программу из другой программы.Что,есть какая-то функция для этого (стандартная)? system как-то не очень хвалят,а как ещё можно вызвать стороннюю программу?

Если не забуду, на работе пример покажу готовый. А так ищи в сторону fork, exec - это строго для запуска, pipe, dup2 - это чтобы перезватывать вывод программы

Как вариант ты можешь оставить то, что сделано у тебя сейчас, но это я бы крайне не рекомендовал, т.к. закопаешься довольно глубоко. Либо опять стОит попробовать по тем же причинам: чтобы понять, что так делать не стОит.
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.11.2009, 13:01  [ТС]     Пишем свой интерпретатор языка BASIC #243
То есть получается,что идея создания динамической разделяемой библиотеки отпадает за недадобностью.Ведь у нас будет две отдельные программы,и одна запускает другую.В этом варианте библиотека .so (или .dll) не нужна.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
17.11.2009, 15:00     Пишем свой интерпретатор языка BASIC #244
Идея динамесечкой библиотеки с точки зрения IDE - отпадает. Но в самом интерпретаторе это сделать можно. Но для порядку надо найти ещё одну графическую библиотеку помимо SDL и уже экспериментировать с двумя собственными динамическими библиотеками, работающие по одному интерфейсу

По поводу запуска программы - я уже выкладывал исходник Трубопровод на линукс
Грубо говоря, можно его запустить как "./a.out ls -l" и в этом случае весь ввод-вывод запускаемой программы (в нашем случае ls) пройдёт через процедуру listener
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
26.11.2009, 02:21  [ТС]     Пишем свой интерпретатор языка BASIC #245
Я вспомнил,что есть класс QProcess,и попробовал сделать с помощью него,(раз уж Qt использую).Наверное,твой пример с pipes намного более транспортабелен между unix-системами?
Вот как я делаю
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    void MainWindow::run()
    {
       console = new QProcess;
    #ifdef _UNIX_
       if (!console->startDetached("/usr/bin/xterm",QStringList()
                                                    << "-e"
                                                    << "./basin"
                                                    << *args
                                                    << filename))
       {
          //Error
       }
    #endif
    #ifdef _WINDOWS_
       // ...
    #endif
    }
Добавлено через 12 минут
Прочитал,что вроде как xterm есть на большинстве unix-систем,но точно не уверен.Ведь xterm может быть в другой папке.Просто немного сложновато в твоём примере,хотя общий смысл я понял,но там есть всякие define-ы специфические,немного усложняет понимание.

Да,я всё таки вернул флаги компиляции в интерпретаторе,это стало возможным благодаря тому,что gui теперь-отдельное приложение.Makefile у меня один,просто теперь он автоматом запускает компиляцию второй программы,заходит в папку,и копирует туда скомпиленный интерпретатор,чтобы лежал рядом с gui программой.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
26.11.2009, 10:18     Пишем свой интерпретатор языка BASIC #246
Цитата Сообщение от #pragma Посмотреть сообщение
Я вспомнил,что есть класс QProcess,и попробовал сделать с помощью него,(раз уж Qt использую).Наверное,твой пример с pipes намного более транспортабелен между unix-системами?
Тот пример действительно именно под юникс. Что-то я не подумал про Qt. Вариант с Qt будет работать на всех платформах, где поддерживается Qt. И делать надо именно через QProcess (имею ввиду именно через высокоуровневые средства Qt)

Цитата Сообщение от #pragma Посмотреть сообщение
Прочитал,что вроде как xterm есть на большинстве unix-систем,но точно не уверен.Ведь xterm может быть в другой папке.
Обычно путь до терминала выносят в настройки IDE. Если программа предварительноработает через configure, то по результату работы выставляется дефолтное значение пути до интерпретатора. Если без configure, то дефолтное значение лучше выставить пустым и при первом запуске выдавать кно с настройками, куда пользователь должен ввести параметры, которые нельзя (или нежелательно) вычислить автоматически, при этом одновременно решается проблема, что в unix'е и windows пути в принципе разные. После чего все настройки сохраняются в файл конфигурации и при следующих запусках цепляются оттуда. Примерно так выглядит нормальная user-friendly реализация
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
27.11.2009, 01:56  [ТС]     Пишем свой интерпретатор языка BASIC #247
>Обычно путь до терминала выносят в настройки IDE. Если программа предварительно работает через configure, то по результату работы выставляется дефолтное значение пути до терминала.
Ты про тот configure скрипт,который запускается до сборки проекта? Сделать что-то навроде test -e /usr/bin/xterm?
Ну на на винде,я так понимаю,будет использоваться стандартная переменная %ComSpec% ? Или имелось в виду создание некой отдельной функции configure,или что-то ещё?
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
27.11.2009, 11:34     Пишем свой интерпретатор языка BASIC #248
Цитата Сообщение от #pragma Посмотреть сообщение
>Обычно путь до терминала выносят в настройки IDE. Если программа предварительно работает через configure, то по результату работы выставляется дефолтное значение пути до терминала.
Ты про тот configure скрипт,который запускается до сборки проекта? Сделать что-то навроде test -e /usr/bin/xterm?
Я имел в виду, как делают люди. У людей обчно сначала запускается configure, а потом make. Имел в виду именно этот configure.

Цитата Сообщение от #pragma Посмотреть сообщение
Ну на на винде,я так понимаю,будет использоваться стандартная переменная %ComSpec% ? Или имелось в виду создание некой отдельной функции configure,или что-то ещё?
Да в общем неважно как настроить дефолтное значение. Важно лишь то, чтобы пользователь имел возможность это настроить. А так же при первом запуске знал, что есть такая вещь, которую надо настроить. Чтобы не было такого, что он надимает кнопку "Run", а в ответ тишина или сообщение об ошибке, которое без поллитры не разберёшь
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
28.11.2009, 02:34  [ТС]     Пишем свой интерпретатор языка BASIC #249
Я сейчас читаю доку по autoconf,пока нашёл это http://www.gnu.org/software/libtool/...neric-Programs и это http://www.gnu.org/software/libtool/...cular-Programs. Мне нужен какой-то макрос,навроде AC_PROG_XTERM,но его там нет,и веть нужно ещё,чтобы в зависимости от проверки на существование писался define в config.h или в похожий файл.
Т.е. например,так
C++
1
#define PATH_TO_XTERM "/usr/bin/xterm"
А как это сделать?
Я помню,ты писал,что не работал с autotools,так что это,скорее вопрос риторический.Придётся наверное отдать всё во власть пользователя и как-то создавать .ini или что-то в этом духе.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
28.11.2009, 10:56     Пишем свой интерпретатор языка BASIC #250
Ну до реальных пользователей пока далеко, так что с autoconf'ом точно можно не спешить и отложить на потому. Потому как в процессе отладки ты все настройки сам можешь сделать.

Просто "пользователь" в данном случае можно рассматривать в двух контекстах:
- тот, кто программой будет пользоваться. Все возможные фичи в интерфейсе (опции командной строки в интерпретаторе и опции в gui и всевозможные кнопочки) надо закладывать сразу и всегда об этом помнить
- тот, кто программу будет собирать из исходников. Грубо говоря, в этом случае все настройки технически решаются через файл config.h. Как вариант ты его создаёшь руками и тестируешь в разной комбинации опций конфигурации системы, а уже под конец пишешь configure, который в автоматическом режиме создаст файл config.h

Получается так, что по второму пункту проблема локализована, и технически решается в узком месте, где вообще не нужно затрагивать исходники программы. А первая проблема - это то, что по большому счёту сильно влияет на работу всего комплекса и удобство пользования. И в сторонке не всегда это получается сделать, поэтому надо как можно раньше в процессе программирования/проектирования закладываться на эти свойства
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
30.11.2009, 08:06  [ТС]     Пишем свой интерпретатор языка BASIC #251
Подглядел,как делают define-ы с помощью configure: написал в configure.in
configure.in
Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AC_CONFIG_HEADERS(Basin-IDE/config.h:Basin-IDE/config.h.in)
 
AC_MSG_CHECKING([for /usr/bin/xterm and define PATH_TO_XTERM])
if ( test -e /usr/bin/xterm ); then 
cat >>./Basin-IDE/config.h.in <<_ACEOF
#ifndef PATH_TO_XTERM
#define PATH_TO_XTERM "/usr/bin/xterm"
#endif
_ACEOF
AC_MSG_RESULT([defined])
else
AC_MSG_FAILURE(
  [/usr/bin/xterm program not found.Please view configure.in file,\
string number 39-42 to fix the problem.])
fi

Там я временно сделал вариант с ошибкой,так как не знаю,где лежит xterm на других системах по умолчанию. А программу всё-таки вызываю непосредственно,так чтобы она была под контролем IDE,а не внешнего терминала.Поэтому,скорее всего,придётся делать окошко для варианта успешного вывода (печать результатов программы),например по некому сигналу открывается окошко с текстовым полем.Меня только смущает то,что IDE по сути,толжна быть только обёрткой для программы,а тут получится вмешательство в стандартный вывод.Обычно же IDE вызывает внешнюю программу (cmd или xterm).
Вызываю интерпретатор так:
mainwindow.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* -------------------------------------------------------------------------- */
  void MainWindow::readErr()
  {
     arr = new QByteArray;
     *arr = console->readAllStandardError();
     errorsList ->insertPlainText( arr->data() );
  }
/* -------------------------------------------------------------------------- */
  void MainWindow::run()
  {
    console = new QProcess;
    connect(console, SIGNAL(finished(int,QProcess::ExitStatus)),
                                                         this, SLOT(readErr()));
    console->setReadChannel (QProcess::StandardError);
  #ifdef _UNIX_
    console->start("./basin",QStringList() << *args << filename);
  #endif
  #ifdef _WINDOWS_
    // ...
  #endif
  }

И в errorsList печатаются сообщения красным цветом.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
30.11.2009, 08:43     Пишем свой интерпретатор языка BASIC #252
Цитата Сообщение от #pragma Посмотреть сообщение
А программу всё-таки вызываю непосредственно,так чтобы она была под контролем IDE
А для чего? Вообще это очень плохой метод работы. С IDE и без IDE программа должна работать эквивалентно. Если работал на виндовых IDE, то во всех случаях консольной программа запускается в отдельно консоли. Понятно, что в случае юниксов это требование менее жёсткое, т.к. в отличие от виндов имеется множество разных реализаций терминалов, а в твоём случае попросту нужно сделать свою реализацию терминала (или эмулятор терминала), встроенную в IDE. Только вот не пойму, что тебе это даст
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
30.11.2009, 10:19  [ТС]     Пишем свой интерпретатор языка BASIC #253
Просто я не представляю,как потом считать данные из потоков ошибок и вывода,ведь если я буду запускать консоль как дочерний процесс IDE,то считываться будут только данные,которые пишет этот процесс,а не запускаемый в консоли? Или в Unix-системах не существует разграничений для процессов,и все данные идут в один общий поток? Вот это мне не понятно. Постараюсь уточнить свой вопрос: допустим,есть файл - поток вывода stdout,запускается программа-терминал,у неё есть свой вывод (свои ошибки или ещё что-то),а программа,запускаемая внутри терминала имеет свой вывод(в данном случае это интерпретатор,и выводится результат программы на QBASIC). Пишутся ли все данные в один поток вывода? С этим и было связано моё затруднение,ведь если я создаю процесс для консоли,и передаю ей в качестве параметра имя программы,то как потом считать вывод этой программы?
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
30.11.2009, 10:34     Пишем свой интерпретатор языка BASIC #254
Цитата Сообщение от #pragma Посмотреть сообщение
Просто я не представляю,как потом считать данные из потоков ошибок и вывода,ведь если я буду запускать консоль как дочерний процесс IDE,то считываться будут только данные,которые пишет этот процесс,а не запускаемый в консоли?
Всё, понял. Надо подумать. Если просто в терминале запустить процесс, а из него ещё один процесс, то извне они будут пользоваться одним и тем же stdout'ом, т.к. shell'ы при порождении нового процесса настраивают файловые дескрипторы таким образом, что они ссылаются на одни и те же потоки (по сути делается dup2, как в том примере про трубопровод). Правда я никогда не пытался запустить терминал, и уже перехватывать его (терминала) ввод-вывод. По логике вещей здесь уже ситуация другая. Тут надо знающих людей поспрашивать
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
30.11.2009, 17:34     Пишем свой интерпретатор языка BASIC #255
В общем, знатоки утверждают, что при запуске черз нормальный терминал подобная операция перехвата ввода-вывода будет слишком геморройной. Не говоря уж о том, что кроссплатформенно сделать в принципе не получится, а потому их предложение так же сводилось к тому, что проще будет использовать самодельную эмуляцию терминала. Причём говорили, что в Qt вроде бы есть какая-то стандартная компонентя для консоли

Цитата Сообщение от #pragma Посмотреть сообщение
а тут получится вмешательство в стандартный вывод
Должно быть не вмешательство, а перехват ввывода. Т.е. интепретатор делает printf, а IDE должна перехватить то, что выдается в стандартный вывод и нарисовать это на своём терминале. В случае отладчика тебе надо будет ещё и ввод подсовывать (хотя ввод и для INPUT'а нужен) - его так же придётся моделировать в самодельном метрминале
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
20.12.2009, 02:45  [ТС]     Пишем свой интерпретатор языка BASIC #256
Пытаюсь сделать конструкцию
PureBasic
1
2
3
SUB NAME (params) 
  ' ...
END SUB
На стадии создания представления всё вроде ясно - создаём список функций,у каждой есть своё подобие стека,который будет передаваться параметром для поиска в нём переменных,ну и может ещё параметр-флаг,который будет сигналить,есть ли SHARED-переменные,и нужно ли искать в глобальной области.
А как быть с самой интерпретацией run-time? По идее нужно тоже передавать нужный стек параметром,только вот как метка вызова функции будет знать,какой стек нужно выбрать? Придётся делать общую переменную для всех stmt's,содержащую указатель на текущий стек? Или только для меток делать указатель на нужную функцию в списке..
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
20.12.2009, 11:35     Пишем свой интерпретатор языка BASIC #257
Как интерпретировать процедуры через промежуточное представление - я вот так с ходу и не скажу. Проблема именно в рекурсивных вызовах. Видимо надо уже над переменными строить надствройку в виде некоторой страницы (которая по сути пул памяти, хранилище). Т.е. при создании переменной ты ещё и указываешь и страницу, в которой эта переменная живёт. Страница для глобальных переменных будет одна. Для локалов и формальных параметров каждой процедуры заводится своя страница и все переменные ссылаются на эту страницу. При наличии рекурсивного вызова процедуры всю страницу можно куда-нибудь в сторону скопировать. Таким образом с точки зрения нашего представления вроде бы остаётся всё как есть: внутреннее представление каждой переменной всегда находится в одном и том же месте памяти интерпретатора. Но это - некий технический бубен, потому как наше представление было рассчитано на случай без функций.

Как вариант можно строить наше же представление. С незначительными отличиями:
- вместо одной цепочки операторов будет несколько цепочек (на каждую функцию отдельная цепочка)
- каким-то образом нужно отличать глобальные переменные от локальной переменной функции. Способов миллион от страниц, про которые говорил выше, до списков переменных, привязанных к каждой процедуре
Оба отличия с технической точки зрения НЕ являются сложными и НЕ являются бубнами/подпорками/костылями. Получится так называемое представление высокого уровня, которое попросту отражает исходник программы в удобный вид для обработки в интерпретаторе (ну и плюс отсеиваем пользовательские ошибки).

Представление верхнего уровня будет использоваться только для хранения "образа программы". Далее нужно построить ещё одно промежуточное представление, которое удобно для интерпретирования. Это будет представление низкого уровня, которое внутри себя будет читывать динамические стеки и прочую лабуду, возникающую при исполнении. Компиляторы, как правило строятся на двух промежуточных представлениях с тем же назначением: высокоуровневое представление, чтобы иметь образ программы, и низкоуровневое, чтобы с него генерировать код.

Высокоуровневое представление оперирует понятиями "объект" ("переменная"). Низкоуровневое - понятиями "память" и "регистр".

Для исходника

PureBasic
1
A = B + C
на высоком уровне мы имеем операцию типа (пишу несколько условно, чтобы отобразить тот факт, что у нас ето всё в одной операции (statement), который по сути имеет некую древовидную конструкцию.

Код
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
Для реальной машины и компилятора в каждом случае надо знать, с каким форматом данных мы работаем (т.е. это int32, int64, float32, ...). От формата данных зависит конкретная аппаратная операция для чтения и арифметики. В случае интерпретатора можно не заморачиваться и считать, что наши "регистры" содержат Value, а потому разбор конкретного типа данных происходит в момент арифметической операции над Value (что у нас уже реализовано)

Адрес глобальных переменных в run-time всегда будет одним и тем же. Адреса локальных переменных будут плавать (в зависимости от глубины стека). В этом случае понадобятся страницы (фрэймы) памяти. Все глобальные переменные будут распределены в одну страницу для глобалов. При входе (на исполнении) в каждую процедуру будет заводиться страница для локалов процедуры и туда помещаться все локалы. Адрес локальной переменной будет вычисляться на основании адреса текущей страницы локалов.

Да, всё это на первый взгляд слишком непонятно. Непонятно так же, как это будет выглядеть технически. Но для начала надо хотя бы в общем виде понять концепцию двух представлений. Причём пока писал, уже понял, что низкоуровневое представление можно значительно сократить. А так же построить его на базе высокоуровневого, т.к. на первый взгляд раличие будет только при работе с переменными. Но это мысли "на потом", для начала понять концепцию.

Прежде, чем это делать, тебе всё-таки нужно навести порядок во внутренностях интерпретатора, потому что закопаешься. И ещё сделать нормальную печать промежуточного представления, чтоб "глазами" можно было увидеть всё, что в нём есть и как оно друг с другом связано (как это смотреть в моей версии, я вроде бы уже писал). В gcc эти печати даже не стали прятать от пользователя (хотя во всех коммерческих компиляторах в пользовательских версиях) она отключена. Запусти "gcc t.c -da" и ты увидишь файлы "gcc t.c.*", в которых расписано представление после каждого прохода или оптимизации. Без подобной НОРМАЛЬНОЙ печати компилятор реализовать просто невозможно
RazorQ
 Аватар для RazorQ
574 / 341 / 9
Регистрация: 06.02.2009
Сообщений: 1,386
20.12.2009, 12:06     Пишем свой интерпретатор языка BASIC #258
Evg, #pragma, я тут прочитал, что вы хотели бы иметь графический интерфейс к программе. Могу помочь. Предлагайте свои идеи, я попробую реализовать. От вас мне будет нужна только программа в скомпилированном виде. И некоторые нюансы можно будет обсудить потом.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16827 / 5248 / 321
Регистрация: 30.03.2009
Сообщений: 14,129
Записей в блоге: 26
20.12.2009, 13:23     Пишем свой интерпретатор языка BASIC #259
Цитата Сообщение от RazorQ Посмотреть сообщение
Evg, #pragma, я тут прочитал, что вы хотели бы иметь графический интерфейс к программе. Могу помочь. Предлагайте свои идеи, я попробую реализовать. От вас мне будет нужна только программа в скомпилированном виде. И некоторые нюансы можно будет обсудить потом.
#pragma, как вариант я поддерживаю такое предложение. Потому как в реальной жизни именно так и происходит. С другой стороны у тебя есть желание самому разобраться в Qt и самому наваять графическую часть.

Как вариант можешь делать сам, но параллельно "заказать" работу. Потому как одна программа низкого уровня (интерпретатор) и несколько программ высокого уровня (графическая оболочка) - это совершенно нормальное явление. А заодно и в чужом коде посмотришь, как делается что-то. Ещё прочувствуешь, чем принципиально отличается работа в команде от работы в одиночку: когда есть много мыслей в голове это одно, а когда эти мысли надо объяснить людям - совсем другое.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
20.12.2009, 13:41     Пишем свой интерпретатор языка BASIC
Еще ссылки по теме:

Пишем свой чекер C++
Не удается откомпилировать интерпретатор М-языка C++
Пишем свой класс, спецификатор доступа protected C++

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
RazorQ
 Аватар для RazorQ
574 / 341 / 9
Регистрация: 06.02.2009
Сообщений: 1,386
20.12.2009, 13:41     Пишем свой интерпретатор языка BASIC #260
Значит ждем решения #pragma.

Добавлено через 1 минуту
Evg, ты не мог бы дать мне список ключевых слов и саму программку. Я пока поэкспериментирую.
Yandex
Объявления
20.12.2009, 13:41     Пишем свой интерпретатор языка BASIC
Закрытая тема Создать тему
Опции темы

Текущее время: 12:34. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2016, vBulletin Solutions, Inc.
Рейтинг@Mail.ru