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

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 1509, средняя оценка - 4.80
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
#1

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

20.06.2009, 20:03. Просмотров 191673. Ответов 464
Метки нет (Все метки)

*****************
Благодаря форуму и Evg в частности интерпретатор развивается, потихоньку превращаясь в простенький интерпретатор QBASIC.
Некоторые из самых старых версий сохранились в теме и ссылки на них будут добавлены в это сообщение,а также ссылки на другие темы,связанные с этой.

Репозиторий с проектом находится тут, там же есть возможность в браузере посмотреть историю ревизий (английский в логах весьма примитивен,комментарии и рекомендации можете писать в личку),а также скачать самый последний архив репозитория в формате .tar.gz
Если кто-то пользуется Subversion,скачать исходники можно так:
Код
svn co https://basin.svn.sourceforge.net/svnroot/basin basin
Эти темы возникли в результате моих вопросов по ходу написания:
Технический приём для формирования согласованных данных
Makefile: как с использованием gcc строить автоматические зависимости от .h файлов?
Вопрос по svn (Subversion)
Создание системы тестирования ПО.
Вопрос про разные реализации бэйсиков
Можно ли выразить порядковый номер элемента массива через индексы?
[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;
30
Лучшие ответы (1)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.06.2009, 20:03
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Пишем свой интерпретатор языка BASIC (C++):

Пишем свой чекер - C++
Я хочу написать свой чекер, но не знаю с чего начать? Кто знает основные принцип работы чекеров прошу объясните.

пишем свой троян с нуля - C++
Всем привет)))соглашусь, что изобретаю велосипед, но хочется сделать все своими ручками не прибегая к open source и т.п. для повышения...

Пишем свой класс, спецификатор доступа protected - C++
Всем привет! Из книги Р. Лафоре относительно спецификатора доступа protected: Далее пишется следующее: Возникает вопросы:...

Не удается откомпилировать интерпретатор М-языка - C++
Задача: взять интерпретатор М-языка на сайте http://cmcmsu.no-ip.info/2course/model.lang.parser.sample.htm и переработать его, добавив в...

Интерпретатор небольшого языка программирования на С++ - C++
Здравствуйте, уважаемые форумчане! Я тут где-то год назад прочитал тему Evg и #pragma о создании интерпретатора, меня эта тема очень...

Написать Интерпретатор Программного Языка(собственного) - C++
Здраствуйте! Кто знает C++ помогите пожалуйста с реализацией данного задания!!! Пожалуйста, очень надо. сроки поджимают. Есть...

464
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
09.10.2009, 17:07 #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 */
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
10.10.2009, 00:29  [ТС] #182
Добавил ещё две опции командной строки:
Bash
1
2
--trace-on
--trace-silent
Насчёт второй не уверен,нужна ли,или просто доделаю,чтобы печаталась точная строка в файле,а то не понятно,где происходит транзакция записи.
Добавил
PureBasic
1
OPTION TRACE <val>
,правда я не понял точного назначения TRACE_CMD и TRACE_WRITE,что ты предложил,поэтому просто сделал единую опцию.
Насчет генератора кода: думаю,лучше пока отложить,а то проект становится громоздким,и добавление новых опций превращается просто в кодинг,это конечно,тоже интересно,но хочется узнать что-то новое,а не топтаться на месте.В любом случае,это можно будет добавить позже.Нужно решить какие конструкции ещё необходимы (ну INPUT я сделаю),имею в виду те,которые нужны будут чтобы двигаться дальше(где-то ты писал,что можно попробовать работу с графикой).Тут понятно,нужны встроенные функции как RND,SIN,COS и т.д.Я вижу это примерно так: на уровне представления это statement,содержащий указатель на элемент класса,у которого свои свойства и т.д.На момент интерпретации все действия выполняются реализацией класса.Ну,не знаю,насколько это правильно,может,предложишь другой вариант.
Также очень интересует вопрос кроссплатформенности,и написание графического интерфейса.
0
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
10.10.2009, 00:53 #183
По поводу синусов, косинусов и прочего идея такая. Заводится тип выражения - процедурный вызов. Только вызывается не пользовательский код, а встроенные функции. Функции передаётся список value'ов - параметры. И возвращается value - результат

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

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

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

Как вариант можно попробовать написать примитивный IDE. Ещё как вариант - встроенный командный отладчик. По типу gdb, но поскольку у нас интерпретатор, то должно быть всё в одном флаконе
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
10.10.2009, 01:13  [ТС] #184
Да,хороший вариант с функциями.То есть функции (встроенные) будут разрешены только внутри выражений,чтобы не путаться в statements.
Qt по вкусу,надо будет попробовать.Интересует вариант создания отдельного пакета к программе,то есть чтобы были возможны оба варианта-консольный и графический.Есть же программы,к которым можно просто доустановить пакет с графическим интерфейсом,и это никак не мешает использовать прогу в консоли.Правда как это делается,сейчас пока загадка,надеюсь в своё время узнаю.
0
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
10.10.2009, 11:11 #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
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 15:07  [ТС] #186
Вчера попробовал скомпилировать исходники на Widows XP,компилятор g++,всё скомпилировалось гладко.Значит,пока программа кроссплатформенная.Проверил тестовый исходник-работает.
Есть небольшая неувязка из-за того,что винда конец строки по другому делает.Или по другой причине,точно не знаю,особо не засиживался,так,поглядел.В-общем,номера строк при ошибках вычисляются неправильно: число точно в два раза больше,чем надо.Я думаю,это связано с окончанием строк как-то,но точно не знаю.И что мне с этим делать?Делить тупо пополам?
Ну ELSEIF вроде сделал,твой тест проходит,также SIN COS TAN ATN,остальные функции постараюсь не затягивать и сделаю.
Сейчас начал потихоньку читать про разные графические библиотеки,пришёл к выводу,что xlib лучше не использовать.Остановился на SDL,она вроде и кроссплатформенная даже,так что проблем с переносимостью будет меньше.Ну и хорошего вроде написали про SDL,надо попробовать.
Добавлено
А да,ещё сделал трассировку ав твоём варианте,теперь OPTION TRACE ACTION <val> и OPTION TRACE TREE <val>,и там по мелочи опции командной строки поменял,всё можно увидеть в option.cpp
0
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
17.10.2009, 15:13 #187
Про строки - перевод строки под виндами (и dos'ом) занимает 2 символа: '\n' и '\r'. Лечится двумя способами:
1. Файл открывать в текстовом режиме. В Си это fopen (..., "r") в отличие от "rb" для бинарного режима. Как в Си++ - не знаю, но там наверняка есть что-то подобное
2. Если файл всё же открывается в бинарном виде, то символ '\r' просто игнорировать (т.е. он трактуется как пробел и знак табуляции)

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

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

Она ж консольная, а консольные практически везде одинаково работают
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 15:18  [ТС] #188
Сделал функции возможными только в выражениях.Добавил тип выражения procedure,как ты и предложил,содержит тип функции из enum и expression-параметр для передачи.На интерпретации вызывается функция по типу,вычисляется параметр,и подставляется в функцию sin cos и т.д. При этом параметр приводится к типу double,пока только эта версия существует.
0
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
17.10.2009, 16:07 #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), а потом будет больше ясности на предмет того, как техничеси организовывать такую схему
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 17:06  [ТС] #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,в смысле что его реализация может зависеть от платформы?Вроде же всё работает..Как бы это стандартная конструкция. Или ты уже метишь в создание вообще какой-то глобальной обёртки для языков?
0
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
17.10.2009, 18:19 #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 минуты
Что-то опции нифига не работают. Да и посмотреть хотелось не трассу исполнения, а просто все операторы промежуточного представления
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.10.2009, 18:46  [ТС] #192
Странно,у меня всё нормально.В ревизии 40 уже всё сделано и я проверял-работает.. Попробуй не с командной строки,а в исходнике через OPTION,а с проблемой разберёмся,думаю.Может,ревизия не та?Я 40-вую только вчера скомиттил
0
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
17.10.2009, 23:15 #193
Я свежую смотрел, может я не так запускаю?

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

Добавлено через 2 минуты
Цитата Сообщение от Evg Посмотреть сообщение
Такое ощущение, что арктангенс неправильно считается. Арктангенс от маленьких углов должен быть ббольшим, а у тебя этого не наблюдается
Блин... перепутал арктангенс и котангенс
0
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
18.10.2009, 04:13  [ТС] #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).
0
Evg
Эксперт CАвтор FAQ
17934 / 6162 / 408
Регистрация: 30.03.2009
Сообщений: 16,917
Записей в блоге: 27
18.10.2009, 12:01 #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 минуты
Собственно, опять вернусь к прежнему вопросу. А просто печать представления у тебя есть? Как ты вообще просекаешь, что в итоге у тебя построилось? Тут показано то, что печатается у меня. В большинстве случаев при ошибках мне достаточно посмотреть на эту печать, чтобы увидеть, что там внутри не так построилось. А что-то подобное у тебя есть?
1
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
18.10.2009, 12:01
Привет! Вот еще темы с ответами:

Написать интерпретатор программного языка -помощь - C++
Здраствуйте! Ребят, кто хорошо разбирается в C++ помогите пожалуйста с реализацией данного задания или хотя бы подтолкните к решению,...

Интерпретатор/компилятор ассемблер-подобного языка - C++
Привет! Чую, что изобрёл велисипед, даже скорее велопарк, но всё же, поделюсь: Некоторое время назад начал изучать кресты, в целом...

Интерпретатор музыки стандарта BASIC PLAY на С++ - C++
У кого нибудь есть функция или класс, который сможет воспроизводить в С++ напрямую музыкальные строки, записанные в стандарте оператора...

Задание: разработать "Интерпретатор языка". С чего начать? - C++
Здравствуйте, вручили темку на курсовик, ну точнее как вручили, не успел взять то, что хотел - пришлось брать то, что осталось. Плоховато...


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

Или воспользуйтесь поиском по форуму:
195
Yandex
Объявления
18.10.2009, 12:01
Закрытая тема Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru