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

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

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

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

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

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

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

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

Добавлено через 16 минут 19 секунд
И ещё опечатка была
C++
1
2
3
                 if (double d = prim (true)) {
                     left /= d;// было left /= prim (true)
                     break;
Лучшие ответы (1)
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
23.09.2009, 00:27  [ТС]     Пишем свой интерпретатор языка BASIC #141
Цитата Сообщение от Evg Посмотреть сообщение
Можешь тест добавить к себе в набор (не пропадать же ему)
Беру на вооружение.Вообще сейчас займусь папкой с тестами,наберу небольшую базу,ну и попробую допилить DIM.
Цитата Сообщение от Evg Посмотреть сообщение
Правда у тебя есть косячок. Если сделать переход на метку, но метку не определять, то у тебя падает в кору.
Это проверю.
Цитата Сообщение от Evg Посмотреть сообщение
И твоя и моя версия страдают недостатком выдачи диагностики - временами без поллитры не разберёшь, что оно хочет. Современные умные компиляторы позволяют более точно жиагностировать ошибку - они указывают не только номер файла и строки, но ещё и номер позиции в строке. Понятное дело, что сейчас это вообще не критично, но это ещё одна вещь, которая нужна для хорошего программного продукта - нормальный диалог с пользователем. Просто для порядка имей в виду
Ок,я понял,попытаюсь добавить.
Цитата Сообщение от Evg Посмотреть сообщение
Я так понимаю, что тестовый пример в репозитории отражает все поддерживаемые конструкции?
Да,в принципе,но возможны варианты этих конструкций,я просто не углублялся.Например,IF теперь,если не заканчивается на EOL,то должен заканчиваться на END IF именно двумя раздельными словами.Кстати,я уже задумываюсь о том,как делать правильный выход из программы,что нужно ведь,наверное,освободить сначала всю память,а потом уже делать выход?
Цитата Сообщение от Evg Посмотреть сообщение
Добавлено через 2 минуты
Смотрю ты концепцию условных переходов переделал. Ты просто содрал или всё-таки разобрался, чем плох старый вариант?
Да,я во всём разбирался,я именно делал сначала не подумав,а потом уже разбирался,почему мой вариант был плох,то есть,не просто содрал,а понял,почему так и так.Хотя сознаюсь,для меня изобретение велосипедов-очень тяжёлое занятие,и если я вижу,что есть уже что-то,что может подойти,я непременно это использую,и только в последнюю очередь буду напрягать мозги,прямо напасть какая-то.
Цитата Сообщение от Evg Посмотреть сообщение
Добавлено через 11 минут
В общем больше не получилось подобрать примеры, чтобы интерпретатор ломался или работал неправильно. В исходниках копаться уже совсем тяжело, так что пока прими поздравления: то, что уже есть, с виду работает так как надо
Можно сказать,что,по большому счёту,это твои исходники,я просто портировал их для компиляции на свой лад.. Просто так получилось методом проб и ошибок,и конечно,лени.. Конечно,там много мусора,я подумал,что,может стоит сделать Statement с помощью классов? А то у меня сокрытие данных превращается в кучу функций,ведь у меня сама структура только в .cpp файле...
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
23.09.2009, 09:28     Пишем свой интерпретатор языка BASIC #142
Цитата Сообщение от #pragma Посмотреть сообщение
Кстати,я уже задумываюсь о том,как делать правильный выход из программы,что нужно ведь,наверное,освободить сначала всю память,а потом уже делать выход?
По большому счёту можно не заморачиваться и выходить. Все данные у тебя живут в течение всей программы, а потому на проблему освобождения динамической памяти можно забить. Я вижу только две причины, чтобы сделать освобождение памяти:
- это хороший тон программирования
- при помощи специальных программ можно будет отслеживать утечки памяти (т.е. в тот момент когда ты освободил всё, утилиты смогут найти фрагменты, который оказались неосвобождёнными)

Цитата Сообщение от #pragma Посмотреть сообщение
я подумал,что,может стоит сделать Statement с помощью классов? А то у меня сокрытие данных превращается в кучу функций,ведь у меня сама структура только в .cpp файле...
Тут я тебе не советчик. У меня нет С++'ного мышления. Когда я поначалу для тренировки решил попробовать написать на Си++, то понял, что при моём подходе нормальная концепция Си++ скорее мешает, чем помогает
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
23.09.2009, 22:40  [ТС]     Пишем свой интерпретатор языка BASIC #143
Пришлось переделывать представление для LET в файле Statement.(это связано с тем,что в левой части присваивания может быть элемент массива,а его индекс,имя массива и значение элемента нужно где-то хранить).
Возник вопрос,скорее,относящийся к чистому С,поскольку данная конструкция пришла оттуда и в принципе это ты мне показал данный способ организации данных.Промежуточная схема теперь выглядит так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
         struct { // Данные для LET
            union {                 // левая часть присваивания {
               struct {
                  Variable    *var;
               } var;
               struct {
                  Array *array;
                  size4 *index;
                  Value *value;
               } arr_el;
            } data;                 // левая часть присваивания }
            expr_Node_t *expr;      // правая часть присваивания
         } let;
Вопрос в том,как можно узнать на этапе калькуляции(когда мы уже интерпретируем и меняем значение переменных),что записано в let.data - arr-el или var?Можно ли полагаться на sizeof? Есть ли способ узнать,куда были записаны данные,в arr_el или в var,или нужно применять какие-то костыли,наподобие дополнительной переменной - флага?
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
24.09.2009, 00:18     Пишем свой интерпретатор языка BASIC #144
Цитата Сообщение от #pragma Посмотреть сообщение
Вопрос в том,как можно узнать на этапе калькуляции(когда мы уже интерпретируем и меняем значение переменных),что записано в let.data - arr-el или var?Можно ли полагаться на sizeof? Есть ли способ узнать,куда были записаны данные,в arr_el или в var,или нужно применять какие-то костыли,наподобие дополнительной переменной - флага?
Только дополнительную переменную. И это не костыль. Ибо если хранить данные в union'е, то обязательно нужно дополнительное поле, по которому возможно понять, какое из полей union'а у нас инициализировано

Добавлено через 33 минуты
Цитата Сообщение от #pragma Посмотреть сообщение
(это связано с тем,что в левой части присваивания может быть элемент массива,а его индекс,имя массива и значение элемента нужно где-то хранить)
Я всё это храню в в виде выражения (которое может быть либо узлом переменной, либо декомпозицией массива) - см. interp_InterpStatementLET
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
26.09.2009, 02:20  [ТС]     Пишем свой интерпретатор языка BASIC #145
Кое-как смог сделать массивы,хотя не уверен,полностью ли правильно,буду тестировать.Как-то у меня громоздко получилось,запутано всё так.У тебя по виду всё так просто сделано с массивами,мне же пришлось добавлять функциональности чуть ли не по всей программе,и ещё осталось (особенно связано с типизацией).
Но с виду работает.Рабочий пример (в ревизии 30):
PureBasic
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
   IF B1 THEN LET C=1: LET D=2 ELSE LET C=10: LET D=20
   PRINT C
   PRINT
   PRINT D
   PRINT
 
   ' Повторяем предыдущий код, но с таким условием, чтобы исполнилось ELSE
   IF B1-460 THEN LET C=1: LET D=2 ELSE LET C=10: LET D=20
   PRINT C
   PRINT
   PRINT D
   PRINT
 
   'Here we define and calculate array
    DIM array(3,10)
 
    LET array(0,9) = 2
 
    LET i = 0
    LET j = 0
 
    WHILE i < 3
      WHILE j < 10
           LET array(i,j) = i+j
           LET j = j+1
      WEND
      LET i = i+1
    WEND
 
    LET i = 0
    LET j = 0
 
    WHILE i < 3
      WHILE j < 10
           PRINT "array(",i,",",j,") is: ",array(i,j)
         PRINT
           LET j = j+1
      WEND
      LET i = i+1
    WEND
 
   ' Считаем сумму чисел от 1 до 100
   LET COUNT=0
   1:
   WHILE COUNT<100
      LET COUNT=COUNT+1
      PRINT COUNT," "
      IF COUNT<50 THEN GOTO 1 ELSE GOTO 2
   WEND
   2:
   PRINT
   PRINT "END OF PROGRAM"
Добавлено через 54 минуты
С
таким примером
PureBasic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
' Работа с многомерным массивом
DIM AA(2,3,4)
LET COUNT = 100
LET i = 0
WHILE i < 2 
  LET j = 0
  WHILE j < 3 
    LET k = 0
    WHILE k < 4 
      LET COUNT = COUNT + 1
      LET AA(i,j,k) = COUNT
      LET k = k + 1
    WEND
    LET j = j + 1
  WEND
  LET i = i + 1
WEND
PRINT COUNT     
PRINT
PRINT AA(1,2,3) 
PRIN
прога вылетает - vector out of range - где-то напутал с индексами,буду чинить...

Добавлено через 19 минут
Всё,вроде поправил.Evg,а утебя работают цвета в консоли,если скриптом прогоняешь тесты? А то я сделал,но не знаю,будет ли везде работать. Я слышал,что это может не работать на некоторых терминалах/системах (в смысле другие управляющие символы для цветов).
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
26.09.2009, 12:54     Пишем свой интерпретатор языка BASIC #146
Красным цветом по крайней мере выделяется. У тебя по ходу не влиты файлы с эталонными выдачами. А вообще цвета - это от терминала зависит. В своё время сталкивался с тем, что на солярисовых родных терминалах это не работает. Так что на цвета я уже давно забил. Обычно я пишу "PASS" и "****** FAIL ******", чтобы "FAIL" на фоне всего чётко выделялось. Но ты можешь пока оставить себе цвета, если это удобнее

По поводу индексации массивов - в бэйсике она начинается с 1, а не с нуля. Т.е. при формировании номера элемента в многомерной декомпозиции из каждого индекса надо вычесть 1. А так же есть DIM A (10 TO 20) - в этом случае надо вычитать 10. У меня пока это не сделано, но в комментариях обозначено. А 0 это частный случай (вычитаем 0, а потому ничего не меняется).

В принципе ты сам волен выбирать, как оно должно быть, но просто лучше делать так, как принято. Я по крайней мере буду приближать максимально к настоящему qbasic'у

А строковые массивы у тебя работают? А то основные конструкции вроде бы как уже реализованы, а потому уже пора заниматься наведением порядка. И в программе и в синтаксисе
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
26.09.2009, 13:00     Пишем свой интерпретатор языка BASIC #147
**

Добавлено через 1 минуту
> С таким примером прога вылетает
Потому что у меня индексация с 1, а у тебя с нуля

Добавлено через 3 минуты
PureBasic
1
LET B=A
PureBasic
1
2
DIM A(2)
LET B=A(1)
Первый пример у тебя нормально ломается с сообщением, что A неинициализировано, а второй - нет
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
26.09.2009, 17:19  [ТС]     Пишем свой интерпретатор языка BASIC #148
Цитата Сообщение от Evg Посмотреть сообщение
**
Первый пример у тебя нормально ломается с сообщением, что A неинициализировано, а второй - нет
Я где-то прочитал,что по умолчанию все элементы массива равны нулю.Они все обнуляются в interp_stmtDIM.
Про идексацию я тоже прочитал что с нуля начинается якобы,но,видимо,книга была не правильная Значит,надо переделать.
Насчёт эталонных выдач - не держу их в проекте.Задумка такая - там есть файл README в корневом каталоге,в нём написано,что если планируются изменения в исходниках,то сначала запускается скрипт tmpl_maker,он всё создаёт,а после изменений уже запускается test_script.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
26.09.2009, 17:48     Пишем свой интерпретатор языка BASIC #149
Цитата Сообщение от #pragma Посмотреть сообщение
Я где-то прочитал,что по умолчанию все элементы массива равны нулю.Они все обнуляются в interp_stmtDIM.
На самом деле в бэйсиках вообще все переменные по умолчанию обнуляются, а не только массивы. А строковые переменные и массивы пробиваются пустыми строками. Просто мы по началу сделали так, чтобы проще было жить (да и что-то типа отладочного средства получилось). В идеале мне видится так, что всё должно быть, как в обычном бэйсике, но по доп. опции включается поддержка контроля неинициализированных переменных. В любом случае это совйство для обычной переменной и для элемента массива должно быть одинаковым

Цитата Сообщение от #pragma Посмотреть сообщение
Насчёт эталонных выдач - не держу их в проекте.Задумка такая - там есть файл README в корневом каталоге,в нём написано,что если планируются изменения в исходниках,то сначала запускается скрипт tmpl_maker,он всё создаёт,а после изменений уже запускается test_script.
Это неправильно. Предположим у тебя работает. Далее я копирую к себе твои исходники и перекомпиливаю. Но на моей машине оно работает неправильно - например есть косяк, зависящий от использования неинициализированной переменной. У тебя он проявляется одним способом, у меня другим. Далее я получаю типа эталон, который на самом деле оказывается неправильным. Поэтому эталонная выдача должна обязательно рядом с тестом лежать. Если при изменении интерпретатора эталонная выдача меняется (изменился формат печати, справлена ошибка и т.п.), то эталонную выдачу надо править. И никуда от этого не денешься
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
28.09.2009, 05:00  [ТС]     Пишем свой интерпретатор языка BASIC #150
Кое-что переделал,теперь неинициализированное значение в массиве вызывает ошибку.Поправил работу со строками и контроль типов для массивов.Попробовал переделать индексацию с нуля на 1,но что-то запутался с индексами и вернул всё назад.Надо просто сесть нарисовать как-то для себя,что-бы иметь представление,где точно менять индексы,а то у меня там кавардак с векторами.Ведь и общая формула доступа к элементу должна измениться? Сейчас она выглядит так(я её на всякий случай прописал в комментах(частный случай для 3-мерного массива),а то там цикл непонятный такой =) ) :
C++
1
array[a][b][c] = linear_array[a*y*z + b*z + c]
И получается вроде как нужно вычесть единицу из каждого индекса при умножении... Ну я как-нибудь соберусь с мыслями и сделаю :/

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

Ещё есть вопрос про FOR: по идее,эту инструкцию тоже можно отнести к Conditional Branch,но нужно тогда правильно сгенерировать условие,так? Я имею в виду,что условие придётся генерировать как-то вручную,ведь это не стандартное выражение,а ещё как-то вручную добавить в конце блока новый LET чтобы менял значение счётчика на нужную величину.Или всё таки лучше делать FOR как отдельную конструкцию?
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
28.09.2009, 09:31     Пишем свой интерпретатор языка BASIC #151
Цитата Сообщение от #pragma Посмотреть сообщение
C++
1
array[a][b][c] = linear_array[a*y*z + b*z + c]
И получается вроде как нужно вычесть единицу из каждого индекса при умножении... Ну я как-нибудь соберусь с мыслями и сделаю :/
Да. В правой части должно быть "linear_array[(a-1)*y*z + (b-1)*z + (c-1)]". А в общем случае надо вычитать стартовый индекс (который по умолчанию равен 1, но по синтаксису можно задавать самому)

Цитата Сообщение от #pragma Посмотреть сообщение
Я вот незнаю,а как лучше сделать выключающуюся поддержку проверки неинициализированных переменных: через параметр командной строки,или через перекомпиляцию с помощью препроцессора? В принципе,и тот и другой вариант можно оправдать.Командная строка - удобство,гибкость.Препроцессор - всё-таки это не стандартная версия QBASIC,и вроде как надо перекомпилить,да и бинарник уменьшит,хоть и ненамного.Хотя стандартная версия всё равно не получится,так вроде командная строка перевешивает.Как считаешь?
Любая пользовательская настройка должна быть в виде опции. ТОлько поведение на мой взгляд надо инвертировать. По умолчанию работать так, как принято в бэйсиках, а по опции - ломаться на неинициализированных данных. Эта опция - по сути дела отладочное средство. Технически удобнее всего делать так, что в момент заведения новой переменной ей сразу прописывать нулевую инициализацию

Цитата Сообщение от #pragma Посмотреть сообщение
Ещё есть вопрос про FOR: по идее,эту инструкцию тоже можно отнести к Conditional Branch,но нужно тогда правильно сгенерировать условие,так? Я имею в виду,что условие придётся генерировать как-то вручную,ведь это не стандартное выражение,а ещё как-то вручную добавить в конце блока новый LET чтобы менял значение счётчика на нужную величину.Или всё таки лучше делать FOR как отдельную конструкцию?
Да, нужно сгенерировать правильный набор операций сравнений, условных переходов и т.п. Т.е. при поддержке FOR'а изменения вносятся только в синтаксический разборщик. В промежуточном представлении и интерпретаторе не надо делать ничего. Имено поэтому такой вид промежуточного представления даёт дополнительные бонусы: т.е. многие сложные высокоуровневые конструкции реализуются через один и тот же маленький набор низкоуровневых примитивов

Т.е. промежуточное представление для

PureBasic
1
2
3
for i=1 to 10 step 2
  <тело цикла>
next i
должно выглядеть точно так же, как для

PureBasic
1
2
3
4
5
let i=1
while i<=10
  <тело цикла>
  let i=i+2
wend
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
29.09.2009, 06:42  [ТС]     Пишем свой интерпретатор языка BASIC #152
При попытке сделать индексацию с единицы нашёл какой-то странный баг.При печати в данном исходнике печать первого и второго цикла расходятся.
Исходник
PureBasic
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
   'Here we define and calculate array
   DIM array(3,10)
 
   LET i = 1
   LET j = 1
   LET n = 1
 
   WHILE i < 4
     WHILE j < 11
          LET array(i,j) = n
      PRINT "array(",i,",",j,") is: ",array(i,j)
          PRINT
      LET j = j+1
          LET n = n+1
     WEND
       LET i = i+1
       LET j = 1
   WEND
   
   PRINT
   LET i = 1
   LET j = 1
 
   WHILE i < 4
     WHILE j < 11
     PRINT "array(",i,",",j,") is: ",array(i,j)
         PRINT
     LET j = j+1
     WEND
       LET i = i+1
       LET j = 1
   WEND

Сначала грешил на формулу,но когда вставил печать в первый цикл,вообще попал в тупик,так как она верная.Вообще,если после первого цикла менять значение array(3,1) или array(2,1),то меняются также значения
array(2,10) и array(1,10) соответственно.Считал по формуле - получается,что у них в линейном массиве тот же индекс.
PureBasic
1
DIM array(3,10)
C++
1
2
linear_index(2,10) = (2-1)*(10-1) + (10-1) = 18
linear_index(3,1) = (3-1)*(10-1) +(1-1) = 18
Но верная печать в первом массиве ставит в тупик.В чём дело-пока загадка.На всякий случай скомиттил 33 ревизию.Там индексация с единицы,добавлена опция командной строки для проверки неинициализированных переменных,называется --noinit-errors.Теперь формат запуска программы такой:
Bash
1
 ./basin [option] [filename]
Также в случае ошибки синтаксиса или выполнения память освобождается.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
29.09.2009, 11:05     Пишем свой интерпретатор языка BASIC #153
Вычитать 1 нужно только из индекса. Размерность массива остаётся прежней
Вместо
C
1
2
linear_index(2,10) = (2-1)*(10-1) + (10-1) = 18
linear_index(3,1) = (3-1)*(10-1) +(1-1) = 18
должно быть
C
1
2
linear_index(2,10) = (2-1)*(10) + (10-1) = 19
linear_index(3,1) = (3-1)*(10) +(1-1) = 20
Добавлено через 7 минут
Пока не забыл, что на будущее. Чего нужно было бы сделать, но что нужно, а что нет - решай сам. В идеале надо сделать всё

1. Поддержать массивы с произвольного стартового индекса. Типа "DIM A(5 TO 10)
2. По возможности поддержать все синтаксические реализации для IF, WHILE, FOR (ну и какие там ещё есть циклы)
3. Видимо придётся наводить порядок в исходниках программы. Когда много чего работает - наверняка накопилось куча мест, которые неплохо бы переделать
4. Возможно сразу же попытаться реализовать вариант с генерацией исходника на Си++ с целью получения бинарника (о чём я уже рассказывал). Такой вариант уже имеет рассматривать с той точки зрения, что при дальнейшем развитии можно будет поддерживать обе ветки (интерпретация и генерация текста Си++), а потому многие неоптимальности видны будут сразу. Из разряда того, что при нынешней схеме реализация конструкции FOR НЕ затронула бы процесс интерпретации и генерации текста, т.к. в промежуточное представление мы не вносили новых конструкций

Ну и дальнейшим пунктом развития будет реализация встроенных функций (SIN, COS, VAL, STR$, ...)
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
30.09.2009, 14:25     Пишем свой интерпретатор языка BASIC #154
Если смотрел мои исхожники, там есть конструкция var_DimDescr_t, описывающая размерность массива. Эту же конструкцию я использую и для описания элемента массива (которая по научному называется "декомпозиция"). Это неправильно (мне тогда просто лень было, а потом забыл). Просто должны быть две вещи:

- Описатель размерностей массива (на каждую размерность хранится стартовый индекс и размер). Этот описатель является свойством переменной
- Описатель декомпозиции элемента массива (на каждую размерность хранится текущее значение индекса). Этот описатель является свойством операции
Номер элемента вычисляется на основании двух этих конструкций.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
30.09.2009, 21:16     Пишем свой интерпретатор языка BASIC #155
Кстати, вот ещё идея. Сделать "OPTION TRACE_CMD <val>", "OPTION TRACE_WRITE <val>". Пользовательские фичи для включения трассировки исполнения операций и трассировки записей. Т.е. если есть блок кода, который предположительно работает неправильно, то командами "OPTION TRACE_WRITE 1" и "OPTION TRACE_WRITE 0" соотвественно включаем и выключаем трассировку
#pragma
Временно недоступен
 Аватар для #pragma
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
30.09.2009, 21:33  [ТС]     Пишем свой интерпретатор языка BASIC #156
Цитата Сообщение от Evg Посмотреть сообщение
Кстати, вот ещё идея. Сделать "OPTION TRACE_CMD <val>", "OPTION TRACE_WRITE <val>". Пользовательские фичи для включения трассировки исполнения операций и трассировки записей. Т.е. если есть блок кода, который предположительно работает неправильно, то командами "OPTION TRACE_WRITE 1" и "OPTION TRACE_WRITE 0" соотвественно включаем и выключаем трассировку
В смысле навроде того,что у нас пишется в логи? Только там что будет писаться,что-то недопонимаю? UPD Всмысле будут писаться текущие значения какой-то переменной?
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
30.09.2009, 21:41     Пишем свой интерпретатор языка BASIC #157
Ну навроде того, только это то, что для пользователя предназначено. При печати записей печатать что-то типа

Код
a.bas:10 WRITE A = 10
a.bas:11 WRITE B = 11
...
При печати трассировки кода печатать что-то типа

Код
exec a.bas:10
exec a.bas:11
exec a.bas:12
В отсутсвие отладчика комбинация этих двух печатей или одна из них способна сильно упростить процесс отладки. Ну это так. Воспомогательная функция, напрямую к интерпретатору не относящаяся

Добавлено через 56 секунд
Когда у тебя два заумных вложенных цикла с кучей ветвлений внутри, тяжело "теоретически" разбирать ошибки
Mozart
 Аватар для Mozart
39 / 17 / 1
Регистрация: 21.08.2009
Сообщений: 63
01.10.2009, 14:48     Пишем свой интерпретатор языка BASIC #158
Господа, увидел вашу тему, решил наваять что-то подобное, но у меня возникла проблема:
сначала сделал линейно и у меня выходило что 2+2*2=8, хотя 6;
потом немного видоизменил код добавив рекурсию в итоге предыдущее считает правильно а 3-1+2=0 хотя 4.
Как вы решали проблему приоритета умножения/деления над сложением/вычитанием?
ЗЫ: если брать все в скобки - считает правильно.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
01.10.2009, 16:32     Пишем свой интерпретатор языка BASIC #159
Создай, пожалуйста, отдельную тему и кинь мне ссылку на неё вличку. Я там всё напишу. Чтобы здесь вторую дискуссию не устраивать
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
01.10.2009, 23:28     Пишем свой интерпретатор языка BASIC
Еще ссылки по теме:

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

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

Или воспользуйтесь поиском по форуму:
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16824 / 5245 / 320
Регистрация: 30.03.2009
Сообщений: 14,125
Записей в блоге: 26
01.10.2009, 23:28     Пишем свой интерпретатор языка BASIC #160
Я что-то смотрю, у тебя одни негативные тесты. А позитивные-то есть?
Yandex
Объявления
01.10.2009, 23:28     Пишем свой интерпретатор языка BASIC
Закрытая тема Создать тему
Опции темы

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