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

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

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

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

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

*****************
Благодаря форуму и 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)
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.06.2009, 20:03     Пишем свой интерпретатор языка BASIC
Посмотрите здесь:

C++ Написать интерпретатор программного языка -помощь
Написать Интерпретатор Программного Языка(собственного) C++
C++ пишем свой троян с нуля
C++ Интерпретатор небольшого языка программирования на С++
C++ Интерпретатор музыки стандарта BASIC PLAY на С++
Интерпретатор/компилятор ассемблер-подобного языка C++
Пишем свой чекер C++
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
30.06.2009, 22:56  [ТС]     Пишем свой интерпретатор языка BASIC #21
Спасибо.Есть над чем поразмыслить.
Пару ньюансов хочу спросить.Я понимаю,это просто твой набросок,и всё же любопытно.
1)Для этих переменных :
C++
1
2
extern char *parser_CurTokenStr;
extern char *parser_CurFile;
Почему ты выбрал тип char *,а не строковый тип string?Из-за лишних вызовов конструктора класса?С другой стороны удобно работать с готовыми алгоритмами и не изобретать велосипед.
2) Почему для функций используется слово extern(для глобальных переменных понятно),ведь они и так должны прекрасно работать?
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
30.06.2009, 23:14     Пишем свой интерпретатор языка BASIC #22
Ответ на оба вопроса - потому что я всю жизнт на Си писал. Конечно используй string. А слово extern обычно для красоты пишут, чтобы подчеркнуть, что в данном моодуле (куда этот интерфейс будет подключаться) этой функции нет. Фактически я описал тебе файл Parser.h, а тебе к нему надо написать Parser.c
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
01.07.2009, 02:11  [ТС]     Пишем свой интерпретатор языка BASIC #23
Evg, появилась у меня такая мысль,после того,как немного прочитал про парсеры и т.д. Делаем таблицу,или вектор(можно состоящий из структур),в котором записаны заведомо ложные последовательности символов/можно и token'ов,и при каждом вызове парсера будем сверять текущее положение дел с этой таблицей.При нахождении ошибки можно выдать сообщение,продолжить парсить и т.д.И как вариант делать инкремент некой переменной в объекте структуры(коим является сама ошибка).Затем сортировать таблицу->в итоге получим,что самые частые ошибки будут лежать в начале вектора/таблицы.А при вызове алгоритма поиска эти ошибки на поверхности.Типа мини-предсказательный механизм.

Не по теме:

Но в-общем я не представляю ещё весь механизм,если это плохая идея,не отвечай зазря,неохота просто так дёргать вопросами...

Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
01.07.2009, 08:17     Пишем свой интерпретатор языка BASIC #24
Э... я просто не совсем понял, идею чего ты мне описал Как сделать вариант с продолжением работы после ошибки? Это называется error recovering (восстановление из ошибочных ситуаций). На твоём месте я бы пока этого не делал, т.к. это довольно сложная проблема и решать её имеет смысл только тогда, когда у тебя интерпретатор в более-менее серьёзном виде заработает
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
02.07.2009, 06:40  [ТС]     Пишем свой интерпретатор языка BASIC #25
Воодушевившись твоим интерфейсом,начал потихоньку писать с header-ов,чтобы хоть немного навести порядок.Захотел для начала сделать нормальный дебаггер,чтобы сразу было видно,где возможная ошибка и т.д.
Структуру дебаггера задумал сделать так,чтобы был некий переключатель
C++
1
#define DEBUG 1
,который можно было бы просто сменить на ноль и процесс отслеживания переменных выключался бы.Печать переменных идёт в файл по ходу работы программы.В каждой функции,где нужно,есть условие,проверяющее включен ли переключатель,и вызывающее главную функцию печати.У каждого модуля есть своя,особенная функция для печати,а в главной функции печати есть switch инструкция,запускающая нужную функцию согласно полученному имени.На мой взгляд,это очень удобная модель,но хотел бы выслушать советы,если есть и если ещё не надоел .
Ещё подумал,было бы неплохо иметь таблицу зарезервированных слов,чтобы легче искать было и код не разрастался.
Ну,конечно всё ещё в процессе,но если есть какие-то замечания,лучше,пока далеко не ушёл,выслушать их.Вот пока код.(Да,английский у меня хромает )
debugger.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
27
28
29
30
31
32
#ifndef DEBUGGER_H_INCLUDED
#define DEBUGGER_H_INCLUDED
 
// This switch is to enable
// program global debugging
// mode.1 is enabled and 0
// is disabled.
#define DEBUG 1
 
#include <fstream>
 
   // Each value of this enum
   // represent function which
   // debugging process is
   // working in.
   enum debug_CurFunc {
       PARSER_INIT,       // parser_Init (const std::string file_name)
       PARSER_GET_TOKEN   // parser_TokenType parser_GetToken ()
   };
 
  /** @brief Clearing the debugging log. */
   extern void debugger_Start();
 
  /** @brief Global printing of variables.
   *  @param debug_CurFunc is name of the function,
   *  that debugger is working with. */
   extern void debugger_Print(debug_CurFunc);
 
   extern std::ofstream debug_data_file;
   extern debug_CurFunc debug_CurFuncName;
 
#endif // DEBUGGER_H_INCLUDED

debugger.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
#include "debugger.h"
#include "parser.h"
 
//
 
   using std::ofstream;
 
 
   ofstream debug_data_file;
 
 
   void debugger_Start()
   {
       debug_data_file.open("data_file.dat");
       debug_data_file.clear();
       debug_data_file.close();
   }
 
   void debugger_Print(debug_CurFunc debug_CurFuncName)
   {
           switch (debug_CurFuncName)
           {
                case PARSER_INIT: {
 
                    debug_data_file.open("data_file.dat",ofstream::app);
 
                    if (debug_data_file.is_open())
 
                       parser_PrintCurToken (&debug_data_file);
 
                    debug_data_file.close();
                }
                break;
                case PARSER_GET_TOKEN: {
 
                    debug_data_file.open("data_file.dat",ofstream::app);
 
                    if (debug_data_file.is_open())
 
                       parser_PrintCurToken (&debug_data_file);
 
                    debug_data_file.close();
                }
          }
   }

parser.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
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
#ifndef PARSER_H_INCLUDED
#define PARSER_H_INCLUDED
 
#include <string>
#include <map>
 
   enum parser_TokenType
   {
     TOKEN_NULL = 0,
 
     TOKEN_CONST_INT,   // Integer constant
     TOKEN_CONST_FLOAT, // Floating point constant
 
     TOKEN_IDENT,       // Identificator
 
     TOKEN_KW_LET,      // Keyword LET
     TOKEN_KW_PRINT,    // Keyword PRINT
 
     TOKEN_DELIM_EQUAL, // Sign "="
     TOKEN_DELIM_PLUS,  // Sign "+"
     TOKEN_DELIM_MINUS, // Sign "-"
 
     TOKEN_EOL,         // End of line
     TOKEN_EOF,         // End of file
 
     TOKEN_LAST
   };
 
  /** @brief Parser initialisation.
   *  @param file_name contains path
   *  to file with source code,which parser is working with.
   *  @return In case of problem with file opening return false,
   *  and true if file was opened */
   extern bool parser_Init (std::string file_name);
 
  /** @brief Procedure parser_GetToken gets the next token from
   *  input stream.
   *  @param As a result writing variables parser_CurToken and
   *  parser_LastTokenStr.
   *  @return Procedure parser_GetToken return value of
   *  parser_CurToken. */
   extern parser_TokenType parser_GetToken ();
 
  /** @var Variables parser_CurFile, parser_CurLine contain
   *  information about place of current token in source
   *  code for debug purposes,parser_CurToken is the current
   *  token in line.parser_CurTokenStr contains value of
   *  token,if available. */
   extern std::string parser_CurFile;
   extern unsigned parser_CurLine;
   extern parser_TokenType parser_CurToken;
   extern parser_TokenType parser_NextToken;
   extern std::string parser_CurTokenStr;
 
  /** @var parser_ResWords contain reserved words
   *  for parser.Initialization is in
   *  parser_Init (std::string file_name) */
   extern std::map<const std::string,int>parser_ResWord;
 
  /** @brief Printer for debugging purposes
   *  of current token state.
   *  @param parser_CurFile is name of the
   *  file,that is being parced. */
   extern void parser_PrintCurToken (std::ofstream *parser_CurFile);
 
  /** @brief End of parser's work */
   extern void parser_Finish ();
 
#endif // PARSER_H_INCLUDED

parser.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <iostream>
#include <cctype>
#include "parser.h"
#include "debugger.h"
#include "driver.h"
 
//
 
   using std::ofstream;
   using std::endl;
   using std::map;
   using std::string;
 
   parser_TokenType parser_CurToken;
   parser_TokenType parser_NextToken;
   string parser_CurTokenStr;
   string parser_CurFile;
   unsigned parser_CurLine;
   map<const string,int>parser_ResWord;
 
   bool parser_Init (const std::string file_name)
   {
       parser_ResWord["LET"] = parser_TokenType(TOKEN_KW_LET);
       parser_ResWord["PRINT"] = parser_TokenType(TOKEN_KW_PRINT);
 
       source.open(file_name.c_str());
 
       if (source.is_open())
       {
           ::parser_CurFile  = file_name;
           ::parser_CurLine  = 0;
           ::parser_CurToken = TOKEN_NULL;
           ::parser_NextToken = TOKEN_NULL;
           ::parser_CurTokenStr = "";
 
           if (DEBUG == 1) debugger_Print(PARSER_INIT);
 
           return true;
       }
       else
           return false;
   }
 
 
   parser_TokenType parser_GetToken ()
   {
       char c = 0;
       ::parser_CurTokenStr = "";
 
       if (DEBUG == 1) debugger_Print(PARSER_GET_TOKEN);
 
       while (!source.eof())
       {
           source.get(c);
           if (isalpha(c))
           {
               while (!source.eof()&&isalpha(c))
               {
                    ::parser_CurTokenStr.push_back(c);
                    source.get(c);
               }
 
               switch (c)
               {
                   case ' ': {
                       ::parser_NextToken = TOKEN_NULL;
                       source.unget();
                       break;
                   }
                   case '=': {
                       ::parser_NextToken = TOKEN_DELIM_EQUAL;
                       source.unget();
                       break;
                   }
                   case '+': {
                       ::parser_NextToken = TOKEN_DELIM_PLUS;
                       source.unget();
                       break;
                   }
                   case '-': {
                       ::parser_NextToken = TOKEN_DELIM_MINUS;
                       source.unget();
                       break;
                   }
                   case '\n':{
                       ::parser_NextToken = TOKEN_EOL;
                       source.unget();
                       break;
                   }
                   default:  {
                       source.unget();
                       break; // Error
                   }
               }
 
               map<const string,int>::iterator i = parser_ResWord.find(::parser_CurTokenStr);
 
               if (i!=parser_ResWord.end())
                   ::parser_CurToken = parser_TokenType(parser_ResWord[::parser_CurTokenStr]);
 
               else
                   ::parser_CurToken = TOKEN_IDENT;
 
               if (DEBUG == 1) debugger_Print(PARSER_GET_TOKEN);
 
               return ::parser_CurToken;
           }
       }
       if (DEBUG == 1) debugger_Print(PARSER_GET_TOKEN);
 
       return ::parser_CurToken;
   }
 
 
   void parser_PrintCurToken (std::ofstream *parser_CurFile)
   {
 
       {
            debug_data_file << ::parser_CurFile     << ' '
                            << ::parser_CurLine     << ' '
                            << ::parser_CurToken    << ' '
                            << ::parser_NextToken   << ' '
                            << ::parser_CurTokenStr << ' '
                            <<   endl;
       }
   }

driver.h
C++
1
2
3
4
5
6
7
8
9
#ifndef DRIVER_H_INCLUDED
#define DRIVER_H_INCLUDED
 
#include <fstream>
 
 
extern std::ifstream source;
 
#endif // DRIVER_H_INCLUDED

driver.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include "driver.h"
#include "debugger.h"
#include "parser.h"
 
 
using std::ifstream;
using std::cout;
 
std::ifstream source;
 
int main()
{
    if (DEBUG == 1) debugger_Start();
    parser_Init("source.bsc");
    parser_GetToken ();
    parser_GetToken ();
    parser_GetToken ();
    return 0;
}

Входной файл:
source.bsc
PureBasic
1
asd let print


Добавлено через 23 минуты 40 секунд
P.S.Да,кстати,я тут подумал,почему была вообще эта проблема со съеденной точкой - по ходу дела функция .get() вынимает неформатированный размер данных в 1 char из потока,а .putback() отдаёт форматированный поток,поэтому следовало пользоваться функцией .unget().Это вроде не написано в объяснении библиотеки,я просто так подумал.

Добавлено через 1 час 30 минут 36 секунд
P.S.2 Чёт не пойму,как это я так сделал,как это работает?
C++
1
2
3
4
5
   void parser_PrintCurToken (std::ofstream *parser_CurFile) // Что и каким образом передаётся в функцию?
   {
            debug_data_file << ::parser_CurFile     << ' '
                       ....
   }
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
02.07.2009, 11:04     Пишем свой интерпретатор языка BASIC #26
Отсмотрю попозже или вечером. Вкратце пока то, что с ходу могу ответить

Чтобы не пуатть терминологию, не слудует называть деяние "дебаггером". Более правильно - "внутренние отладочные печати" или "внутренняя отладка", чтобы не путать с программой-отладчиком (которая и есть "дебаггер")

Удобнее для разных фич делать внутреннюю отладку под разными макросами. Т.е. парсер печатать под одним макросом, таблицу переменных под другим и т.п. Когда программа разрастётся, то суммарная выдача станет очень большой, а потому надо уже делить мух от котлет

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

> // Что и каким образом передаётся в функцию?

В функцию не должно передаваться ничего. Смысл следующий. Ты вызываешь parser_GetToken, которая в процессе работы взводит 4 глобальных переменных. А процедура parser_PrintCurToken просто печатает эти 4 значения. Т.е. пара этих процедур работает симметрично: одна записывает результат, другая его читает и печатает

> если ещё не надоел

Попробую пояснить попроще. Как-то меня начальник спросил, считаю ли я себя хорошим специалистом. Я ответил что нет, потому как считаю, что без того, чтобы кого-то обучать (обучить) хорошим специалистом никогда не станешь. Заниматься преподаванием мне как-то неинтересно, ибо обучшение материала строго по плану и по расписанию - это попросут затратить кучу усилий практически впустую. В твоём же случае немного по другому. Ты сам в этом процессе заинтересован, пинать тебя не надо. Программирование - работа творческая, она не может идти по расписанию. Когда ты хочешь - ты программируешь. И программируешь то, что интересно, а не то, что записано в плане. Когда есть желание и заинтересованность - то производительность работы очень высокая. Поэтому я считаю, что всё (или почти всё), что я тебе рассказываю - оно оказывается с пользой для дела. Поэтому в том числе и в моих интересах, чтобы ты довёл эту работу до какого-то логически завершённого состояния.

Добавлено через 51 минуту 10 секунд
Ну будещее. Выкладывай исходники в двух экземплярах:
1. Так, как ты уже делаешь (чтобы с форума просто посомтреть можно было)
2. Заархивируй весь каталог с исходниками (чтобы к себе по простуму скопировать и запустить)

Добавлено через 2 минуты 4 секунды
Да, вот ещё. Посмотри ещё у меня в исходниках судоку как сделано ASSERT. Это внутренние самопроверки. Для порядку тоже учись этой вещью пользоваться. Если непонятно, что это - поясню

Добавлено через 5 минут 49 секунд
Ну и сразу концептуальное замечание. В debugger_Print у тебя стоит switch. В таких случаях надо обязательно лепить ветку default, в которой помещать вызов FatalError (ну или как там её назвать). В один прекрасный расстановка таких условий в недостижимых точках и ASSERT'ы сэкономят тебе кучу времени при отладке

Добавлено через 14 минут 23 секунды
Ну и ещё момент. У тебя отладочная печать принудительно срётся в файл. Здесь бы по аналогии с разделением флага DEBUG сделал разделение выходного файла. Т.е. завёл бы глобалы "ofstream debug_FileParser, debug_FileVarTable", сделал бы процедуру инициализации debug_Init, в котором бы делал открытие данных файлов. А дальше уже по ситуации. Если тебе удобно раздельная печать - открываешь два разных файла. Если тебе удобно видеть их совместно (когда надо понять, что в каком порядке запускается), то один файл открываешь, а для второго делаешь типа "debug_FileVarTable = debug_FileParser". Если нужно печатаь на экран, а не в файл, то просто "debug_FileParser = cout; debug_FileVarTable = cout;". В итоге получается, что у тебя всё гибко, а менять код надо только в одной процедуре

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

Содержимое печати (т.е. то, что ты увидишь) удобнее было бы не тупо 4 значения (при этом надо помнить, что на какой позиции находится), а что-то типа следующего. Для строки "LET a=51"

file="source.bsc", line=1, token=KW_LET, token_str="LET"
file="source.bsc", line=1, token=IDENT, token_str="a"
file="source.bsc", line=1, token=DELIM_EQUAL, token_str="="
file="source.bsc", line=1, token=CONST_INT, token_str="51"

Кстати, при правильном построении синтаксического разборщика TokenNext, который ты добавил, он не нужен. По крайней мере в теории пока выглядит так

Добавлено через 38 секунд
Нумерацию строк принято начинать с 1, а не с 0

Добавлено через 21 минуту 25 секунд
Посмотрел твой вариант GetToken - моск сразу же сломался

Ещё раз формальное описание грамматики (чтобы перед глазами было)

Код
Const = ConstInt | ConstFloat
ConstInt = Digit { Digit }
ConstFloat = Digit { Digit } "." Digit { Digit }
Ident = Letter { Letter | Digit }
Letter = "A" | "B" | ... | "y" | "z"
Digit = "0" | "1" | ... | "9"
KeywordLet = "LET"
KeywordPRINT = "PRINT"
Правильное построение примерно такое:

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
// Пропускаем все пробелы и знаки табуляции
 
// Если текущий символ - латинская буква, значит это начало ключевого слова
// или идентификатора. Если текущий символ - цифра,значит это
// начало константы. В остальных случаях это хз что
if (isalpha(...))
{
  // Читаем из потока до тех пор, пока имеем букву или цифру,
  // таким образом формируем целое слово. После чело проверяем,
  // это является ли слово ключевым. Если да, то это keyword,
  // иначе идетификатор
  while (...) { ... }
} else if (isdigit(...))
{
  // Для простоты пока читаем до тех пор, пока умеем цифру или точку
  // Опять получаем слово. Если оно содержит точку, значит плавающая
  // константа, иначе целочисленная
  // Но потом код надо будет аккуратно передалать, чтобы "1.1.1" не воспринималось
  // как единый токен.
  while (...) { ... }
} else
{
  // Разбираем delimiter'ы. Выбирай для себя способы, которыми задаются
  // комментарии, пропустить их тоже можно будет здесь (после чего сделать
  // goto на начало разбора). В скриптовых языках принято, что комментарий
  // начинается символом '#' и заканчивается в конце строки
}
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
05.07.2009, 04:30  [ТС]     Пишем свой интерпретатор языка BASIC #27
Итак,потихоньку едем далее.Немного помучавшись,я всё-же оставил переменную NextToken.Не знаю почему,мне кажется это удобным.В некоторых моментах я опирался на эту переменную..
Если имеется такой код:
Код
PRINT


#fghfjgh

a
jj

7.9

4
То нужно только 5 вызовов GetToken,чтобы считать действительные строки,хотя нумерация строк учитывается даже на пустых строках и комментариях.Я пока не вижу смысла возвращать из функции значение конца строки.Пока эти значения только для отслеживания процесса.Такой код:
Код
PRINT aa LET #yjyjy


#fghfjgh

a
jj

7.9

4
-потребует 7 вызовов GetToken,то есть важно только количество "токенов",а не строк и др.Последующие вызовы функции (когда "токенов" уже нет) бесполезны.Я надеюсь,что ничего не напутал,сам проверял,вроде работает как надо,но если найдешь недочеты,буду исправлять.
Про assert я почитал,конечно,интересная штука,но хотелось бы сделать подобное силами С++,а это надо углубиться в тему,я ещё даже не знаю генерацию исключений.
Кое-какие мелочи исправил в отладочной печати,обработку ошибок даже пока не знаю,как делать.Хочется сделать получше,надо подумать.Как делаются такие вещи в нормальных программах?
Ну вот,теперь помоему,самое время приступить к организации рекурсивного спуска.Если,конечно,на данном этапе не сделал таких ошибок в проектировании,которые потом запутают донельзя. Т.е.самое сложное ещё впереди. Код,надеюсь,не очень запутан.
Что есть пока выкладываю,вот:
driver.h
C++
1
2
3
4
5
6
7
8
9
#ifndef DRIVER_H_INCLUDED
#define DRIVER_H_INCLUDED
 
#include <fstream>
 
 
extern std::ifstream source;
 
#endif // DRIVER_H_INCLUDED

driver.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
#include <iostream>
#include "driver.h"
#include "debugger.h"
#include "parser.h"
 
 
using std::ifstream;
using std::cout;
 
std::ifstream source;
 
int main()
{
    if (DEBUG == 1) debugger_Start();
    parser_Init("source.bsc");
    parser_GetToken ();
    parser_GetToken ();
    parser_GetToken ();
    parser_GetToken ();
    parser_GetToken ();
    parser_GetToken ();
    parser_GetToken ();
    return 0;
}

parser.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
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
#ifndef PARSER_H_INCLUDED
#define PARSER_H_INCLUDED
 
#include <string>
#include <map>
 
   enum parser_TokenType
   {
     TOKEN_NULL = 0,
 
     TOKEN_CONST_INT,   // Integer constant
     TOKEN_CONST_FLOAT, // Floating point constant
 
     TOKEN_IDENT,       // Identificator
 
     TOKEN_KW_LET,      // Keyword LET
     TOKEN_KW_PRINT,    // Keyword PRINT
 
     TOKEN_DELIM_EQUAL, // Sign "="
     TOKEN_DELIM_PLUS,  // Sign "+"
     TOKEN_DELIM_MINUS, // Sign "-"
 
     TOKEN_EOL,         // End of line
     TOKEN_EOF,         // End of file
 
     TOKEN_LAST
   };
 
   const std::string parser_TokenTypeNames[] =
   {
     "TOKEN_NULL    ",
 
     "TOKEN_CONST_INT",       // Integer constant
     "TOKEN_CONST_FLOAT",     // Floating point constant
 
     "TOKEN_IDENT    ",           // Identificator
 
     "TOKEN_KW_LET    ",          // Keyword LET
     "TOKEN_KW_PRINT",        // Keyword PRINT
 
     "TOKEN_DELIM_EQUAL",     // Sign "="
     "TOKEN_DELIM_PLUS",      // Sign "+"
     "TOKEN_DELIM_MINUS",     // Sign "-"
 
     "TOKEN_EOL    ",         // End of line
     "TOKEN_EOF    ",         // End of file
 
     "TOKEN_LAST"
   };
 
  /** @brief Parser initialization.
   *  @param file_name contains path
   *  to file with source code,wich parser is working with.
   *  @return In case of problem with lile opening return false,
   *  and true if file was opened */
   extern bool parser_Init (std::string file_name);
 
  /** @brief Procedure parser_GetToken get's the next token from
   *  input stream.
   *  @param As a result writing variables parser_CurToken and
   *  parser_LastTokenStr.
   *  @return Procedure parser_GetToken return value of
   *  parser_CurToken. */
   extern parser_TokenType parser_GetToken ();
 
  /** @var Variables parser_CurFile, parser_CurLine contain
   *  information about place of current token in source
   *  code for debug purposes,parser_CurToken is the current
   *  token in line.parser_CurTokenStr contains value of
   *  token,if available. */
   extern std::string parser_CurFile;
   extern unsigned parser_CurLine;
   extern parser_TokenType parser_CurToken;
   extern parser_TokenType parser_NextToken;
   extern std::string parser_CurTokenStr;
 
  /** @var parser_ResWords contain reserved words
   *  for parser.Initialization is in
   *  parser_Init (std::string file_name) */
   extern std::map<const std::string,int>parser_ResWord;
   extern std::map<const char,int>parser_ResSign;
 
  /** @brief Printer for debugging purposes
   *  of current token state.
   *  @param parser_CurFile is name of the
   *  file,that is being parced. */
   extern void parser_PrintCurToken (std::ofstream *);
   extern void parser_PrintCurToken ();
 
  /** @brief End of parser's work */
   extern void parser_Finish ();
 
#endif // PARSER_H_INCLUDED

parser.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#include <iostream>
#include <cctype>
#include "parser.h"
#include "debugger.h"
#include "driver.h"
 
//
 
   using std::ofstream;
   using std::endl;
   using std::map;
   using std::string;
   using std::cout;
 
   parser_TokenType parser_CurToken;
   parser_TokenType parser_NextToken;
   string parser_CurTokenStr;
   string parser_CurFile;
   unsigned parser_CurLine;
 
   map<const string,int>parser_ResWord;
   map<const char,int>parser_ResSign;
 
   bool parser_Init (const std::string file_name)
   {
       parser_ResWord[ "LET" ] = parser_TokenType(TOKEN_KW_LET);
       parser_ResWord["PRINT"] = parser_TokenType(TOKEN_KW_PRINT);
 
       parser_ResSign[  ' '  ] = parser_TokenType(TOKEN_NULL);
       parser_ResSign[  '='  ] = parser_TokenType(TOKEN_DELIM_EQUAL);
       parser_ResSign[  '+'  ] = parser_TokenType(TOKEN_DELIM_PLUS);
       parser_ResSign[  '-'  ] = parser_TokenType(TOKEN_DELIM_MINUS);
       parser_ResSign[  '\n' ] = parser_TokenType(TOKEN_EOL);
 
       source.open(file_name.c_str());
 
       if (source.is_open())
       {
           ::parser_CurFile  = file_name;
           ::parser_CurLine  = 1;
           ::parser_CurToken = TOKEN_NULL;
           ::parser_NextToken = TOKEN_NULL;
           ::parser_CurTokenStr = "";
 
           return true;
       }
       else
           return false;
   }
 
 
   parser_TokenType parser_GetToken ()
   {
       char c = 0;
       ::parser_CurTokenStr = "";
 
       if ((PARSER_DEBUG == 1)&&(DEBUG == 1)) debugger_Print(PARSER_GET_TOKEN);
 
       while (!source.eof())
       {
           source.get(c);
 
           // Identificator or
           // reserved word
           if (isalpha(c))
           {
               while (!source.eof()&&isalpha(c))
               {
                    ::parser_CurTokenStr.push_back(c);
                    source.get(c);
               }
 
               map<const char,int>::iterator i = parser_ResSign.find(c);
 
               if (i!=parser_ResSign.end())
                   ::parser_NextToken = parser_TokenType(parser_ResSign[c]);
 
               else
                   ;//Error
 
               map<const string,int>::iterator ii = parser_ResWord.find(::parser_CurTokenStr);
 
               if (ii!=parser_ResWord.end())
                   ::parser_CurToken = parser_TokenType(parser_ResWord[::parser_CurTokenStr]);
 
               else
                   ::parser_CurToken = TOKEN_IDENT;
 
               if ((PARSER_DEBUG == 1)&&(DEBUG == 1)) debugger_Print(PARSER_GET_TOKEN);
 
               if (::parser_NextToken == TOKEN_EOL) ++::parser_CurLine;
 
               return ::parser_CurToken;
           }
           else
 
           // Number
           if (isdigit(c))
           {
               while (!source.eof()&&isdigit(c))
               {
                    ::parser_CurTokenStr.push_back(c);
                    source.get(c);
               }
               // Float
               if (c == '.')
               {
                    ::parser_CurTokenStr.push_back(c);
                    source.get(c);
 
                    if (isdigit(c))
                        while (!source.eof()&&isdigit(c))
                        {
                             ::parser_CurTokenStr.push_back(c);
                             source.get(c);
                        }
                    else
                       ;//Error
 
                    map<const char,int>::iterator i = parser_ResSign.find(c);
 
                    if (i!=parser_ResSign.end())
                        ::parser_NextToken = parser_TokenType(parser_ResSign[c]);
                    else
                       ;//Error
 
                    ::parser_CurToken = TOKEN_CONST_FLOAT;
 
                    if ((PARSER_DEBUG == 1)&&(DEBUG == 1)) debugger_Print(PARSER_GET_TOKEN);
 
                    if (::parser_NextToken == TOKEN_EOL) ++::parser_CurLine;
 
                    return ::parser_CurToken;
               }
 
               // Integer
               map<const char,int>::iterator i = parser_ResSign.find(c);
 
               if (i!=parser_ResSign.end())
                    ::parser_NextToken = parser_TokenType(parser_ResSign[c]);
 
               else
                   ;//Error
 
               ::parser_CurToken = TOKEN_CONST_INT;
 
               if ((PARSER_DEBUG == 1)&&(DEBUG == 1)) debugger_Print(PARSER_GET_TOKEN);
 
               if (::parser_NextToken == TOKEN_EOL) ++::parser_CurLine;
 
               return ::parser_CurToken;
           }
           else
 
           // !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
           if (ispunct(c))
           {
               map<const char,int>::iterator i = parser_ResSign.find(c);
 
               if (i!=parser_ResSign.end())
               {
                   ::parser_CurToken = parser_TokenType(parser_ResSign[c]);
 
                   if ((PARSER_DEBUG == 1)&&(DEBUG == 1)) debugger_Print(PARSER_GET_TOKEN);
 
                   return ::parser_CurToken;
               }
               else
 
               // Comment
               if (c == '#')
               {
                   while (!source.eof()&&c!='\n') source.get(c);
 
                   if (c == '\n')
                   {
                       ::parser_CurToken = TOKEN_EOL;
                       ::parser_NextToken = TOKEN_NULL;
                       ++::parser_CurLine;
                   }
                   else
                   {
                       ::parser_CurToken = TOKEN_EOF;
                       ::parser_NextToken = TOKEN_NULL;
 
                   }
 
                   // When on commented line
                   // we don't want to return
                   // from function
                   if ((PARSER_DEBUG == 1)&&
                       (DEBUG == 1)&&
                       (PRINT_COMMENTED_LINES == 1)) debugger_Print(PARSER_GET_TOKEN);
 
                   continue;
               }
 
               // ...
           }
 
           else
 
           // ' ' and '\n'
           if (isspace(c))
           {
               if (c == ' ') continue;
               else
               if (c == '\n')
               {
                   ::parser_CurToken = TOKEN_EOL;
                   ::parser_NextToken = TOKEN_NULL;
                   ++::parser_CurLine;
               }
               if ((PARSER_DEBUG == 1)&&
                   (DEBUG == 1)&&
                   (PRINT_EMPTY_LINES ==1)) debugger_Print(PARSER_GET_TOKEN);
 
               continue;
           }
 
           else ;//Error
 
       }
       if ((PARSER_DEBUG == 1)&&(DEBUG == 1)) debugger_Print(PARSER_GET_TOKEN);
 
       return ::parser_CurToken;
   }
 
 
   void parser_PrintCurToken (std::ofstream *)
   {
       switch (FULL_NAMES)
       {
           case 0: {
 
                debug_data_file << "File: "        << ::parser_CurFile     << ' '
                                << "Line: "        << ::parser_CurLine     << ' '
                                << "CurToken: "    << ::parser_CurToken    << ' '
                                << "NextToken: "   << ::parser_NextToken   << ' '
                                << "TokenStr: "    << ::parser_CurTokenStr << ' '
                                << endl;
           }
           break;
           case 1: {
 
                debug_data_file << "File: "        <<                                          ::parser_CurFile     << ' '
                                << "Line: "        <<                                          ::parser_CurLine     << '\t'
                                << "CurToken: "    << parser_TokenTypeNames[parser_TokenType(::parser_CurToken)]    << '\t'
                                << "NextToken: "   << parser_TokenTypeNames[parser_TokenType(::parser_NextToken)]   << '\t'
                                << "TokenStr: "    <<                                          ::parser_CurTokenStr << ' '
                                << endl;
           }
           break;
           default:  //Some error will be here
               debug_data_file << "Check value of FULL_NAMES in debugger.h";
       }
   }
 
   void parser_PrintCurToken ()
   {
       switch (FULL_NAMES)
       {
           case 0: {
 
                cout << "File: "        << ::parser_CurFile     << ' '
                     << "Line: "        << ::parser_CurLine     << ' '
                     << "CurToken: "    << ::parser_CurToken    << ' '
                     << "NextToken: "   << ::parser_NextToken   << ' '
                     << "TokenStr: "    << ::parser_CurTokenStr << ' '
                     << endl;
           }
           break;
           case 1: {
 
                cout << "File: "        <<                                          ::parser_CurFile     << ' '
                     << "Line: "        <<                                          ::parser_CurLine     << '\t'
                     << "CurToken: "    << parser_TokenTypeNames[parser_TokenType(::parser_CurToken)]    << '\t'
                     << "NextToken: "   << parser_TokenTypeNames[parser_TokenType(::parser_NextToken)]   << '\t'
                     << "TokenStr: "    <<                                          ::parser_CurTokenStr << ' '
                     << endl;
           }
           break;
           default:  //Some error will be here
                cout << "Check value of FULL_NAMES in debugger.h";
       }
   }

debugger.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
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
#ifndef DEBUGGER_H_INCLUDED
#define DEBUGGER_H_INCLUDED
 
// This switch is to enable
// program global debugging
// mode.1 is enabled and 0
// is disabled.
#define DEBUG 1
 
// Enable and disable of
// debugging print of
// different components
// of the program
#define PARSER_DEBUG 1
 
// When SILENT is 1 printing
// to a file,when 0 - to console.
#define SILENT 1
 
// Printing of full variables names
// 1 - enabled.
#define FULL_NAMES 0
 
// Print on commented and empty lines
// 1 - enabled.
#define PRINT_COMMENTED_LINES 0
#define PRINT_EMPTY_LINES     0
 
#include <fstream>
 
   // Each value of this enum
   // represent function which
   // debugging process is
   // working in.
   enum debug_CurFunc {
       PARSER_INIT,       // parser_Init (const std::string file_name)
       PARSER_GET_TOKEN   // parser_TokenType parser_GetToken ()
   };
 
  /** @brief Clearing the debugging log. */
   extern void debugger_Start();
 
  /** @brief Global printing of variables.
   *  @param debug_CurFunc is name of the function,
   *  that debugger is working with. */
   extern void debugger_Print(debug_CurFunc);
 
   extern std::ofstream debug_data_file;
   extern debug_CurFunc debug_CurFuncName;
 
#endif // DEBUGGER_H_INCLUDED

debugger.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
#include "debugger.h"
#include "parser.h"
 
//
 
   using std::ofstream;
 
 
   ofstream debug_data_file;
 
 
   void debugger_Start()
   {
       if (SILENT == 0)
             ;
       else
       {
           debug_data_file.open("data_file.dat");
           debug_data_file.clear();
           debug_data_file.close();
       }
   }
 
   void debugger_Print(debug_CurFunc debug_CurFuncName)
   {
           switch (debug_CurFuncName)
           {
                case PARSER_INIT: {
 
                    if (SILENT == 0)
                        parser_PrintCurToken ();
 
                    else
                    {
                        debug_data_file.open("data_file.dat",ofstream::app);
 
                        if (debug_data_file.is_open())
 
                            parser_PrintCurToken (&debug_data_file);
 
                        debug_data_file.close();
                    }
                }
                break;
                case PARSER_GET_TOKEN: {
 
                    if (SILENT == 0)
                        parser_PrintCurToken ();
 
                    else
                    {
 
                       debug_data_file.open("data_file.dat",ofstream::app);
 
                       if (debug_data_file.is_open())
 
                           parser_PrintCurToken (&debug_data_file);
 
                       debug_data_file.close();
                    }
                }
                break;
                default: //Error
                ;
          }
   }

Входные данные:
source.bsc

PureBasic
1
2
3
4
5
6
7
8
9
10
11
PRINT aa LET #yjyjy
 
 
#fghfjgh
 
a
jj
 
7.9
 
4
Исходник запаковал сначала tar.gz,потом ещё в rar (на всякий случай,там ещё 7 кб сэкономилось)
Вложения
Тип файла: rar Interpreter.rar (3.3 Кб, 196 просмотров)
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
05.07.2009, 05:02  [ТС]     Пишем свой интерпретатор языка BASIC #28
P.S.Я так полагаю,мне придётся искать инфу по бейсику в сети? Честно,не помню уже этот язык.
Да,ещё забыл-NextToken почему ещё оставил: подумал,что будет удобно проверять составные операторы,типа << , += , := , и т.д.
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
05.07.2009, 11:42     Пишем свой интерпретатор языка BASIC #29
> то есть важно только количество "токенов",а не строк и др

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

> Последующие вызовы функции (когда "токенов" уже нет) бесполезны

Вопрос в том, а как понимать, что мы имеем последний токен. EOF нужен только для этого. EOL нужен для обозначения конца оператора (statement). Чтобы конструкция "LET a=1 LET b=2" трактовалась как ошибочная (ибо считаем, что положено не более одного оператора в строке). Так же EOL служит разделителем между операторами, что упростит процесс синтаксического разбора. Но, оставь так, как сделал. Потому что более полезно учиться на своих ошибках, а не на том, как другие тебе их указывают

> обработку ошибок даже пока не знаю,как делать

Предлагаю такой вариант. Для начала сделай хоть как-то, чтобы оно работало в самом примитивном варианте. Лишь бы потом можно было просто найти все места, которые надо исправить. КОгда интерпретатор более-менее задышит и будет поддерживать несколько конструкций - тогда будет проще спроектировать конкретную реализацию обработки ошибок, потому как уже можно будет охватить широким взглядом всё то, что уже есть (а не виртуально то, что предположительно будет, как это сейчас)

> Как делаются такие вещи в нормальных программах?

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

> Ну вот,теперь помоему,самое время приступить к организации рекурсивного спуска

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

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

> Я так полагаю,мне придётся искать инфу по бейсику в сети?

А зачем. У тебя же не стоИт задача реализовать полноценный бэйсик. Ты пока только учишься, а потому можно сделать что-то очень простое бэйсикоподобное. Когда ты реализуешь до конца свою простую задачу, я тебя уверяю, ты сам сможешь её расширить до нормального интерпретатора. И синтаксис потом поменять как угодно

Щас погляжу твоё творение

Добавлено через 22 минуты 6 секунд
Для твоего исходника напечаталось так

Код
File: source.bsc Line: 1	CurToken: TOKEN_NULL    	NextToken: TOKEN_NULL    	TokenStr:  
File: source.bsc Line: 1	CurToken: TOKEN_KW_PRINT	NextToken: TOKEN_NULL    	TokenStr: PRINT 
File: source.bsc Line: 1	CurToken: TOKEN_KW_PRINT	NextToken: TOKEN_NULL    	TokenStr:  
File: source.bsc Line: 1	CurToken: TOKEN_IDENT    	NextToken: TOKEN_NULL    	TokenStr: aa 
File: source.bsc Line: 1	CurToken: TOKEN_IDENT    	NextToken: TOKEN_NULL    	TokenStr:  
File: source.bsc Line: 1	CurToken: TOKEN_KW_LET    	NextToken: TOKEN_NULL    	TokenStr: LET 
File: source.bsc Line: 1	CurToken: TOKEN_KW_LET    	NextToken: TOKEN_NULL    	TokenStr:  
File: source.bsc Line: 6	CurToken: TOKEN_IDENT    	NextToken: TOKEN_EOL    	TokenStr: a 
File: source.bsc Line: 7	CurToken: TOKEN_IDENT    	NextToken: TOKEN_EOL    	TokenStr:  
File: source.bsc Line: 7	CurToken: TOKEN_IDENT    	NextToken: TOKEN_EOL    	TokenStr: jj 
File: source.bsc Line: 8	CurToken: TOKEN_IDENT    	NextToken: TOKEN_EOL    	TokenStr:  
File: source.bsc Line: 9	CurToken: TOKEN_CONST_FLOAT	NextToken: TOKEN_EOL    	TokenStr: 7.9 
File: source.bsc Line: 10	CurToken: TOKEN_CONST_FLOAT	NextToken: TOKEN_EOL    	TokenStr:  
File: source.bsc Line: 11	CurToken: TOKEN_CONST_INT	NextToken: TOKEN_EOL    	TokenStr: 4 
m
Почему на каждый токен по две печати - я особенно разбираться не стал. Правда всё равно не понял корелляции между CurToken и NextToken. Т.е. мне казалось, что NextToken это то, что мы получим при следующем вызове GetToken. Но не суть. Главное, что мысль того, что должен делать GetToken, ты понял правильно

В parser.cpp есть фрагмент (строки 89-93)

C++
1
2
3
if ((PARSER_DEBUG == 1)&&(DEBUG == 1)) debugger_Print(PARSER_GET_TOKEN);
if (::parser_NextToken == TOKEN_EOL) ++::parser_CurLine;
return ::parser_CurToken;
Т.е. увеличение текщуего номера строки у тебя идёт после печати. Т.е. глазами в печати ты будешь видеть одно, а реально у тебя будет другое. Это доволно частая ошибка

Добавлено через 14 минут 37 секунд
Вот примерное формальное описание синтаксиса на текущий момент

Код
StatementList = Statement { EOL Statement } EOF
Statement = LetStatement | PrintStatement
LetStatement = IDENT "=" Expr
PrintStatement = "PRINT" Expr
Expr = Term { "+" | "-" Term }
Term = Factor { "*" | "/" Factor }
Factor = CONST | IDENT | "(" Expr ")"
Битовые операции пока включать не стал, из соображений минимизации. Да и хотелось бы, чтобы ты сам прикинул, как добавить операции с ещё одним уровнем приоритета. Для PRINT'а сделал чуть пошире, чем у тебя было - он печатает не просто переменную, а произвольное выражение (переменная тоже является выражением)

Так что предлагаю тебе сначала реализовать такой синтаксис, а потом начать выделять константы и таблицу переменных

Сейчас получается всё просто. Верхний уровень (по сути дела обработка StatementList) что-то типа того. Пишу в своём предположенииналичия EOL'ов и EOF'ов

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
while ((parser_GetToken()) != EOF)
{
  switch (parser_CurToken)
  {
    case KW_LET:
      stmt_LET();
      break;
    case KW_PRINT:
      stmt_PRINT();
      break;
    default:
      синтаксическая ошибка
  }
 
  if (parser_GetToken() != EOL)
    синтаксическая ошибка
}
 
если дошли до этой точки, значит программа проинтерпретирована до конца
Процедуры stmt_LET и stmt_PRINT внутри себя дальше дёргают parser_GetToken и работают согласно описанному синтаксису

Добавлено через 6 минут 57 секунд
На начальном этапе с EOL'ами можешь не заморачиваться, но тогда у тебя будут разрешены операции типа "LET a=1 LET b=2". В скриптовых языках принято операции разделять энтером или знаком ";"

Добавлено через 14 минут 52 секунды
Кстати, в 20-м посте я ещё в терминологии по ходу дела ошибся. Тут куча тонких моментов, но по ходу дела общепринятые термины такие. Парсер является лексическим анализатором (т.е. процесс формирования токенов из символов называется lexical analyser). А вот построение операторов - это синтаксический разбор (а правила называются syntax rules)
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
06.07.2009, 23:30  [ТС]     Пишем свой интерпретатор языка BASIC #30
Пытаюсь сделать такой шаблон (это набросок,пока не представляю как будет):
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
#ifndef SYNTAX_PARSER_H_INCLUDED
#define SYNTAX_PARSER_H_INCLUDED
 
#include <cstdlib>
#include <fstream>
#include <iostream>
#include "parser.h"
#include "debugger.h"
 
 
   template<typename T>T syntax_parserPrimary()
    {
        switch (parser_GetToken ())
        {
            case TOKEN_CONST_INT: {
                int x = atoi(parser_CurTokenStr.c_str());
                std::cout << x;
                return x;
            }
            case TOKEN_CONST_FLOAT: {
                double x = atof(parser_CurTokenStr.c_str());
                return x;
            }
        }
    }
 
#endif // SYNTAX_PARSER_H_INCLUDED
в main всё просто.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include "driver.h"
#include "debugger.h"
#include "parser.h"
#include "syntax_parser.h"
 
 
using std::ifstream;
using std::cout;
 
std::ifstream source;
 
int main()
{
    syntax_parserPrimary();
    return 0;
}
Кстати в той версии я уже пытался делать шаблоны,но у меня не получилось.Не понимаю,почему такая ошибка:
Код
/media/Part2/C++/Projects/CodeBlocks/Interpreter/driver.cpp||In function ‘int main()’:|
/media/Part2/C++/Projects/CodeBlocks/Interpreter/driver.cpp|15|ошибка: нет соответствующей функции для вызова ‘syntax_parserPrimary()’|
||=== Build finished: 1 errors, 0 warnings ===|
Такое ощущение,что сам шаблон не компилируется.Всё это я пытаюсь сделать,чтобы потом не пришлось переписывать и перекраивать пол-программы,ну и хочется сократить количество функций.
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
07.07.2009, 08:54     Пишем свой интерпретатор языка BASIC #31
Не совсем понятно, зачем тут шаблон, если процедура у тебя будет присутсвовать в одном экземпляре. Попробуй всё-таки пояснить, а я возможно укажу тебе ошибку в твоих рассуждениях

Ну а коли его сделал, то шаблон в качестве параметра имеет тип. У тебя тут типа нет никакого среди параметров, а потому компилятору непонятно, как выбирать функцию. Для void функции по идее надо так: "syntax_parserPrimary<int>();" Ну или тип нужный указываешь

Добавлено через 2 минуты 31 секунду
Код функции при первом просмотре не смотрел, а сейчас явно вижу ошибку. Ты почему-то считаешь, что функция у тебя может возвращать и int и double - такого быть не может Не говоря уж о том, что по ветке default у тебя ничего не возвращается

По поводу синтаксического нисходящего разбора - всё-таки поковыряйся вот тута Представление выражения в двоичном дереве В 3-м посте исходник, в 5-м комментарии к нему. Читать именно в части синтаксического разбора, грамматический там сделан излишне навороченно, тот вариант, который у тебя, он более удобен

Добавлено через 2 минуты 15 секунд
Я так понимаю, что ты добрался до того, как же аккуратно поддерживать константы разных типов? Если так, то могу рассказать вкратце
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
07.07.2009, 23:47  [ТС]     Пишем свой интерпретатор языка BASIC #32
Я думал так: когда у нас константа,нужно вернуть её значение,но ведь тип возвращаемого не ясен с самого начала.Я надеялся это можно решить с помощью шаблонов,но,похоже ошибся.
Тут я вижу 2 выхода из ситуации(трудно абстрактно мыслить на много шагов вперёд ):
1)Сами функции ничего не возвращают,но изменяют переменные в объекте класса,который и требуется разработать.Возможно придётся идти на какие-то хитрости,вроде того,какая переменная изменилась при предыдущем вызове- int или double)
2)Для каждой переменной типа,будь то int или double,есть своя функция,которая с этим типом работает.То есть типизация по сути добавит кучу экземпляров функций,и получится что-то вроде древовидного ветвления,где будут вызываться нужные функции согласно типу.
3) Первый вариант мне не нравится из-за опасности запутаться в бесконтрольном изменении переменных,второй тоже запутан и сложен.Есть какой-то третий вариант?Я твои исходники специально не стал смотреть пока,хочется скорее совета,навроде заданного курса,чтобы потом не заниматься мазохизмом (по крайней мере не слишком много),переписывая и переписывая.
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
08.07.2009, 11:32     Пишем свой интерпретатор языка BASIC #33
На самом деле тебе нужно разработать две вещи:

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

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
class MyConst
{
  private:
    bool is_float; // плавающая или целая
    union
    {
      long long ival; // здесь храним целое значение, если константа целая
      double fval; //  здесь храним плавающее значение, если константа плавающая
    } val;
 
  public:
    // Констркуторы MyConst(long long), MyConst(double), конструктор копирования.
    // При этом конструктор по умолчанию должен вызывать ошибку, ибо понятие
    // константы без значения смысла не имеет
    // Перегруженные операторы плюс, минус и т.д.
    // как конкретно разруливать ошибочные ситуации (деление на ноль, битовые
    // операции над плавающими числами и т.п.) - решать тебе, самый простой
    // метод - через исключение. Можно вместо операторов реализовывать функции
    // plus, minus и т.п. - в этом случае можно возвращать код ошибки, а не
    // работать через исключения. Это ты решай сам, как тебе удобнее и что
    // понятнее.
    //
    // Сложение, например, целого и плавающего, можно реализовать двояко:
    // - преобразование второго операнда к типу первого делать внутри
    //   перегруженных операторов (или функций plus, minus...)
    // - операции разрешить только над константами одного типа, а преобразоване
    //   типов делать в процессе синтаксического разбора
    // Первый вариант хорош своей простотой использования класса - во время
    // синтаксического разбора надо будет меньше делать телодвижений
    // (соответсвенно, интерпретатор будет работать более надёжно).
    // Второй вариант концептуально более правильный, тбо это правило (второй
    // операнд должен приводиться к типу первого) - это свойство языка, а не
    // свойство констант.
    // 
    // Если бы делал я, я бы пошёл по второму варианту, т.к. я делаю код на Си,
    // а работу с типами сделал бы в виде дополнительной прокладки между
    // синтаксическим анализатором и реализацией констант. С точки зрения
    // концепции Си++ это, видимо, означает создание класс-наследника
    // и перегрузку операторов или что-то ещё, в итоге получится тот случай,
    // когда объектный код усложнит проблему, чем её упростит. Да и поскольку
    // ты пока только учишься, я бы тебе рекомендовал первый вариант
    //
    // Соотвественно, в случае выбора первого варианта, появляется дополнительный
    // метод для преобразования типа константы. Теоретически он должен быть
    // private, поскольку это внутреннее действо, но практически может понадобиться
    // использовать его снаружи
    //
    // Отладочный код печати константы (печатает тип и значение, возможно что-то ещё,
    // что появится в процессе работы
    // Пользовательский код печати константы (грубо говоря, его будешь дёргать
    // в процессе работы оператора PRINT)
}
В итоге сейчас у тебя есть независимый модуль parser, который режет входной текст на токены. Теперь у тебя появится независимый модуль работы с константами (который так же независимо от всего можно отлаживать). Потом появится модуль таблицы переменных, который зависит только от модуля констант (и опять-таки его можно будет отлаживать независимо от парсера и синтаксического анализатора). В итоге программа должна быть как конструктор: состоять из отдельных кубиков, внутреннюю реализацию кубиков можно отлаживать независимо друг от друга, а взаимодействовать между собой кубики должны посредством простых интерфейсов

Надеюсь, что смысл донеёс

Добавлено через 1 минуту 3 секунды
Если непотяна какая-то часть именно в плане технической реализации - лучше спрашивай сейчас

Добавлено через 7 минут 13 секунд
Кстати, а на чём ты пишешь? Просто все твои примеры с ходу идут у меня на gcc. При этом тесты исходников явно записаны под виндами (там перевод строки не такой, как в линухе). Т.е. у тебя какая-то gcc-образная среда из разряда Dev-C++, mingw или всё-таки в стандартных билдерах ухитряешься так аккуратно написать?

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

Добавлено через 1 час 40 минут 24 секунды
Кстати, если исходить из простоты, то в случае деления на 0 и прочих ошибок можно сразу вызывать функцию, выдающую пользовательскую ошибку и завершать работу. В этом случае модуль констант будет не абсолютно независимым, а у него будет зависимость от модуля ошибок, что не страшно
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
08.07.2009, 14:12  [ТС]     Пишем свой интерпретатор языка BASIC #34
Вроде теперь образуется некая структура,просто до этого не мог представить в теории реализацию.Есть некоторые вопросы.Для начала,примерно так я представляю будущую структуру:
1)Значит,будет класс,описывающий произвольный тип переменной.

2)Сами функции рекурсивного спуска будут возвращать объект класса,а все ошибочные ситуации обрабатываются внутри самого объекта.

3)Конструктор,я полагаю,будет принимать готовую строку,плюс логическое значение(зависит от TOKEN_CONST_***),(или сам токен),которое передаётся в переменную is_float,которое,в свою очередь,влияет на работу конструктора.Но в принципе можно сделать 3 конструктора просто для удобства.


Поскольку ты написал,что не стоит заморачиваться с памятью,я так понимаю,что во-первых,каждый объект будет создаваться в динамической памяти,но не понимаю,зачем это нужно,разве не достаточно локальных копий во время рекурсии(?),во-вторых,я думаю,там будет какой-то автоматический сборщик мусора,а как это реализовать,я понятия не имею(пока),я просто немного знаком с темой классов и немного с темой динамической памяти.

Зачем будет нужен конструктор-копировщик,пока для меня тоже загадка.

Пишу я в среде Code::Blocks,компилятор gcc,ты не ошибся,но правда пишу код в Linux,уж не знаю,что там не так с переводом строки.Но также стараюсь не использовать специфических вызовов типа как в винде system(***) ,код по идее должен скомпилироваться и в билдере на винде,но точно не знаю,я просто пользуюсь только Линуксом
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
08.07.2009, 14:39     Пишем свой интерпретатор языка BASIC #35
Цитата Сообщение от #pragma Посмотреть сообщение
1)Значит,будет класс,описывающий произвольный тип переменной.
Не тип переменной, а значение константы.

С переменными в этой части можно не заморачиваться и считать, что тип переменной есть текущее значение типа константы. Т.е. записали в переменную целое число, значит в этот момент переменная целая, записали плавающее - значит плавающая. Т.е. тип переменной по сути дела отсутсвует, существует только тип текущего значения. Либо считать, что при первой записи определяется тип переменной, а при дальнейших записях значение всегда будет сводиться к этоу типу. Либо явно в языке ввести тип переменной. На том бейсике, на котором я сто лет назад работыл было так, что A% - целая, A! - плавающая одинарной точности (float), A - плавающая двойной точности (double), A$ - строковая. Ты сам выбирай поведение, которое кажется тебе логичным, но первый вариант (когда тип переменной определяется по записанному в него значению), вроде бы наиболее прост в реализации и начать можно с него. А потом переделать (тоже полезный опыт по переделыванию)

Цитата Сообщение от #pragma Посмотреть сообщение
2)Сами функции рекурсивного спуска будут возвращать объект класса,а все ошибочные ситуации обрабатываются внутри самого объекта.
Грубо говоря да (только "экземпляр класса", а не "объект класса"). Но только в той части синтаксического разбора, где идёт вычисление выражения. Понятно, что по результату разбора Statement'а ничего этого не нужно

Цитата Сообщение от #pragma Посмотреть сообщение
3)Конструктор,я полагаю,будет принимать готовую строку,плюс логическое значение(зависит от TOKEN_CONST_***),(или сам токен),которое передаётся в переменную is_float,которое,в свою очередь,влияет на работу конструктора.Но в принципе можно сделать 3 конструктора просто для удобства.
Ога, только вариант "или сам токен" не годится, ибо модуль констант не должен зависеть от каких-либо enum'ов парсера. Т.е. два параметра: строка и bool (означающий плавающее или целочисленное). Вообще концептуально честно вместо bool ввести enum { CONST_VAL_INT, CONST_VAL_FLOAT }, а затем может придётся добавить туда ещё и строковые константы, могут понадобиться и булевские константы. Т.е. вариант с enum'ом выглядит более логично для дальнейшего развития

Цитата Сообщение от #pragma Посмотреть сообщение
Поскольку ты написал,что не стоит заморачиваться с памятью,я так понимаю,что во-первых,каждый объект будет создаваться в динамической памяти,но не понимаю,зачем это нужно,разве не достаточно локальных копий во время рекурсии(?)
Правильно, нужно создавать именно локалы. У меня почти отсутсвует практика работы на Си++, а потому мне интуитивно кажется, что в процессе перегрузки операторов создаются новые объекты. Хотя вроде бы должно быть без этого

Цитата Сообщение от #pragma Посмотреть сообщение
во-вторых,я думаю,там будет какой-то автоматический сборщик мусора,а как это реализовать,я понятия не имею(пока),я просто немного знаком с темой классов и немного с темой динамической памяти.
Всё будет создаваться в локальных объектах, так что удаляться будут автоматически

Цитата Сообщение от #pragma Посмотреть сообщение
Зачем будет нужен конструктор-копировщик,пока для меня тоже загадка.
Опять-таки следствие моей работы на Си, а не Си++, может он и не нужен (т.е. компилятор сам его построит правильно). Смысл примерно такой. Экземпляры класса MyConst будут рожаться только при непосредственном вызове конструктора, при построении операций и возврате из процедуры. Чтобы не оставлись просто висячие локальные объекты, в которые ничего не записано (ибо мы будем иметь константу без значения). Хотя можно ввести дополнительный признак, проинциализирована константа значением или нет. Всё это мудодейство нужно лишь с одной целью - внутренний контроль при написании интерпретатора. Чем больше контроля, тем быстрее будут находиться ошибки

Цитата Сообщение от #pragma Посмотреть сообщение
правда пишу код в Linux
Отлично. Значит с командной строкой ты более-менее дружишь. Значит проще будет создать систему тестирования (ну это в будущем, когда хоть что-то более-менее заработает нормально). Да и установить svn в этом случае не помешало бы, но настроитьего именно в варианте работы с сетью (обращаться через 127.0.0.1) и под отдельным пользователем - чтобы случайно репозиторий не грохнуть
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
09.07.2009, 23:47  [ТС]     Пишем свой интерпретатор языка BASIC #36
Я тут подумал,не лучше ли будет для переменных организовать свой класс,в который,в свою очередь,будет содержать в себе экземпляр класса констант?То есть,когда потребуется выполнить
PureBasic
1
LET a = 1
То просто будет создаваться экземпляр класса переменных,а в конструктор констант будет передаваться строка,и вся ответственность за типизацию,инициализацию и прочее уже ляжет на реализацию класса.Единственная возможная проблема мне видится с видимостью переменных,что данный объект не будет видно из функций,вызываемых далее,а если создавать какой-то глобальный экземпляр,то встаёт вопрос сколько их нужно...Может тут namespace как-то спасёт?
Еще небольшая мелочь в оформлении-проектировании: В syntax_parser.h у меня записано:
syntax_parser.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef SYNTAX_PARSER_H_INCLUDED
#define SYNTAX_PARSER_H_INCLUDED
 
 
 
   // Contain all types of
   // constants,that constructor
   // will get in create of
   // class object.
   enum Types {
       CONST_VAL_INT,   // Тип константы,передаваемый
       CONST_VAL_FLOAT  // конструктору класса
   };
 
   extern Types VarType;
   
   #include "const_class.h"
   class ConstVar;
 
   extern ConstVar syntax_parserPrimary();
 
#endif // SYNTAX_PARSER_H_INCLUDED

То есть я делаю тобой предложенный enum частью синтаксического анализатора(ну не в класс констант же его включать). Мне не нравится,что пришлось так неэстетично записать включение "const_class.h" прямо посередине header-а,как-то не смотрится,да и запись class ConstVar; тоже не к месту.Это всё потому,что enum Types используется в классе ConstVar,который и подключается с помощью "const_class.h".Как бы так сделать поэлегантнее,чтобы в то же время сохранить некий порядок и структуру разделения? Модульное программирование для меня пока не совсем ясно,вот такие ньюансы немного сбиваю с толку.На всякий случай вот код(я надеюсь компилить его не нужно):
syntax_parser.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
#include <cstdlib>
#include <fstream>
#include <iostream>
#include "syntax_parser.h"
#include "parser.h"
#include "debugger.h"
 
//
    using std::ofstream;
    using std::cout;
    using std::endl;
 
    Types VarType;
 
 
    ConstVar syntax_parserPrimary()
    {
        switch (parser_GetToken ())
        {
            case TOKEN_CONST_INT: {
 
                ConstVar int_x(::parser_CurTokenStr,CONST_VAL_INT);
 
                return int_x;
            }
            case TOKEN_CONST_FLOAT: {
 
                ConstVar float_x(::parser_CurTokenStr,CONST_VAL_FLOAT);
 
                return float_x;
            }
            case TOKEN_IDENT: {
                
                ;
            }
            default: ;
                //Error
        }
    }

const_class.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
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
#ifndef CONST_CLASS_H_INCLUDED
#define CONST_CLASS_H_INCLUDED
 
 
#include "syntax_parser.h"
#include "parser.h"
#include <string>
#include <iostream>
 
 
 
 
   class ConstVar
   {
       private:
              // is_float will be used for
              // setting type of the constant
              // 0 means integer constant
              bool is_float; // плавающая или целая
 
              // "Pure optimisation"
              // Depends of is_float
              union
              {
                  long ival; // здесь храним целое значение, если константа целая
                  double fval; //  здесь храним плавающее значение, если константа плавающая
              } constant;
 
       public:
            /** @brief Costructor receive string from parser
             *  and turn it into variable.The type of variable
             *  depends on VarType value(syntax_parser.h).*/
             ConstVar(const std::string parser_CurToken,Types VarType);
 
            /** Standart destructor */
            ~ConstVar(){};
 
            /** Operators overloading */
             ConstVar operator +  (const ConstVar& prev);
             ConstVar operator -  (const ConstVar& prev);
             ConstVar operator *  (const ConstVar& prev);
             ConstVar operator /  (const ConstVar& prev);
             ConstVar operator =  (const ConstVar& prev);
 
             bool Is_Float() const {return is_float;};
             long Get_ival() const {return constant.ival;};
             double Get_fval() const {return constant.fval;};
   };
 
 
   std::ostream& operator << (std::ostream& os,const ConstVar& curr);
 
#endif // CONST_CLASS_H_INCLUDED

const_class.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include "const_class.h"
#include "syntax_parser.h"
#include <iostream>
#include <cstdlib>
#include <ostream>
 
//
 
   using std::string;
   using std::cout;
 
   ConstVar::ConstVar(const std::string parser_CurToken, Types VarType)
   {
       switch (VarType)
       {
           case CONST_VAL_INT: {
 
               is_float = 0;
 
               constant.ival = atol(parser_CurToken.c_str());
 
           break;
           }
           case CONST_VAL_FLOAT: {
 
               is_float = 1;
 
               constant.fval = atof(parser_CurToken.c_str());
 
           break;
           }
           default:; // Error;
       }
   }
 
 
   ConstVar ConstVar::operator = (const ConstVar& prev)
   {
       if (is_float == 1&&is_float == prev.Is_Float())
       {
           constant.fval = prev.Get_fval();
           return *this;
       }
       else
         if (is_float == 0&&is_float == prev.Is_Float())
         {
             constant.ival = prev.Get_ival();
             return *this;
         }
         else ;
            // Types messing Error?
         return *this;
   }
 
 
   ConstVar ConstVar::operator + (const ConstVar& prev)
   {
       if (is_float == 1&&is_float == prev.Is_Float())
       {
           constant.fval += prev.Get_fval();
           return *this;
       }
       else
         if (is_float == 0&&is_float == prev.Is_Float())
         {
             constant.ival += prev.Get_ival();
             return *this;
         }
         else ;
            // Types messing Error?
         return *this;
   }
 
 
   ConstVar ConstVar::operator - (const ConstVar& prev)
   {
       if (is_float == 1&&is_float == prev.Is_Float())
       {
           constant.fval += prev.Get_fval();
           return *this;
       }
       else
         if (is_float == 0&&is_float == prev.Is_Float())
         {
             constant.ival -= prev.Get_ival();
             return *this;
         }
         else ;
            // Types messing Error?
         return *this;
   }
 
 
   ConstVar ConstVar::operator * (const ConstVar& prev)
   {
       if (is_float == 1&&is_float == prev.Is_Float())
       {
           constant.fval *= prev.Get_fval();
           return *this;
       }
       else
         if (is_float == 0&&is_float == prev.Is_Float())
         {
             constant.ival *= prev.Get_ival();
             return *this;
         }
         else ;
            // Types messing Error?
         return *this;
   }
 
 
   ConstVar ConstVar::operator / (const ConstVar& prev)
   {
       if (is_float == 1&&is_float == prev.Is_Float())
       {
           constant.fval /= prev.Get_fval();
           return *this;
       }
       else
         if (is_float == 0&&is_float == prev.Is_Float())
         {
             constant.ival /= prev.Get_ival();
             return *this;
         }
         else ;
            // Types messing Error?
         return *this;
   }
 
 
   std::ostream& operator << (std::ostream& output_stream,const ConstVar& curr)
   {
       switch (curr.Is_Float())
       {
           case 1:
 
                output_stream << curr.Get_fval();
                return cout;
           break;
           case 0:
 
                output_stream << curr.Get_ival();
                return cout;
           break;
           default:;
                //Something
           return cout;
       }
   }


Добавлено через 29 минут 46 секунд
P.S. Сейчас увидел только: я по ходу ошибся в перегрузке операторов,возвращать нужно было this а не *this,так? Просто я на лету делал,пришлось попутно читать про перегрузку ,union и т.д.
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
10.07.2009, 14:28     Пишем свой интерпретатор языка BASIC #37
Цитата Сообщение от #pragma Посмотреть сообщение
Я тут подумал,не лучше ли будет для переменных организовать свой класс,в который,в свою очередь,будет содержать в себе экземпляр класса констант?
Именно так. Только я это условно называю "таблицей переменных". Хотя ты пока пишешь простой интерпретатор (а не полноценный бэйсик). Я бы пока глубоко с этим не заморачивался и понятие "переменная" наверх не вытаскивал. Сделал бы модуль, а к нему интерфейсы: "записать значение в переменную с такми-то именем", "прочитать значение из переменной с таким-то именем", "проверить, существует ли переменная с таким-то именем". А реализацию поначалу сделал бы простую (типа того, что у тебя уже было), потом при необходимости можно было бы усложнить

Цитата Сообщение от #pragma Посмотреть сообщение
То есть,когда потребуется выполнить
PureBasic
1
LET a = 1
То просто будет создаваться экземпляр класса переменных,а в конструктор констант будет передаваться строка,и вся ответственность за типизацию,инициализацию и прочее уже ляжет на реализацию класса
Что-то слабо понял, о чём ты говоришь. А какую строку передавать в случае "LET a=b+c"? Просто в большой программе всё должно быть функционально отделено. Создание константы - отдельно. Создание переменной, инициализированной константой - отдельно. Создавать переменную через строку, и типа внутри автоматически дёрнется создание константы - это идеологически неправильно, потому как при таком подходе ты свою программу оченб быстро превратишь в помойку, в которой сложно будет разобраться. Не даром я тебе приводил сравнение с кубиками. Когда у тебя есть много маленьких кубиков, ты из них можешь собрать всё что угодно, когда та сразу же начинаешь склеивать кубики в другие конструкции, то из них уже тяжело что-то собрать. И есть хорошее программерское наблюдение: чем более универсальным делается интерфейс, тем меньше остаётся мест, где его удаётся применить".

Цитата Сообщение от #pragma Посмотреть сообщение
Единственная возможная проблема мне видится с видимостью переменных,что данный объект не будет видно из функций,вызываемых далее,а если создавать какой-то глобальный экземпляр,то встаёт вопрос сколько их нужно...Может тут namespace как-то спасёт?
Опять-таки, я бы сразу не стал так далеко заглядывать. Просто по той причине, что ты ещё толком не имеешь понятия о том, как будет выглядеть весь интерпретатор. Я уже несколько раз говорил идею, что для начала бы надо сделать, чтобы хоть как-то заработал вариант с выражениями и PRINT'ами. Т.е на текущий момент ты имеешь такую сложность, что тебе надо реализовывать сразу три вещи: константы, переменные, синтаксический анализатор. При этом ты пока не имеешь чёткого представления о том, что будет, а поэтому у тебя возникает масса затруднений на предмет того, а как же конкретно всё реализовывать.

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

То есть я делаю тобой предложенный enum частью синтаксического анализатора(ну не в класс констант же его включать). Мне не нравится,что пришлось так неэстетично записать включение "const_class.h" прямо посередине header-а,как-то не смотрится,да и запись class ConstVar; тоже не к месту.Это всё потому,что enum Types используется в классе ConstVar,который и подключается с помощью "const_class.h".Как бы так сделать поэлегантнее,чтобы в то же время сохранить некий порядок и структуру разделения? Модульное программирование для меня пока не совсем ясно,вот такие ньюансы немного сбиваю с толку.
Опять, я не понял чётко постановку вопроса, но есть реально две разные вещи: TOKEN_CONST_INT, TOKEN_CONST_FLOAT - это один enum, относящийся к лексическому анализатору (парсеру). Есть другой enum: CONST_VAL_INT, CONST_VAL_FLOAT, относящийся к представлению констант. Это две совершенно разные вещи (поэтому я их и выделяю префиксами TOKEN_ и CONST_ чтобы не путать). Или вопрос в чём-то в другом? То что ты сразу же задаёшься вопросами правильного распределения по муодям - это хорошо, но по опыту знаю, что всё равно идеально не получится Правильное ощущение выработается со временем, а здесь опять та же проблема - ты пока ещё не понимаешь, как в и тоге всё получится В общем поэтому я и говорю, что не надо бояться эксприментировать, ошибаться и переделывать, ибо пока на своём опыте не увидишь, как делается "неправильно", то никогда на уровне ощущений не поймёшь, как делать "правильно"

Добавлено через 13 минут 16 секунд
Немного поглядел исходники - у тебя с терминологией что-то в кучу намешано
  • Value (сокращённо val) - это непосредственное числовое значение: 5, 10, 12.345
  • Constant (сокращённо const) - это некое абстрактное понятие, которое содержит в себе значение (Value), но при этом содержит дополнительные атрибуты, описывающие тип значения. В моём понятии Value я не выделяю в отдельный класс, я выделяю лишь класс Constant, который содержит поле value
  • Variable (сокращённо var) - это переменная, имеющая имя, чтобы по этому имени можно было обращаться. Перменная в каждый момент времени содержит в себе какое-то значение, т.е. переменная внутри себя содержит поле класса Constant

Т.е. что-то типа того

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
// ------------------ это интерфейс модуля констант
 
// Тип значения (константы) - этот enum строго относится только к модулю констант
enum ConstantType
{
  CONST_TYPE_NULL = 0,  // ввожу это поле, чтобы "полезные" значения были ненулевыми
  CONST_TYPE_INT,
  CONST_TYPE_FLOAT,
};
 
// Типы для хранения целой и плавающей константы
typedef long long const_Int_t;
typedef double const_Float_t;;
 
// Класс описывает значение, как абстрактную единицу. Над экземплярами класса
// можено осуществлять арифметические операции. Конкретное значение
// и его тип являются полями класса
class Constant
{
  private:
    // Эти два поля описывают значение (value) константы
    ConstantType type;
    union
    {
      const_Int_t ival;
      const_Float_t fval;
    } value;
 
  public:
    // С конструкторами я, возможно, тебе нагнал, ибо опять-таки плохо Си++ знаю
 
    // Конструктор для создания целой константы
    Constant (const_Int_t ival);
 
    // Конструктор для создания плавающей константы
    Constant (const_Float_t fval);
 
    // Конструктор для создания константы по строковому представлению
    // Внутри синтаксического анализатора ты будешь пользоваться именно этим конструктором
    Constant (string str, ConstantType type);
 
...
}
 
// ------------------ это интерфейс модуля переменных
 
// Класс, описывающий именованный объект (т.е. переменную), который
// ХРАНИТ значение. Т.е. в переменную можно только прочитать или записать
// значение. Операций над переменными мы НЕ делаем, операции делаются
// ТОЛЬКО НАД ЗНАЧЕНИЯМИ (константами)
//
// Таким образом для конструкции a=b+1 надор атомарных действий такой
// 1. Прочитать значение переменной b (т.е. получить константу, хрянащуюся в b)
// 2. Создать константу, значение которой в строковой форме представлено как "1"
// 3. Выполнить операцию "+" над константами из пунктов 1 и 2
// 4. Записать константу, полученную в пнкте 3 в качестве текущего значения
//    переменной a
class Variable
{
  private:
    string name;
    Constant value; // текущее значение переменной
...
}
Возможно опять что-то сумбурно, ты справшивай если что непонятно. Но старайся вопрос задать так, чтобы его можно было понять

Добавлено через 18 минут 27 секунд
По поводу твоего кода

C++
1
ConstVar::ConstVar(const std::string parser_CurToken, Types VarType)
не создавай параметр с именем parser_CurToken, ибо это какая-то обязаловка получается. Модуль у тебя независимый, парсер тут не при чём, поэтому пусть имя параметра будет независимым (str или что-то типа того)

Операция сложения должна выглядеть так (по отношению именно к твоему коду):

C
1
2
3
4
5
6
7
8
9
10
11
12
if (is_float && prev.IsFloat())
  // f + f
  constant.fval += prev.GetFVal();
else if (is_float && prev.IsInt())
  // f + i
  constant.fval += prev.GetIVal();
else if (!is_float && prev.IsFloat())
  // i + f
  constant.ival += prev.GetFVal();
else
  // i + i
  constant.ival += prev.GetIVal();
Т.е. при сложении значений разных типов тип результата совпадает с типом первого аргумента

Кстати, когда есть ситуация, которая кажется тебе ошибочной (ты отметил её "Types messing Error?"), сразу влепи туда вызов своей процедуры error, а то потом забудешь и долго будешь отлаживать, программу

Добавлено через 7 минут 4 секунды
Класс, который я назвал Constant можно назвать Value, чтобы тебе меньше было путанницы. Тогда у тебя остаётся только два понятия: Value и Variable, а понятия Constant как такового не будет

Добавлено через 2 минуты 50 секунд
syntax_parserPrimary написан правильно (в итоге он получается предельно примитивным)

Добавлено через 2 часа 2 минуты 40 секунд
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ConstVar syntax_parserPrimary()
...
            case TOKEN_IDENT: {
                // По имени переменной узнаём Variable. При этом понимаем,
                // что переменные у нас живут всё время работы интерпретатора и
                // как с константами на локальных переменных тут не получится
                Variable *variable = .....
 
                // Возвращаем значение, записанное в переменной
                return variable->getValue();
            }
...
            default: ;
                // Выводим синтаксическую ошибку. gcc пишет что-то типа "syntax error near <TokenStr>"
...
Добавлено через 2 часа 3 минуты 22 секунды
Да, по поводу видимости переменных. Попробую условно объяснить на пальцах, как это в компиляторах делается (чтобы ты имел представление о том, как тебе гибко написать код). Заводится некое понятие "таблица переменных", которая содержит в себе список переменных (или массив - не суть). Для глобальных объектов заводится одна таблица. Обрабатываем процедуру, заводим вторую таблицу и локалы процедуры затаскаваем в неё. Обрабатываем лексический блок (фигурные скобки), заводим третью таблицу и локалы лексического блока пишем уже туда. таким образом тыблицы у тебя образуют что-то типа стека. Ну и далее когда встречается использование переменной, то ищес в таблице, которая на вершине стека, если не нашли - то дальше и т.п.

Тебе пока предлагаю ограничиться лишь одними глобмальными переменными. При этом ты заводишь понятие таблицы переменных, но таблица у тебя будет одна. А потом в случае чего добавишь
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
10.07.2009, 20:54  [ТС]     Пишем свой интерпретатор языка BASIC #38
Сейчас читаю про неявные преобразования.
Есть одна неувязка.При смешении типов,где гарантия,что это не приведет к потере данных и их искажению? То есть,если мы плюсуем 4 (long) и число,заведомо большее,чем границы диапазона long,то что произойдёт с этими данными? Я считаю,тут нужно пересмотреть смешение типов,или сделать какой-то алгоритм,проверяющий выход за границы,exeption,или приводить тип long к doube,но не наоборот. Ведь такая проблема может возникнуть?
P.S. Но в принципе я сам уже ответил на свой вопрос,что все исключения добавляю потом,как всё заработает,так что зря написал,наверное.Просто первой мыслью было запретить смешение типов,вот и всполошился,но уже вижу,что просто можно добавить исключения потом.
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
17014 / 5419 / 335
Регистрация: 30.03.2009
Сообщений: 14,667
Записей в блоге: 26
11.07.2009, 00:25     Пишем свой интерпретатор языка BASIC #39
Сечас мы делаем ровно то, что делается в языках Си\Си++. Т.е.

C
1
2
3
long a, b;
double c;
a = b + c;
трактуется как

C
1
a = b + (long)c;
По стандарту Си в данном случае если значение "c" выходит за рамки диапазона long'а, то значение "(long)c" неопределено (т.е. стандарт ничего не говорит, что должно быть в этом случае и ситуация по сути дела является некорректной).

Ну и действительно, ты сам всегда в момент вычисления результат операции можешь сам всё проверить и запрограммировать так, как душе угодно. И всё это будет внутри модуля констант. Ну а пока наша задача сделать синтаксический разбор и добраться-таки до print'а.

Добавлено через 2 минуты 27 секунд
Т.е. мне кажется, что когда мы это допилим, а потом, допустим, сделаем if'ы, то тебе сразу многие вещи станут гораздо понятнее, после того, как ты их реализуешь. В том числе и вещи, которые мы ещё только будем делать потом, но у тебя скорее всего должно появиться понимание, как оно будет сделано. Так это или нет - скоро, думаю, узнаем
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
12.07.2009, 02:33     Пишем свой интерпретатор языка BASIC
Еще ссылки по теме:

Задание: разработать "Интерпретатор языка". С чего начать? C++
По русскому названию языка программирования определить английское название этого языка C++
C++ Перепишите пожалуйста код программы с языка Visual Basic в C++
Не удается откомпилировать интерпретатор М-языка C++
Пишем свой класс, спецификатор доступа protected C++

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

Или воспользуйтесь поиском по форуму:
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
12.07.2009, 02:33  [ТС]     Пишем свой интерпретатор языка BASIC #40

Не по теме:

перед этим сообщением было сообщение-вопрос,и оно было удалено модератором


В моём коде нет функции __tmain,или ты что-то поменял,или среда поменяла за тебя сама.Возможно,функция main в проектах на VS должна называться tmain,но точно не знаю.
Yandex
Объявления
12.07.2009, 02:33     Пишем свой интерпретатор языка BASIC
Закрытая тема Создать тему
Опции темы

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