Форум программистов, компьютерный форум 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)
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
09.10.2009, 17:07     Пишем свой интерпретатор языка BASIC #181
Всё равно проблему не понял

PureBasic
1
2
3
4
5
6
7
IF A=1 THEN
  LET B=100
ELSEIF A=2 THEN
  LET B=200
ELSE
  LET B=300
END IF
На уровне представления эквивалентно коду

PureBasic
1
2
3
4
5
6
7
8
9
IF A=1 THEN
  LET B=100
ELSE
  IF A=2 THEN
    LET B=200
  ELSE
    LET B=300
  END IF
END IF
Добавлено через 2 минуты
Мой нынешний вариант
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
static void
syntax_StatementIF (void)
{
  expr_Expr_t *cond_expr;
  stmt_Statement_t *label_then, *label_else, *label_finish;
 
  /* На текущий момент IF уже прочитано. Читаем следующий символ */
  lex_GetToken ();
 
  /* Метка окончания конструкции (на неё будет сделан переход в конце каждой
   * из альтернатив) */
  label_finish = stmt_NewStatementLabelEmpty ("#finish");
 
  /* Условие перехода и условный переход. Переход будет "вперёд", так что
   * сразу же создаим висячие метки для альтернатив */
  cond_expr = syntax_CondExpr ();
  label_then = stmt_NewStatementLabelEmpty ("#then");
  label_else = stmt_NewStatementLabelEmpty ("#else");
  stmt_NewStatementCBranch (cond_expr, label_then, label_else);
 
  /* THEN */
  syntax_Expect (LEX_TOKEN_KW_THEN);
 
  if (lex_CurToken != LEX_TOKEN_EOL)
    {
      /* Однострочный вариант записи. В этом случае пустых альтернатив быть
       * не может. Есть THEN и обязан быть код по THEN. Если есть ELSE,
       * то обязан быть код по ветке ELSE */
 
      /* Метка #then, затем операторы по ветке THEN, затем переход на метку #finish */
      stmt_LinkEmptyLabel (label_then);
      syntax_NecessarySimpleStatementList ();
      stmt_NewStatementBranch (label_finish);
 
      /* Метка #else, затем операторы по ветке ELSE (если они есть) */
      stmt_LinkEmptyLabel (label_else);
      if (lex_CurToken == LEX_TOKEN_KW_ELSE)
        {
          lex_GetToken ();
          syntax_NecessarySimpleStatementList ();
          stmt_NewStatementBranch (label_finish);
        }
    }
  else
    {
      /* Многострочный вариант записи. В данном случае по альтернативе
       * могут быть и пустые строки */
 
    Then:
      /* EOL */
      syntax_Expect (LEX_TOKEN_EOL);
 
      /* Метка #then, затем операторы по ветке THEN, затем переход на метку #finish */
      stmt_LinkEmptyLabel (label_then);
      syntax_ComplexStatementList ();
      stmt_NewStatementBranch (label_finish);
 
      /* Метка #else, относящаяся к последнему сравнению */
      stmt_LinkEmptyLabel (label_else);
 
      /* В случае наличия ELSEIF по большому счёту повторяем код для IF'а,
       * единственно, что уже не допускаем однострочной записи. Веток ELSEIF
       * может быть несколько штук */
      if (lex_CurToken == LEX_TOKEN_KW_ELSEIF)
        {
          lex_GetToken ();
 
          /* Текущий условный переход. Метки label_then и label_else будут
           * воткнуты уже после исполнения 'goto Then' (см. ниже) */
          cond_expr = syntax_CondExpr ();
          label_then = stmt_NewStatementLabelEmpty ("#then");
          label_else = stmt_NewStatementLabelEmpty ("#else");
          stmt_NewStatementCBranch (cond_expr, label_then, label_else);
 
          /* THEN */
          syntax_Expect (LEX_TOKEN_KW_THEN);
 
          /* Далее повторяем код для THEN от IF, т.к. для ELSEIF всё то же самое */
          goto Then;
        }
 
      /* Альтернатива по ветке ELSE может и не присутсвовать */
      if (lex_CurToken == LEX_TOKEN_KW_ELSE)
        {
          lex_GetToken ();
          syntax_Expect (LEX_TOKEN_EOL);
          syntax_ComplexStatementList ();
        }
 
      /* END IF */
      syntax_Expect (LEX_TOKEN_KW_END);
      syntax_Expect (LEX_TOKEN_KW_IF);
    }
 
  /* Метка #finish */
  stmt_LinkEmptyLabel (label_finish);
} /* syntax_StatementIF */
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
10.10.2009, 00:29  [ТС]     Пишем свой интерпретатор языка BASIC #182
Добавил ещё две опции командной строки:
Bash
1
2
--trace-on
--trace-silent
Насчёт второй не уверен,нужна ли,или просто доделаю,чтобы печаталась точная строка в файле,а то не понятно,где происходит транзакция записи.
Добавил
PureBasic
1
OPTION TRACE <val>
,правда я не понял точного назначения TRACE_CMD и TRACE_WRITE,что ты предложил,поэтому просто сделал единую опцию.
Насчет генератора кода: думаю,лучше пока отложить,а то проект становится громоздким,и добавление новых опций превращается просто в кодинг,это конечно,тоже интересно,но хочется узнать что-то новое,а не топтаться на месте.В любом случае,это можно будет добавить позже.Нужно решить какие конструкции ещё необходимы (ну INPUT я сделаю),имею в виду те,которые нужны будут чтобы двигаться дальше(где-то ты писал,что можно попробовать работу с графикой).Тут понятно,нужны встроенные функции как RND,SIN,COS и т.д.Я вижу это примерно так: на уровне представления это statement,содержащий указатель на элемент класса,у которого свои свойства и т.д.На момент интерпретации все действия выполняются реализацией класса.Ну,не знаю,насколько это правильно,может,предложишь другой вариант.
Также очень интересует вопрос кроссплатформенности,и написание графического интерфейса.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
10.10.2009, 00:53     Пишем свой интерпретатор языка BASIC #183
По поводу синусов, косинусов и прочего идея такая. Заводится тип выражения - процедурный вызов. Только вызывается не пользовательский код, а встроенные функции. Функции передаётся список value'ов - параметры. И возвращается value - результат

Единственный геморрой - в бэйсике есть долбанная конструкция MID$, которая может стоять в том числе и в левой части присваивания. Но не думаю, что она очень уж часто используется

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

По поводу кроссплатформенности - понятное дело, что текстовый режим он и в африке текстовый режим. А графика - надо сначала понимать, как на разных системах она работает, а потом думать, как это реализовать. Т.е. если делать в лоб и использовать "обычные" библиотеки, то скорее всего под каждую ветку придётся писать отдельные коды. Можно попробовать и поработать через Qt, тогда по идее с переносом на другие платформы проблем не будет

Как вариант можно попробовать написать примитивный IDE. Ещё как вариант - встроенный командный отладчик. По типу gdb, но поскольку у нас интерпретатор, то должно быть всё в одном флаконе
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
10.10.2009, 01:13  [ТС]     Пишем свой интерпретатор языка BASIC #184
Да,хороший вариант с функциями.То есть функции (встроенные) будут разрешены только внутри выражений,чтобы не путаться в statements.
Qt по вкусу,надо будет попробовать.Интересует вариант создания отдельного пакета к программе,то есть чтобы были возможны оба варианта-консольный и графический.Есть же программы,к которым можно просто доустановить пакет с графическим интерфейсом,и это никак не мешает использовать прогу в консоли.Правда как это делается,сейчас пока загадка,надеюсь в своё время узнаю.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
10.10.2009, 11:11     Пишем свой интерпретатор языка BASIC #185
Цитата Сообщение от #pragma Посмотреть сообщение
Интересует вариант создания отдельного пакета к программе
Ну... в твоём случае под линухом, например, это можно было бы сделать в виде отдельной динамической библиотеки *.so. Под виндами это будет *.dll. Но для начала сделай, чтобы хотябы на одной платформе заработало. Или как вариант, сначала попробуй без графики под винду пернести. И ещё как вариант постоянно проверять под линухом и виндой

Добавлено через 37 минут
По поводу трассировки. Возьмём, например, программу (WHILE я по-человечески ещё не переделал, а потому он в дебильном синтаксисе)

PureBasic
1
2
3
4
5
6
7
8
9
10
DIM A(4)
LET I=1
WHILE I<4 DO
  IF I+1 < 5-I THEN
    LET A(I)=1
  ELSE
    LET A(I)=2
  END IF
  LET I=I+1
END_WHILE
Далее запускаю с опцией трассировки записи в переменные

Код
$ ./interp -w
TRACE: a.txt:2 WRITE I = 1
TRACE: a.txt:5 WRITE A(1) = 1
TRACE: a.txt:9 WRITE I = 2
TRACE: a.txt:7 WRITE A(2) = 2
TRACE: a.txt:9 WRITE I = 3
TRACE: a.txt:7 WRITE A(3) = 2
TRACE: a.txt:9 WRITE I = 4
По этой трассировке чётко видно, что проинициализированы только три элемента массива, а не 4, как хотелось бы. Ну и когда выясняешь, что в каком-то фрагменте кода переменная имеет не то значение, которое ожидалось, то по такой трассировке это можно легко найти. В качестве вариантов это можно дополнить фичами типа отслеживать печать только в какую-то конкретную переменную и т.д.

Трассировка исполнения

Код
$ ./interp -e
TRACE: a.txt:11 EXEC LET
TRACE: a.txt:2 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:4 EXEC LABEL
TRACE: a.txt:4 EXEC LET
TRACE: a.txt:5 EXEC BRANCH
TRACE: a.txt:6 EXEC LABEL
TRACE: a.txt:8 EXEC LET
TRACE: a.txt:9 EXEC BRANCH
TRACE: a.txt:10 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:4 EXEC LABEL
TRACE: a.txt:6 EXEC LET
TRACE: a.txt:7 EXEC LABEL
TRACE: a.txt:8 EXEC LET
TRACE: a.txt:9 EXEC BRANCH
TRACE: a.txt:10 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:4 EXEC LABEL
TRACE: a.txt:6 EXEC LET
TRACE: a.txt:7 EXEC LABEL
TRACE: a.txt:8 EXEC LET
TRACE: a.txt:9 EXEC BRANCH
TRACE: a.txt:10 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
В данном случае какой-то особенно пользы нет, т.к. трассировка записей однозначно вскрыло проблему и оказалось более наглядной. Но в случае каких нибудь сложных ветвлений, внутри которых вызываются какие-то прочие функции (типа отрисовки линий и окружностей) и при этом нет никаких записей, то трассировка исполнения может помочь увидеть "снимок" того, как отработала задача. Сейчас печатается имя statementa (того, который на уровне промежуточного представления). Для пользователя это неудобно, удобнее, когда печатается имя того оператора, который написан в исходнике - т.е. чтобы вместо CBRANCH и BRANCH печаталось "WHILE" и "WEND" и.т.п. Пока что-то лениво этим заморачиваться

Ну и как вариант комбинация этих двух трассировок

Код
$ ./interp -w -e
TRACE: a.txt:11 EXEC LET
TRACE: a.txt:2 WRITE I = 1
TRACE: a.txt:2 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:4 EXEC LABEL
TRACE: a.txt:4 EXEC LET
TRACE: a.txt:5 WRITE A(1) = 1
TRACE: a.txt:5 EXEC BRANCH
TRACE: a.txt:6 EXEC LABEL
TRACE: a.txt:8 EXEC LET
TRACE: a.txt:9 WRITE I = 2
TRACE: a.txt:9 EXEC BRANCH
TRACE: a.txt:10 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:4 EXEC LABEL
TRACE: a.txt:6 EXEC LET
TRACE: a.txt:7 WRITE A(2) = 2
TRACE: a.txt:7 EXEC LABEL
TRACE: a.txt:8 EXEC LET
TRACE: a.txt:9 WRITE I = 3
TRACE: a.txt:9 EXEC BRANCH
TRACE: a.txt:10 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:4 EXEC LABEL
TRACE: a.txt:6 EXEC LET
TRACE: a.txt:7 WRITE A(3) = 2
TRACE: a.txt:7 EXEC LABEL
TRACE: a.txt:8 EXEC LET
TRACE: a.txt:9 WRITE I = 4
TRACE: a.txt:9 EXEC BRANCH
TRACE: a.txt:10 EXEC LABEL
TRACE: a.txt:3 EXEC CBRANCH
TRACE: a.txt:3 EXEC LABEL
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 15:07  [ТС]     Пишем свой интерпретатор языка BASIC #186
Вчера попробовал скомпилировать исходники на Widows XP,компилятор g++,всё скомпилировалось гладко.Значит,пока программа кроссплатформенная.Проверил тестовый исходник-работает.
Есть небольшая неувязка из-за того,что винда конец строки по другому делает.Или по другой причине,точно не знаю,особо не засиживался,так,поглядел.В-общем,номера строк при ошибках вычисляются неправильно: число точно в два раза больше,чем надо.Я думаю,это связано с окончанием строк как-то,но точно не знаю.И что мне с этим делать?Делить тупо пополам?
Ну ELSEIF вроде сделал,твой тест проходит,также SIN COS TAN ATN,остальные функции постараюсь не затягивать и сделаю.
Сейчас начал потихоньку читать про разные графические библиотеки,пришёл к выводу,что xlib лучше не использовать.Остановился на SDL,она вроде и кроссплатформенная даже,так что проблем с переносимостью будет меньше.Ну и хорошего вроде написали про SDL,надо попробовать.
Добавлено
А да,ещё сделал трассировку ав твоём варианте,теперь OPTION TRACE ACTION <val> и OPTION TRACE TREE <val>,и там по мелочи опции командной строки поменял,всё можно увидеть в option.cpp
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
17.10.2009, 15:13     Пишем свой интерпретатор языка BASIC #187
Про строки - перевод строки под виндами (и dos'ом) занимает 2 символа: '\n' и '\r'. Лечится двумя способами:
1. Файл открывать в текстовом режиме. В Си это fopen (..., "r") в отличие от "rb" для бинарного режима. Как в Си++ - не знаю, но там наверняка есть что-то подобное
2. Если файл всё же открывается в бинарном виде, то символ '\r' просто игнорировать (т.е. он трактуется как пробел и знак табуляции)

Про функции посмотрю, правда копаться в исходниках неохота. В двух словах расскажи, как оно сделано

Добавлено через 30 секунд
> Значит,пока программа кроссплатформенная

Она ж консольная, а консольные практически везде одинаково работают
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 15:18  [ТС]     Пишем свой интерпретатор языка BASIC #188
Сделал функции возможными только в выражениях.Добавил тип выражения procedure,как ты и предложил,содержит тип функции из enum и expression-параметр для передачи.На интерпретации вызывается функция по типу,вычисляется параметр,и подставляется в функцию sin cos и т.д. При этом параметр приводится к типу double,пока только эта версия существует.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
17.10.2009, 16:07     Пишем свой интерпретатор языка BASIC #189
А у тебя есть какая-нибудь возможность распечатать промежуточное представление по типу того, как делалось в моём примере. ЧТобы глазами "увидеть", как оно устроено

Добавлено через 13 минут
  • Итвой и мой интерпретаторы фатально ломаются на "LET A=1/0"
  • Тоже самое наверное будет, если в тангенс подать значение, эквивалентное 90 градусам (в этой точке тангенс не определён), или в арксинус значение большее единицы по модулю и т.п.
  • Такое ощущение, что арктангенс неправильно считается. Арктангенс от маленьких углов должен быть ббольшим, а у тебя этого не наблюдается
  • Для порядку надо завести предопределённую переменную PI (без неё работа с синусами и косинусами лишена смысла)

Добавлено через 17 минут
А теперь то, к чему я клонил, когда говорил про функции

1. Выносим функцию expr_CalcProcedure в отдельный модуль (который назвать function.cpp или ещё как, потому как к выражению он уже не очень относится). В результате чего имеем следующее: при добавлении новой функции мы делаем исправления только "вверху" (в разборе синтаксиса) и "внизу" (в процессе вычисления этого call'а), а вся серёдка (промежуточное представление, интерпретация) не меняются.
2. Поддерживаем возможность работы, когда у функции несколько входных переменных
3. А теперь самое интересное. Заводим в function.cpp некую внутреннюю функцию "__print". "Внутреннюю" в том смысле, что пользователь напрямую её вызвать не может. Далее в процессе разбора синтаксиса вместо оператора SK_PRINT строим некий фиктивный оператор CALL (который по сути то же самое, что ты сейчас с делал в выражении) и будем строить операцию вызова процедуры "__print" и формировать список параметров к ней. Такое поведение даёт тебе тот же самый бонус, что и в пункте 1: по сути поведение PRINT'а у тебя настраивается только "сверху" и "снизу", а вся "серёдка" остаётся без изменений.

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

В результате, то, что я называл "серёдкой" фактически выполняет роль некоторого движка. "Сверху" ему задаётся набор операторов, основная часть которых является фиксированной (LET, Условные переходы), а всё остальное реализуется в виде операции процедурного вызова. "Снизу" задаётся вся логика работы процедурных вызовов. Код движка при этом по большому счёту не модифицируется. При этом "сверху" ты можешь понимать синтаксис какого-то другого языка и посадить его на тот же самый движок, возможно добавив что-то "снизу"

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

Хз насколько понятно пояснил, но на мой взгляд пойдя по такому пути можно сделать интерпретатор, в который очень легко добавляется новая функциональность. Тезнически может быть проще будет сначала реализовать несколько графических операторов (POINT, LINE, CIRCLE), а потом будет больше ясности на предмет того, как техничеси организовывать такую схему
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 17:06  [ТС]     Пишем свой интерпретатор языка BASIC #190
Цитата Сообщение от Evg Посмотреть сообщение
А у тебя есть какая-нибудь возможность распечатать промежуточное представление по типу того, как делалось в моём примере. ЧТобы глазами "увидеть", как оно устроено
Это как раз делается опцией в исходнике OPTION TRACE TREE <val> (то есть трассировка дерева при выполнении) или из командной строки(см.ниже опции)
А OPTION TRACE ACTION <val> выполняет роль трассировки действий,производимых с данными.Пока это только для LET,но задумано так,что для каждой инструкции можно добавить свою трассировку(например для графических функций это может быть успешная отрисовка чего-то,для циклов это может быть переход по метке или ещё что-то.
Возможные опции командной строки на данный момент
Bash
1
   --noinit-errors | --trace-act | --trace-tree | --trace-loud
trace-loud это для печати в рабочую консоль всей трассировки,по умолчанию пишется в файл.
Насчёт остального,это очень интересно.В принципе,для графики я так и задумывал,что будет некая "обёртка" в виде класса graphics.h +.cpp которая уже непосредственно общается с SDL,чтобы в случае чего можно было сменить графическую библиотеку по надобности,даже пусть она и кроссплатформенная.Правда насчёт PRINT я немного не понял,зачем делать для него CALL,в смысле что его реализация может зависеть от платформы?Вроде же всё работает..Как бы это стандартная конструкция. Или ты уже метишь в создание вообще какой-то глобальной обёртки для языков?
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
17.10.2009, 18:19     Пишем свой интерпретатор языка BASIC #191
Цитата Сообщение от #pragma Посмотреть сообщение
Правда насчёт PRINT я немного не понял,зачем делать для него CALL,в смысле что его реализация может зависеть от платформы?
Смысл этих действий в том, чтобы из промежуточного представления исключить "лишние" операторы и оставить минимально необходимый набор. С минимальным набором ты уже сталкивался - это построение операций управления. При этом получалось так, что добавляя новые конструкции языка ты вообще никак не затрагивал интерпретатор и промежуточное представление. С остальными операторами по большому счёту то же самое: промежуточное представление и интерпретацию не меняем, а меняем лишь "крайние" компоненты. Технически такая схема всегда более оправдана, т.к. при добавлении новой функциональности ограничиваешься минимально необходимыми правками, не меняющими логику работы интерпретации.

Да и в случае print'а. "Внизу" ты делаешь "тупую" реализацию: на входе получаешь набор Value'ов и тупо его распечатываешь. При этом "вылизывание" семантики PRINT'а в соответствии с тем, как оно должно быть в настоящем бэйсике, делается только "наверху" в момент синтаксического разбора. У тебя сейчас PRINT неправильно работает, но предположим, он работает правильно. Тогда для конструкции

PureBasic
1
PRINT "A=" ; A ; " B=", B
будет просто вызвана процедура __print, параметрами которой является следующий список значений:

строка "A="
целое (или плвающее) A
строка " B="
строка "\t" (табуляция) - т.к. там стоИт запятая, а не точка с запятой
целое (или плвающее) B
строка "\n" (перевод строки) - т.к. в конце у нас нет символа ";"

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

То же самое касается всех остальных операторов языка. Например, что касается LINE, то процедура "__line" должна тупо рисовать линию от одной точки до другой с заданным цветом. А вот в случае, если цвет не задан, то пусть "наверху" языковая часть решает, каким цветом надо рисовать в тех случаях, если цвет явно не указан. Это не задача процедуры "__line", это задача языковой части. Или, например, вроде бы возможно написать "LINE - (100, 100)", что означает линию от последней поставленной точки до точки (100,100). Опять-таки, пусть это решается "наверху", и в конечном итоге позовётся "__line" со всеми нужными параметрами

Цитата Сообщение от #pragma Посмотреть сообщение
Или ты уже метишь в создание вообще какой-то глобальной обёртки для языков?
Не, такое не мечу Просто обозначил как некую теоретическую возможность, чтобы понятно было, что на каком уровне должно делаться и какая компонента за что должна отвечать. Я понимаю, что понять насколько это важно или нужно можно лишь тогда, когда сначала сделаешь "в лоб", а потом "правильно" и сравнить один и другой подход. Но в качестве некоего аналога такой концепции я тебе уже привёл построение операций управления. Смысл там такой же.

Добавлено через 2 минуты
Что-то опции нифига не работают. Да и посмотреть хотелось не трассу исполнения, а просто все операторы промежуточного представления
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 18:46  [ТС]     Пишем свой интерпретатор языка BASIC #192
Странно,у меня всё нормально.В ревизии 40 уже всё сделано и я проверял-работает.. Попробуй не с командной строки,а в исходнике через OPTION,а с проблемой разберёмся,думаю.Может,ревизия не та?Я 40-вую только вчера скомиттил
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
17.10.2009, 23:15     Пишем свой интерпретатор языка BASIC #193
Я свежую смотрел, может я не так запускаю?

Добавлено через 3 минуты
PureBasic
1
2
3
4
OPTION TRACE ACTION 1
LET r = 1
LET e = atn(0.0001)
PRINT e:PRINT
На таком исход нике тоже не пашет

Добавлено через 2 минуты
Цитата Сообщение от Evg Посмотреть сообщение
Такое ощущение, что арктангенс неправильно считается. Арктангенс от маленьких углов должен быть ббольшим, а у тебя этого не наблюдается
Блин... перепутал арктангенс и котангенс
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
18.10.2009, 04:13  [ТС]     Пишем свой интерпретатор языка BASIC #194
Это очень странно,потому что я сейчас проверил,вот выдача с этого исходника(правда нумерация строк немного попутана,но это поправимо).

PureBasic
1
2
3
source.bas 3 WRITE R = 1
 
source.bas 4 WRITE E = 0.0001
Может,ты не там смотришь? По умолчанию запись трассировки идёт в файл,имя файла - tracing.dat и он должен создаваться в папке с исходниками.
Меню я забыл обновить,оно должно вылазить на ошибках в опциях командной строки.Должно быть так:
Bash
1
2
3
4
5
6
7
8
9
Usage: ./basin [options] [filename]
[options] --noinit-errors - If in program are variables without
                initialization,error will be generated.           
              --trace-act - Enable tracing for actions,performed   
                in the program on run-time.                        
              --trace-tree - Enable tracing for program tree,to    
                view which instruction in the program is running.  
              --trace-loud - Write tracing output to console      
                instead of file (Default is to file).
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
18.10.2009, 12:01     Пишем свой интерпретатор языка BASIC #195
Цитата Сообщение от #pragma Посмотреть сообщение
По умолчанию запись трассировки идёт в файл,имя файла - tracing.dat
Ну и как я об этом мог догадаться? Правда для пользовательской опции такое поведение несколько нелогично, ну да фиг с ним пока

PureBasic
1
2
3
4
for i=1 to 5
LET e = atn(0.0001)
PRINT e:PRINT
next i
При исполнении этого ломаемся:

Код
$ ./basin a.bas
0.0001
file: a.bas line: 5 error: Cannot convert type
Добавлено через 4 минуты
Собственно, опять вернусь к прежнему вопросу. А просто печать представления у тебя есть? Как ты вообще просекаешь, что в итоге у тебя построилось? Тут показано то, что печатается у меня. В большинстве случаев при ошибках мне достаточно посмотреть на эту печать, чтобы увидеть, что там внутри не так построилось. А что-то подобное у тебя есть?
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
19.10.2009, 01:17  [ТС]     Пишем свой интерпретатор языка BASIC #196
Есть только печать дерева при исполнении,то есть видишь не всё построение,а только ветки,по которым идёт программа.Делается или OPTION TRACE TREE 1 в начале исходника,или --trace-tree из консоли.Печатать всё дерево не вижу смысла,если только для себя.То есть конечно для пользователя удобнее смотреть печать только тех инструкций,которые исполняются,навроде отладочного средства,но всё дерево печатать наверное не нужно?

>Ну и как я об этом мог догадаться? Правда для пользовательской опции такое поведение несколько нелогично, ну да фиг с ним пока

А мне,наоборот кажется это весьма логичным:представь,что пользователь рисует что нибудь-такая печать будет мешать процессу отладки,ломая изображение.Или данные печатаются в виде таблицы,да мало ли чего.Другое дело,надо это где-то явно указать,куда пишется отладка.Например,перед началом запуска программы пользователь получает сообщение на экране,о том куда будут записаны данные в случае печати в файл.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
19.10.2009, 12:41     Пишем свой интерпретатор языка BASIC #197
Цитата Сообщение от #pragma Посмотреть сообщение
Есть только печать дерева при исполнении,то есть видишь не всё построение,а только ветки,по которым идёт программа.Делается или OPTION TRACE TREE 1 в начале исходника,или --trace-tree из консоли.Печатать всё дерево не вижу смысла,если только для себя.То есть конечно для пользователя удобнее смотреть печать только тех инструкций,которые исполняются,навроде отладочного средства,но всё дерево печатать наверное не нужно?
Естественно, это нужно как внутреннее отладочное средство (т.е. НЕ для пользователя). Хороший набор отладочных средств в большинстве случаев позволяет найти ошибку только глядя на отладочные печати и трассы и не прибегать к отладчику

Цитата Сообщение от #pragma Посмотреть сообщение
А мне,наоборот кажется это весьма логичным:представь,что пользователь рисует что нибудь-такая печать будет мешать процессу отладки,ломая изображение
Правильно

Цитата Сообщение от #pragma Посмотреть сообщение
Или данные печатаются в виде таблицы,да мало ли чего.Другое дело,надо это где-то явно указать,куда пишется отладка.Например,перед началом запуска программы пользователь получает сообщение на экране,о том куда будут записаны данные в случае печати в файл.
Не надо за пользователя что-то решать. Это дурной тон в стиле микрософта. Пользователь должен иметь набор инструментов для того, чтобы саму решать что и как делать. В данном случае нужна пользовательская опция, чтобы задать, куда сливается вся трассировочная печать. Нормальным поведением было бы по опции --trace-tree печатать в stdout (т.е. туда же, куда и PRINT) - тогда будет чёткое представление что и как запускается относительно пользовательских PRINT'ов. По дополнительной опции (либо в случае, если задано --trace-tree=<file>) сливать печать в файл, указанный пользователю. Точно так же все имена файлов можно настраивать через OPTION. Т.е. предоставить набор средств, предоставляющий пользователю полную свободу выбора: "хочешь - мороженное, хочешь - пирожное" (c)

Добавлено через 13 минут
И что немаловажно - выбор настроек по умолчанию. Т.е. при просто поданной опции правильным (на мой взгляд) считается печатать в stdout, ибо увидев эту опцию в help'е и начав проверять"методом тыка", увидев лишнюю печать на экране пользователь хоятбы поймёт, что эта функциональность реально работает и что оно делает
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
22.10.2009, 00:02  [ТС]     Пишем свой интерпретатор языка BASIC #198
Вот касательно этого созрел вопрос.
Цитата Сообщение от Evg Посмотреть сообщение
3. А теперь самое интересное. Заводим в function.cpp некую внутреннюю функцию "__print". "Внутреннюю" в том смысле, что пользователь напрямую её вызвать не может. Далее в процессе разбора синтаксиса вместо оператора SK_PRINT строим некий фиктивный оператор CALL (который по сути то же самое, что ты сейчас с делал в выражении) и будем строить операцию вызова процедуры "__print" и формировать список параметров к ней. Такое поведение даёт тебе тот же самый бонус, что и в пункте 1: по сути поведение PRINT'а у тебя настраивается только "сверху" и "снизу", а вся "серёдка" остаётся без изменений.
Функции ведь возвращают какое-то значение,хотя и не всегда.Так сложилось исторически,что у меня при интерпретации кажая функция,обрабатывающая statement,возвращает указатель на следующую инструкцию в списке.А как ты видишь вариант с функциями? Я так немного прикинул,что будет функция interp_stmtCALL(stmt),и есть некий контейнер,в котором хранятся последние вычисленные значения функций.Но это всё же видится как костыль.Или нет? То есть если функция может выполнять роль инструкции(то есть быть не в выражении,а просто в программе) ,тогда нужно где-то хранить возвращаемое значение? Просто я начал перенос PRINT и в голову пришла такая мысль...
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
22.10.2009, 09:46     Пишем свой интерпретатор языка BASIC #199
В моём пониятии вызов функции - это всё-таки expr, а не statement. В случае print'а мы имеем statement с именем call, который внутри себя содержит expr с именем call. У себя я буду делать так, что все функции (__print, __line и т.п.) на вход будут понимать список value'ов (или масив - не суть) и возвращать value. Внутри выражений будет всё считаться точно так же, как и, например, для операции сложения - отдаём куда-то значения, а то, что нам возвращается, отдаём наверх.

Только вот с такой позиции пока не совсем понятно, как делать INPUT. Т.е. он содержит имя переменной, которую надо передавать НЕ как value, а наоборот, как адрес. Теоретически это рулится элементарно тем, что заводится value, означающее адрес на переменную. Но в моём варианте интерпретатора нет нигде взятие адреса на переменную, т.к. мне не хотелось выставлять в интерфейс прямой доступ к месту, где хранится value внутри переменной, но по ходу дела придётся

Исходя из такой концепции можно и арифметические выражения удалять и реализовывать их в виде функций. Таким образом промежуточное представление будет состоять по большому счёту из одних операций call. Единственное, я пока не уверен, а не будет ли это слишком большим извратом.

Добавлено через 22 минуты
Хотя с INPUT'ом я что-то торможу. Процедура __input возвращает в виде строкового value то, что введено с клавиатуры. А дальше просто строится операция присваивания с корректным учётом типа
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
25.10.2009, 20:00     Пишем свой интерпретатор языка BASIC
Еще ссылки по теме:

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

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

Или воспользуйтесь поиском по форуму:
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
25.10.2009, 20:00  [ТС]     Пишем свой интерпретатор языка BASIC #200
Сделал инструкцию SCREEN(ревизия 42),пока только в одном варианте,так как там куча всяких режимов,я даже не знаю,получится ли все их сделать,библиотеки разные,выбрал наиболее подходящий.(640 на 480,глубина цвета 16 бит,страница создаётся в системной памяти)
В-общем,если интересно попробовать(в принципе там ничего пока не увидишь,только чёрный экран с курсором),то тогда нужно указать режим возврата(то есть твой режим видео) а то экран так и останется в 640Х480,а мне даже понравилось,некоторые старые игрушки,Homm 3 например,не растягиваются у меня на полный экран,так как у меня widescreen,а это способ,возможно,насильно растянуть картинку как хочешь.
Менять нужно в файле graphics.h вот эти два enum'a
C++
1
2
3
4
5
6
7
8
9
10
11
  enum ScreenModeW { // Screen width
     x640  = 640,
     x800  = 800,
     x1400 = 1400
  };
/* -------------------------------------------------------------------------- */
  enum ScreenModeH { // Screen height
     x480 = 480,
     x600 = 600,
     x900 = 900
  };
и функцию
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
  template <class T> void graphics_SetSCREEN (T params)
  {
     switch (params->at(0)->Get_ival())
     {
        case 12: // SCREEN 12
           if (!graphics.isinit())
              if (SDL_Init( SDL_INIT_VIDEO ) < 0)
                 error(CANNOT_INIT_VIDEO_SUBSYS);
           Gr_Surface_t *display;
           if (SDL_VideoModeOK(ScreenModeW(x640),ScreenModeH(x480),
                                               BitDepth(_16bit),SDL_SWSURFACE|
                                                                SDL_FULLSCREEN))
              display = SDL_SetVideoMode (ScreenModeW(x640),ScreenModeH(x480),
                                               BitDepth(_16bit),SDL_SWSURFACE|
                                                                SDL_FULLSCREEN);
 
           else error(CANNOT_SET_DISPLAY);
           // TODO Just for test,need to be removed later
           sleep(2);
           if (SDL_VideoModeOK(ScreenModeW(x1400),ScreenModeH(x900),
                                               BitDepth(_32bit),SDL_SWSURFACE|
                                                                SDL_FULLSCREEN))
              display = SDL_SetVideoMode (ScreenModeW(x1400),ScreenModeH(x900),
                                               BitDepth(_32bit),SDL_SWSURFACE|
                                                                SDL_FULLSCREEN);
           //////////////////////////////////////////////
           assert (display);
           graphics.set_last_display(display);
        break;
        default: error(UNKNOWN_VIDEO_MODE);
     }
  }
Как раз после // TODO,просто прописать новые значения из перечислений.
И ксатати,если не нужно полный экран,то просто убрать " | SDL_FULLSCREEN"
Насчёт линий и прочего я не нашёл специальных функций,но зато обнаружил пример рисования пикселя на экране.Причём функцию нужно писать самому.Но главное нарисовать точку,а там уже "да здравствует математика".
Yandex
Объявления
25.10.2009, 20:00     Пишем свой интерпретатор языка BASIC
Закрытая тема Создать тему
Опции темы

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