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

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

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

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

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

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

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

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

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

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

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

Добавлено через 16 минут 19 секунд
И ещё опечатка была
C++
1
2
3
                 if (double d = prim (true)) {
                     left /= d;// было left /= prim (true)
                     break;
30
Лучшие ответы (1)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.06.2009, 20:03
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Пишем свой интерпретатор языка BASIC (C++):

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

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

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

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

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

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

464
Evg
Эксперт CАвтор FAQ
18041 / 6273 / 428
Регистрация: 30.03.2009
Сообщений: 17,259
Записей в блоге: 28
28.06.2009, 12:08 #16
Код
a=10
b=11
c=12
d=13
e=a+b|c*d
#e
Код
Строка: 6 Ошибка: Неинициализированная переменная
0
1. Исправить ошибку (текст корректный, но интерпретатор отработал неправильно)
2. Когда пишешь "неинициализированная переменная", надо ещё указывать, какая переменная. Написано, что строка 6 "#e", я могу догадаться, что это e. Но если бы ошибка была на строке 5, а там выражение из тридцати слагаемых, то надо долго разбираться, что же там неинициализировано.
3. В случае ошибки программа по прежнему отрабатывает до конца. Советую этот вопрос долго не откладывать, а отработать для себя механизм выдачи пользовательской ошибки, потому как чем раньше ты это сделаешь, тем меньше в будущем надо будет переделывать

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

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

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

Цитата Сообщение от #pragma Посмотреть сообщение
4)Есть ограничение на битовые операции с числами с плавающей точкой
Следующий код отрабатывает без ошибок (хотя там число с плавающей точкой)

Код
a = 10.0 | 11

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

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

Добавлено через 2 минуты 58 секунд
А код по процедурным вызовам удали пока, чтобы не мешал. Ибо это большой паравоз, прицепленный к твоей программе, который ты пока не знаешь как работает - он только занимает место и мозолит глаза

Добавлено через 10 минут 40 секунд
Кстати, положи к себе в проект ещё и текстовый файл, в который будешь складывать все найденные ошибки. А исправленные ошибки переносить в другой файл (но не удалять - для истории полезно их сохранить)
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
29.06.2009, 01:02  [ТС] #17
Хотел уточнить свои слова насчёт проблемы с вызовом функций(ну очень хочется доделать)
Цитата Сообщение от Evg Посмотреть сообщение
Поясни на конкретном примере, а то в общих словах я ничего не понял
В-общем как я понимаю в данном конкретном случае проблема сводится к тому,как правильно послать готовую строку (которую я уже приготовил для вычисления,например "a*b") обратно в поток ввода.То есть у меня получается что когда из функции get_token() возвращается значение FUNC,за этим следует вызов expr(true) с последующими вызовами get_token(),но в потоке для чтения нет строки,которую я приготовил и отослал функцией putback в строке 342: source.putback(::result_expr[j]); В итоге при прилагаемых входных данных программа печатает 'a',которая следует за именем функции и находится в списке параметров,а моя готовая строка ещё до этого куда-то девается..Вот я думаю может я неправильно это сделал(в смысле работал с потоком)?
Вот прилагаю исходник,если не лень поглядеть,конечно.А насчёт svn это в интернете репозиторий создается,навроде как в launchpad сделано?Бесспорно,это удобнее...есть ещё cvs какой-то.
main.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 "LexicalAnalyzer.h"
#include "error_handler.h"
 
 
int main()
{
    table["pi"]=3.1415926535897932385;
    table["e"]=2.7182818284590452354;
    source.open("program.clc",std::ifstream::in);
    while (source.good()) {
        get_token();
        if (err_state==GLOBAL_ERROR)break;
        if (curr_tok==END) break;
        if (curr_tok==PRINT) continue;
        double x=expr(false);
        if (print_var==PRINT_VAR)
        {
           std::cout<<x<<'\n';
           print_var = DONT_PRINT_VAR;
        }
    }
    source.close();
    return 0;
}

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
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
#ifndef LEXICALANALYZER_H_INCLUDED
#define LEXICALANALYZER_H_INCLUDED
 
#include <cctype>
#include <string>
#include <map>
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <fstream>
 
extern std::ifstream source;
 
enum Token_value {
    NAME,       NUMBER,      END,       FUNC = '$',
    PLUS = '+', MINUS = '-', MUL = '*', DIV = '/',
    PRINT = ';',ASSIGN = '=',LP = '(',  RP = ')',
    AND = '&',  XOR = '^',   OR = '|',  NOT = '~',
    BEGIN_FUNC = '@',COMMENT = '"'
};
 
enum Printer_state {
    PRINT_VAR,DONT_PRINT_VAR
};
 
 
 
typedef std::map<std::string,int>Map_of_names;
typedef std::map<std::string,double>Map_of_table;
 
extern Token_value curr_tok;
extern Printer_state print_var;
 
extern std::map<std::string,double>table;
extern std::vector<std::string>initialized_vars;
extern std::string string_value;
 
extern std::map<std::string,int>func_name;
extern std::vector<std::string>func_value;
extern std::map<std::string,int>func_num_of_param;
extern std::string result_expr;
extern std::string get_resulting_expr(std::string,std::string,std::string,int);
 
 
Token_value get_token();
 
double expr(bool);
double term (bool);
double prim (bool);
 
 
 
#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
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#include "LexicalAnalyzer.h"
#include "error_handler.h"
 
 
std::map<std::string,double>table;
Token_value curr_tok = PRINT;
Printer_state print_var = DONT_PRINT_VAR;
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 /= d;
                     break;
                 }
                 return error(divide_by_zero);
            case AND:
                 if (err_state != BIN_TO_DOUBLE)
                 {
                     left = static_cast<int>(left) & static_cast<int>(prim(true));
                     break;
                 }
                 return error(binary_to_double);
            case XOR:
                 if (err_state != BIN_TO_DOUBLE)
                 {
                     left = static_cast<int>(left) ^ static_cast<int>(prim(true));
                     break;
                 }
                 return error(binary_to_double);
            case OR:
                 if (err_state != BIN_TO_DOUBLE)
                 {
                     left = static_cast<int>(left) | static_cast<int>(prim(true));
                     break;
                 }
                 return error(binary_to_double);
            default:
                 return left;
        }
}
 
double number_value;
std::string string_value;
std::map<std::string,int>func_name;
std::vector<std::string>func_value;
std::map<std::string,int>func_name_to_param;
std::vector<std::string>initialized_vars;
std::string result_expr;
std::ifstream source;
int string_number=0;
 
double prim (bool get)
{
    if (get) get_token();
    switch (curr_tok){
 
        case NUMBER:{
                    double& v = ::number_value;
                    get_token();
                    if (curr_tok==LP)
                        return error(wrong_expression);
                    return v;
        }
        case NAME:{
                    double& v = table[::string_value];
                    if (get_token()==ASSIGN)
                    {
                        ::initialized_vars.push_back(::string_value);
                        v = expr(true);
                        return v;
                    }
                    else
                    {
                        int i = count(::initialized_vars.begin(),::initialized_vars.end(),::string_value);
                        if (i == 0)
                        {
                            table.erase(::string_value);
                            return error(unknown_variable,::string_value);
                        }
                    }
                    return v;
        }
        case MINUS:
                   return -prim(true);
        case NOT:{
                   if (err_state != BIN_TO_DOUBLE)
                   {
                       int e = ~static_cast<int>(prim(true));
                       if (err_state == BIN_TO_DOUBLE)
                       {
                            return error(binary_to_double);
                       }
                       return static_cast<double>(e);
                   }
                   else
                       return error(binary_to_double);
        }
        case LP:{
                   double e = expr(true);
                   if (curr_tok!=RP)
                       return error(lp_expected);
                   get_token();
                   return e;
        }
        case FUNC:{
                   double e = expr(true);
                   return e;
        }
        case COMMENT:
                     get_token();
                     return 0;
        default:
                  return error(primary_expr_expected);
    }
}
 
Token_value get_token()
{
    char ch = 0;
 
 
    if (curr_tok == COMMENT)
        do {
               if (!source.get(ch))
               return curr_tok = END;
 
           }
        while (ch!='\n');
    /*if (curr_tok == FUNC)
        do {
               if (!source.get(ch))
               return curr_tok = END;
 
           }
        while (ch!='\n');*/
    do {
        if (!source.get(ch)) return curr_tok = END;
 
    } while (ch!='\n'&&isspace(ch));
 
    switch (ch) {
        case 0:
                 return curr_tok = END;
 
        case ';':
                 return curr_tok = PRINT;
 
        case '\n':
                 ++string_number;
                 return curr_tok = PRINT;
 
        case '"':return curr_tok =COMMENT;
 
        case '*':case '/':case '+':case '-':case '(':case ')':
        case '=':case '~':case '&':case '^':case '|':
                 return curr_tok=Token_value(ch);
 
        case '0':case '1':case '2':case '3':case '4' :
        case '5':case '6':case '7':case '8':case '9':
                 source.putback(ch);
                 source>>number_value;
                 if(number_value-static_cast<int>(number_value)!=0)
                 err_state = BIN_TO_DOUBLE;
                 return curr_tok = NUMBER;
 
        case ',':return curr_tok = PRINT;
 
        case '#':
                 print_var = PRINT_VAR;
                 return curr_tok = PRINT;
 
        case '@':{
                 // Writing whole string to build a function.
                 // @ - reserved sign for start user function
                 // programming.
 
                 // Copying whole function string to tmp
                 std::string tmp;
                 tmp = ch;
                 while (source.get(ch)&&ch!='\n') tmp.push_back(ch);
                 source.putback(ch);
 
                 // Set useful places in the string
                 unsigned int i = find(tmp.begin(),tmp.end(),'(')-tmp.begin();
                 unsigned int k = find(tmp.begin(),tmp.end(),')')-tmp.begin();
 
                 // There must be parameters list in the function
                 if (i==tmp.size()||k==tmp.size())
                 {
                     error("Ожидался список параметров");
                     return curr_tok = PRINT;
                 }
 
                 // Checking function name for previous declaration
                 std::string s = tmp.substr(1,i-1);
                 Map_of_names::iterator it = ::func_name.find(s);
                 if (it != ::func_name.end())
                 {
                     error("Повторное определение функции");
                     return curr_tok = PRINT;
                 }
                 else
                 {
                    // Declaration
                    unsigned int j = find(tmp.begin(),tmp.end(),'{')-tmp.begin();
 
                    // Function must have body
                    if (j==tmp.size())
                    {
                        error("Ожидалась {");
                        return curr_tok = PRINT;
                    }
 
                    // Not empty body
                    if (tmp.substr(j+1,tmp.size()-j-2).size()<1)
                    {
                        error("Пустая функция");
                        return curr_tok = PRINT;
                    }
 
                    // At start of parameters list,as at the end,
                    // have to be an alphabet character
                    std::string param_list = tmp.substr(i+1,k-i-1);
                    if (isalpha(param_list.at(0))&&isalpha(param_list[param_list.size()-1]))
                    {
                        // Function template creation
                        ::func_name_to_param[s] = count(param_list.begin(),param_list.end(),',')+1;
                        ::func_name[s];
                        ::func_value.push_back(tmp.substr(i,tmp.size()-i));
                        return curr_tok = PRINT;
                    }
                    else
                    {
                        error("Неправильный список параметров");
                        return curr_tok = PRINT;
                    }
                 }
                 return curr_tok = PRINT;
             }
        case '}':case '{':{
                 // End of user function
                 source.putback(ch);
                 return curr_tok = PRINT;
             }
        case '$':{
                 // $ - reserved for starting to calculate
                 // function that was programmed before
 
                 // Writing function with parameters
                 // that we need to calculate to tmp
                 std::string tmp;
                 tmp = ch;
 
                 while (source.get(ch)&&(ch != '\n')) tmp.push_back(ch);
                 source.putback(ch);
 
                 // Set useful places in the string
                 unsigned int i = find(tmp.begin(),tmp.end(),'(')-tmp.begin();
                 unsigned int k = find(tmp.begin(),tmp.end(),')')-tmp.begin();
 
                 // Checking for function was declared before
                 std::string nm_tmp = tmp.substr(1,i-1);
                 Map_of_names::iterator it = ::func_name.find(nm_tmp);
                 if (it == ::func_name.end())
                 {
                     error("Функция не была объявлена ранее");
                     return curr_tok = PRINT;
                 }
                 else
                 {
                     std::string param_list = tmp.substr(i+1,k-i-1);
                     if (isalnum(param_list[0])&&isalnum(param_list[param_list.size()-1]))
                     {
                         // Count parameters and checking amount of
                         // parameters in declared function
                         int param_count = count(param_list.begin(),param_list.end(),',')+1;
                         if (param_count> ::func_name_to_param[nm_tmp]||
                             param_count< ::func_name_to_param[nm_tmp])
                         {
                             error("Неправильное количество параметров функции");
                             return curr_tok = PRINT;
                         }
                         ///////////////////////////////////////////
                         /**  Our function is "OK".               */
                         ///////////////////////////////////////////
                         std::string main_param_list = ::func_value[::func_name[nm_tmp]].substr(1,
                                                                                         ::func_value[::func_name[nm_tmp]].find_first_of(')',1)-1);
 
                         ::result_expr = get_resulting_expr(param_list,
                                                            main_param_list,
                                                            ::func_value[::func_name[nm_tmp]],
                                                            ::func_name_to_param[nm_tmp]);
                        // ::result_expr.push_back('\n');
 
                         for (int j = ::result_expr.size(); j >= 0; --j)
                         source.putback(::result_expr[j]);
                         return  curr_tok = FUNC;
 
                     }
                     else
                     {
                         error("Неверный параметр функции");
                         return curr_tok = PRINT;
                     }
                 }
                 return curr_tok = PRINT;
             }
        default:
             if (isalpha(ch)) {
                 ::string_value = ch;
                 while (source.get(ch)&&isalnum(ch)) ::string_value.push_back(ch);
                 source.putback(ch);
                 return curr_tok = NAME;
             }
             error (wrong_expression);
             return curr_tok = PRINT;
    }
}

error_handler.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
#ifndef ERROR_HANDLER_H_INCLUDED
#define ERROR_HANDLER_H_INCLUDED
 
#include <string>
#include <iostream>
 
enum Error_state {
    BIN_TO_DOUBLE,NO_ERRORS,
    GLOBAL_ERROR
};
 
extern Error_state err_state;
 
 
const std::string divide_by_zero        = "Деление на ноль";
const std::string binary_to_double      = "Битовые операции могут выполняться только над целыми типами";
const std::string unknown_variable      = "Неинициализированная переменная";
const std::string lp_expected           = "Ожидалась )";
const std::string primary_expr_expected = "Ожидалось первичное выражение";
const std::string wrong_expression      = "Неправильная лексема";
 
extern int string_number;
 
int error(const std::string&);
int error(const std::string,const std::string);
 
#endif // ERROR_HANDLER_H_INCLUDED

error_handler.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "LexicalAnalyzer.h"
#include "error_handler.h"
 
Error_state err_state = NO_ERRORS;
 
int error(const std::string& s)
{
    ++string_number;
    ::err_state=GLOBAL_ERROR;
    ::print_var = DONT_PRINT_VAR;
    std::cerr<<"Строка: "<< ::string_number<<" Ошибка: "<<s<<'\n';
    return 0;
}
int error(const std::string s,const std::string v)
{
    ++string_number;
    err_state=GLOBAL_ERROR;
    print_var = DONT_PRINT_VAR;
    std::cerr<<"Строка: "<< ::string_number
             <<" Ошибка: "<<s<<' '<<"'"<<v<<"'"<<'\n';
    return 0;
}

param_swapper.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
#include "LexicalAnalyzer.h"
 
// Function get parameters and temlate in
// strings of two functions and returns
// resulting string as expression for
// future calculation
// Просто жесть
 
std::string get_resulting_expr(std::string s_son_par,
                               std::string s_father_par,
                               std::string s_father_expr,
                               int param_count)
{
    std::string resulting_expr;
    if (param_count==1)
    {
        //To be continued?
    }
    else
    {
        // Store parameters
        std::vector<std::string>tmp_son_par;
        std::vector<std::string>tmp_father_par;
 
        // Using iterators to find parameters
        std::string::iterator iter_s_son = find(s_son_par.begin(),s_son_par.end(),',');
        std::string::iterator iter_s_father = find(s_father_par.begin(),s_father_par.end(),',');
 
        // First parameter
        tmp_son_par.push_back(s_son_par.substr(0,(iter_s_son-s_son_par.begin())));
        tmp_father_par.push_back(s_father_par.substr(0,(iter_s_father-s_father_par.begin())));
 
        if (param_count > 2)
        {
           // All parameters exept first and last
           for (int i = 1; i < param_count-1; ++i)
           {
               iter_s_son = find(iter_s_son,s_son_par.end(),',');
               iter_s_father = find(iter_s_father,s_father_par.end(),',');
               tmp_son_par.push_back(s_son_par.substr(iter_s_son-s_son_par.begin(),
                                                 (find(iter_s_son,s_son_par.end(),',')-iter_s_son)-1));
               tmp_father_par.push_back(s_father_par.substr(iter_s_father-s_father_par.begin(),
                                                 (find(iter_s_father,s_father_par.end(),',')-iter_s_father)-1));
           }
 
           // Last parameter
           tmp_son_par.push_back(s_son_par.substr(iter_s_son-s_son_par.begin(),
                                                  iter_s_son-s_son_par.end()));
           tmp_father_par.push_back(s_father_par.substr(iter_s_father-s_father_par.begin(),
                                                        iter_s_father-s_father_par.end()));
        }
        if (param_count == 2)
        {
           // Second parameter
           tmp_son_par.push_back(s_son_par.substr(iter_s_son-s_son_par.begin(),
                                                  iter_s_son-s_son_par.end()));
 
           tmp_father_par.push_back(s_father_par.substr(iter_s_father-s_father_par.begin(),
                                                        iter_s_father-s_father_par.end()));
        }
 
        // Finally replace parameters from son
        // to father expression
        unsigned int pos   = 0;
        unsigned int l = s_father_expr.size();
        for (int i = 0; i < param_count;++i)
        {
            for (pos = 0;pos <s_father_expr.size();)
            {
                // There is a mistake in algorithm.
                // Last parameter isnt getting swapped.
                pos = s_father_expr.find(tmp_father_par.at(i));
                if (pos>l)break;
                s_father_expr.replace(pos,tmp_father_par.at(i).size(),tmp_son_par.at(i));
 
            std::cout<<"DEBUG "<<s_father_expr<<std::endl;
            }
        }
        // Using now useless pos
        pos = s_father_expr.find_first_of('{',0);
        resulting_expr = s_father_expr.substr(pos+1,s_father_expr.size()-pos-2);
    }
    std::cout<<"DEBUG "<<resulting_expr<<std::endl;
    return resulting_expr;
}

Входной файл:
program.clc
a=10.6
b=2
@f(c,d){c*b}
#$f(a,b)
0
Evg
Эксперт CАвтор FAQ
18041 / 6273 / 428
Регистрация: 30.03.2009
Сообщений: 17,259
Записей в блоге: 28
29.06.2009, 20:53 #18
Цитата Сообщение от #pragma Посмотреть сообщение
В-общем как я понимаю в данном конкретном случае проблема сводится к тому,как правильно послать готовую строку (которую я уже приготовил для вычисления,например "a*b") обратно в поток ввода.То есть у меня получается что когда из функции get_token() возвращается значение FUNC,за этим следует вызов expr(true) с последующими вызовами get_token(),но в потоке для чтения нет строки,которую я приготовил и отослал функцией putback в строке 342: source.putback(::result_expr[j]); В итоге при прилагаемых входных данных программа печатает 'a',которая следует за именем функции и находится в списке параметров,а моя готовая строка ещё до этого куда-то девается..Вот я думаю может я неправильно это сделал(в смысле работал с потоком)?
Видишь ли, такой подход он неправильный. Лексический разбор в таких простых случаях должен идти без putback'ов. Т.е. разобрал имя функции, разобрал левую скобку, дальше с текущей позиции запускается разбор выражения для параметра. Именно идёт разбор, а не формируется строка. Далее по выходу из процедуры разбора выражения ты будешь иметь уже вычисленное значение параметра, адальше тебе останется разобрать правую скобку, после чего сделать вызов. При этом в интерпретируемых языках, содержащих процедуры, как правило строится сначало некое промежуточное представление, а затем работа идёт на нём (без входного потока). Я, собственно, потому и предлагал тебе пока забить на вызов функций, потому как надо сначала до конца добить простые вещи. Но я тебе не навязываю своё мнение, если есть желание сделать функцию - попробуй

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

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

Цитата Сообщение от #pragma Посмотреть сообщение
А насчёт svn это в интернете репозиторий создается,навроде как в launchpad сделано?Бесспорно,это удобнее...есть ещё cvs какой-то.
Можно и на локальной машине Я поначалу делал просто на файловой системе и доступ через файловую систему. А потом переделал на сетевой вариант. В итоге у меня svn сервер запущен под виндой, под vmwar'ой запущен линух и через виртуальную сеть из-под линуха работаю с svn-репозиторием, который физически находится под виндой

Добавлено через 10 часов 14 минут 45 секунд
Глубоко не смотрел, но поглядел поверхностно исходники. По теущему положению тебе действительно будет сложно дальше развивать интерпретатор. Со своей стороны могу тебе предожить для начала навести порядок. Затем чётко разбить на компоненты (потому как у тебя пока мешанина): аккуратно сделать механизм обработки ошибок, грамматический анализатор, лексический анализатор, разбиение на операторы (statement), коих у тебя пока работающих только два (оператор присваивания и оператор печати), таблицу переменных. Затем добавлять поддержку новых конструкций.

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

Если есть какие-то идеи, вопросы и т.п. - не стесняйся, спрашивай. Пока я добрый, помогу

Добавлено через 12 минут 53 секунды
Для такого примера не ловит использование неинициализированной переменной b во 2-й строке
Код
a=10.6
b=2+b
#a
#b
Добавлено через 2 минуты 27 секунд
Здесь в 3-й строке должна либо выдаваться ошибка, либо печататься все значения

Код
a=10.6
b=2
#a b b b
#b
Добавлено через 1 минуту 54 секунды
Аналогично долдна быть синтаксическая ошибка (ну или любое другое сообщение об ошибке)
Код
b=2 2
Или, судя по последним двум примерам, перенос строки игнорируется и здесь фактически два оператора "b=2" и пустышка "2"?

Добавлено через 3 минуты 58 секунд
Неправильно учитывается приоритет операций. Следующий пример демонстрирует это. При вычислении b поставлены скобки (которые по большому счёту не нужны, только для показывания в каком порядке должны идти вычисления). Оба выражения должны дать один и тот же результат

Код
a=2+4|5*6
b=(2+4)|(5*6)
#a
#b
Код
32
30
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
30.06.2009, 05:11  [ТС] #19
Пытаюсь избавиться от этой проблемы
C++
1
a = 10.0 | 11
И вот тут у меня вопрос.При посимвольном разборе данной конкретной строки текущий символ ch в функции get_token никогда не принимает значение '.' (точка).
1)Почему?
2) Как её поймать? Я уже успел поискать про машинную запись чисел с плавающей точкой,я так понимаю,этот ньюанс требует пересмотра посимвольного разбора с помощью putback? Где-то там по дороге эта точка и теряется между цифрами...
0
Evg
Эксперт CАвтор FAQ
18041 / 6273 / 428
Регистрация: 30.03.2009
Сообщений: 17,259
Записей в блоге: 28
30.06.2009, 12:23 #20
Лучший ответ Сообщение было отмечено автором темы, экспертом или модератором как ответ
Точка не должна отлавливаться. У тебя "10.0" должно идти как единая грамматическая единица (token). Собственно, потому я и предложил навести порядок, что логически у тебя некорректно написано.

Сейчас у тебя get_token выдирает число по одной циферке. Это не есть правильно. get_token за раз должен выдрать целиком грамматическую единицу. Т.е. если записано "123", то за раз будет выдрано "123", если "123.45", то "123.45". А вот если "123.45.67", то первая лексическая единица будет "123.45", а следующая "." (или ".67", если понимать дополнительный вариант записи плавающих чисел). Если это ключевое слово "print", то и будет "print" (с чем у тебя на текущий момент проблемы). Сейчас у меня под рукой нет формальных описаний, но если желаешь - ознакомлю тебя с формальными описаниями грамматики и лексики

Добавлено через 1 минуту 40 секунд
> Я уже успел поискать про машинную запись чисел с плавающей точкой,я так
> понимаю,этот ньюанс требует пересмотра посимвольного разбора с помощью
> putback?

Нет. Как я уже писал, get_token за раз должен выдрать "123.45", котору затем стандартными функциями ты превратишь в плавающее число. Возможно, я пока объясняю слишком непонятно, но если ты морально готов к перелопачиванию своей программы, могу начать пояснять более подробно.

Добавлено через 5 минут 0 секунд
Хотя нашёл в инете пример формального описания грамматики. Так что если надо - могу вкратце пояснить суть работы грамматического анализатора

Добавлено через 2 часа 51 минуту 6 секунд
================================================================

В общем, появилось немного свободного времени на работе. Так что родил примерно следующее пояснение

Есть две вещи разного уровня: грамматика и лексика. Грамматика - это по сути дела правила построения слов из отдельных букв. Лексика - построение предложений из слов (а последние построены по правилам грамматики)

Давай рассмотрим простейший вариант того, какие грамматические единицы (token'ы) должны поддерживаться нашим интерпретатором:
  • константы (числа): целочисленные и плавающие
  • идентификаторы (имена переменных)
  • ключевые слова: в нашем случае пока только PRINT, но для простоты в будущем развитии я бы ввёл ещё LET
  • знаки операций, которые принято называть разделителями (delimiter) : + - * / | & ~ =
  • признак конца строки (поскольку конец строки будет являться разделителем между предложениями)
  • признак конца файла

Задача парсера, который по сути дела является грамматическим анализатором, является нарезка входного текста на слова (token'ы). При этом грамматический анализатор будет пропускать комментарии, ненужные пробелы и знаки табуляции. Мы будем считать, что один вызов GetToken (я всё-таки обзову именно так, чтобы не путать с тем, что сейчас есть у тебя) вынимает из входного потока одно слово (token). Как это будет представлено на уровне данных, пока не рассматриваем (чисто чтобы теорию понять)

Вот для такого примера:

Код
let a = 5.123 # комментарий
print a
последовательные вызовы GetToken должны вернуть следующий набор значений:
  • Ключевое слово (keyword) LET
  • Идентификатор (identifier) A
  • Операция (delimiter) =
  • Плавающая константа (float constant) 5.123
  • EOL (конец строки)
  • Ключевое слово PRINT
  • Идентификатор A
  • EOL (конец строки)
  • EOF (конец файла)

При этом комментарии за пределы парсера вообще не вылезают. Дабы остальным компонентам с ними не возиться

Теперь, как это всё должно выглядеть технически. По результату вызова GetToken фактически должен возвращать в качестве результата некие два значения. Первым значением является непосредственное строковое представление слова, которое полезно для печати диагностики, отладки, а так же необходимо для разбора идентификаторов и констант. Т.е. для нашего примера этими строковыми значениями будут "LET", "A", "=", "5.123" и т.д. Вторым значением является значение некоего enum'а, которое удобно обрабатывать в виде целочисленного значения и которое является описанием того, что у нас записано в строке. Таким образом пара этих значений полностью описывает наш token

Как конкретно сделать enum - зависит от того, как тебе удобно работать.
Я бы сделал так:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum parser_TokenType
{
  TOKEN_NULL = 0,
 
  TOKEN_CONST_INT,   // целочисленная константа
  TOKEN_CONST_FLOAT, // плавающая константа
 
  TOKEN_IDENT,       // идентификатор
 
  TOKEN_KW_LET,      // ключевое слово LET
  TOKEN_KW_PRINT,    // ключевое слово PRINT
 
  TOKEN_DELIM_EQUAL, // знак "="
  TOKEN_DELIM_PLUS,  // знак "+"
  TOKEN_DELIM_MINUS, // знак "-"
  ....
 
  TOKEN_EOL,         // конец строки
  TOKEN_EOF,         // конец файла
 
  TOKEN_LAST
};
При этом для значений TOKEN_CONST_INT, TOKEN_CONST_FLOAT и TOKEN_IDENT нам необходимо второе значение (которое описывает строковое представление token'а), а в остальных случаях элемент enum'а полностью описывает наш token

В итоге интерфейс нашего грамматического анализатора будет примерно таким:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* Инициализация парсера. В качестве file_name подаём имя файла,
 * с которым работаем. В случае проблем с открытием файла возвращаем
 * false, если всё в порядке - true */
extern bool parser_Init (const char *file_name);
 
/* Процедура parser_GetToken вынимает очередное слово из нашего входного
 * потока. Порезультату работы записываются переменные parser_CurToken
 * и parser_LastTokenStr. Процедура parser_GetToken возвращает то же значение,
 * что записывается в parser_CurToken (для удобства работы).
 * В перменные parser_CurFile, parser_CurLine записывается информация
 * о положении в файле текущего token'а (для выдачи ошибок) */
extern parser_TokenType parser_GetToken (void);
extern parser_TokenType parser_CurToken;
extern char *parser_CurTokenStr;
extern char *parser_CurFile;
extern unsigned parser_CurLine;
 
/* Отладочная печать текущего token'а */
extern void parser_PrintCurToken (void);
 
/* Завершение работы */
extern void parser_Finish (void);
Попробуй наваять парсер (который по сути является грамматическим анализатором) примерно по такому интерфейсу. Либо меняй его на своё усмотрение, лишь бы остался принцип того, что на один вызов GetToken вытаскивается грамматическая единица целиком. И заодно встрой в программу под макросом или опцией отладочную печать из-под своего парсера. Тут тоже можешь экспериментировать. Печать может быть интегрирована прямо вовнутрь GetToken'а, можно её вызывать снаружи. Лишь бы тебе было удобно по печатям отслеживать процесс работы

Ну и весь грамматический анализатор полезно выделить в отдельный файл

Добавлено через 14 минут 34 секунды
Вот примерное формальное описание грамматики:

Код
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"
То, что написано с заглавной буквы, представляет собой правило. То, что в кавычках - непосредственно указанные внутри кавычек символ(ы). Символ | означает один из вариантов, то, что заключено в фигурные скобки - это ноль или более потворений того, что заключено в них (скобках)

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

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

Не по теме:

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

0
Evg
Эксперт CАвтор FAQ
18041 / 6273 / 428
Регистрация: 30.03.2009
Сообщений: 17,259
Записей в блоге: 28
01.07.2009, 08:17 #24
Э... я просто не совсем понял, идею чего ты мне описал Как сделать вариант с продолжением работы после ошибки? Это называется error recovering (восстановление из ошибочных ситуаций). На твоём месте я бы пока этого не делал, т.к. это довольно сложная проблема и решать её имеет смысл только тогда, когда у тебя интерпретатор в более-менее серьёзном виде заработает
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
02.07.2009, 06:40  [ТС] #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     << ' '
                       ....
   }
0
Evg
Эксперт CАвтор FAQ
18041 / 6273 / 428
Регистрация: 30.03.2009
Сообщений: 17,259
Записей в блоге: 28
02.07.2009, 11:04 #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 на начало разбора). В скриптовых языках принято, что комментарий
  // начинается символом '#' и заканчивается в конце строки
}
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
05.07.2009, 04:30  [ТС] #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 кб сэкономилось)
0
Вложения
Тип файла: rar Interpreter.rar (3.3 Кб, 197 просмотров)
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
05.07.2009, 05:02  [ТС] #28
P.S.Я так полагаю,мне придётся искать инфу по бейсику в сети? Честно,не помню уже этот язык.
Да,ещё забыл-NextToken почему ещё оставил: подумал,что будет удобно проверять составные операторы,типа << , += , := , и т.д.
0
Evg
Эксперт CАвтор FAQ
18041 / 6273 / 428
Регистрация: 30.03.2009
Сообщений: 17,259
Записей в блоге: 28
05.07.2009, 11:42 #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)
1
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
06.07.2009, 23:30  [ТС] #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 ===|
Такое ощущение,что сам шаблон не компилируется.Всё это я пытаюсь сделать,чтобы потом не пришлось переписывать и перекраивать пол-программы,ну и хочется сократить количество функций.
0
06.07.2009, 23:30
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
06.07.2009, 23:30
Привет! Вот еще темы с ответами:

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
30
Закрытая тема Создать тему
Опции темы

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