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

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

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

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

20.06.2009, 20:03. Просмотров 189588. Ответов 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;
Лучшие ответы (1)
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.06.2009, 20:03     Пишем свой интерпретатор языка BASIC
Посмотрите здесь:
Пишем свой чекер C++
C++ пишем свой троян с нуля
Пишем свой класс, спецификатор доступа protected C++
C++ Интерпретатор небольшого языка программирования на С++
Не удается откомпилировать интерпретатор М-языка C++
Написать Интерпретатор Программного Языка(собственного) C++
Интерпретатор/компилятор ассемблер-подобного языка C++
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
17.08.2009, 20:31     Пишем свой интерпретатор языка BASIC #91
Теперь что касается всякийх циклов и условных исполнений. Нам дополнительно понадобится одна-единственная операция условного перехода (CONDITIONAL_GOTO)

C
1
2
3
4
5
6
7
8
9
10
11
12
struct
{
  // Условие перехода. Здесь будет подвешено дерево, аналогичное тому,
  // что ты видел в моей реализации Expr, только добавятся ещё операции
  // сравнения
  CondExpr *cond_expr;
 
  // Две метки. Переход на одну произойдёт, если условие true, на другую -
  // если условие false
  Statement *label_true;
  Statement *label_false;
} cond_goto;
Итого, имея три операции LABEL, GOTO и COND_GOTO ты в промежуточном представлении можешь соорудить любую операцию передачи управления, начиная от циклов WHILE или FOR и заканчивая всякими операциями типа "ON <expr> GOTO"

Например, для такого кода

PureBasic
1
2
3
4
5
6
LET a = 10
IF a < 3 THEN
  b = 20
ELSE
  с = 30
LET d = 40
Промежуточное представлении будет выглядеть так

Код
STMK_LET, var=a, val=10 (в нормальном варианте в правой части будет Expr)
STMK_COND_GOTO, cond_expr=<дерево "a<3">, label_true=L1, label_false=L2
STMK_LABEL, условно пишу L1, чтобы по тексту можно было понять. Реально из STMK_IF сюда будет торчать ссылка
STMK_LET, var=b, val=20
STMK_GOTO, label=L3
STMK_LABEL, условно пишу L2
STMK_LET, var=c, val=30
STMK_LABEL, условно пишу L3
STMK_LET, var=d, val=40
А для такого кода

PureBasic
1
2
3
WHILE a < 3
  LET c = 1 ; <-- это внутри цикла
LET d = 2
Промежуточное представление такое

Код
STMK_LABEL, L1
STMK_COND_GOTO, cond_expr=<дерево "a<3">, label_true=L2, label_false=L3
STMK_LABEL, L2
STMK_LET, var=c, val=1
STMK_GOTO, label=L1
STMK_LABEL, L3
STMK_LET, var=d, val=2
Если случалось писать на ассемблере, то можешь увидеть, что этот код по своей структуре уже несколько похож на ассемблерный код
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
18.08.2009, 22:39  [ТС]     Пишем свой интерпретатор языка BASIC #92
Ещё одна мелочь.Вот тут
C++
1
2
3
4
5
6
7
       union
       {   // Данные для LET
           struct
           {
               Variable *var; // левая часть присваивания
               Value *val;  // правая часть присваивания
           } let;
Правая часть присваивания может быть выражением,в таком случае нужна ещё одна структура Expression,так? И в ней уже таким же образом указатель next определяет следующий элемент,а в конце разбора выражения,когда встречается EOL,next получает нулевое значение,и так заканчиваем цепочку,так?
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
19.08.2009, 20:17     Пишем свой интерпретатор языка BASIC #93
Expression нет смысла провязывать в цепочку. Это же по сути дерево. Statement'ы мы в цепочку провязываем для того, чтобы всё это потом обходить последовательно. Т.е. вместо Value у тебя будет указатель на Expression, который, по аналогии с моим примером выглядить что-то типа того

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
// Тип узла дерева
enum ExprKind
{
  EK_CONST, // элемент дерева представляет собой константу
  EK_VAR, // элемент дерева представляет собой переменную
  EK_UNOP, // элемент дерева представляет собой унарную операцию
  EK_BINOP, // элемент дерева представляет собой бинарную (двухаргументную) операцию
};
 
// Тип операции (сюда включаем и унарные и бинарные)
enum OpKind
{
  OP_PLUS,
  OP_MINUS,
  ...
}
 
struct Expression
{
  ExprKind kind;
 
  union
  {
    struct
    {
      Value *val;
    } const;
    struct
    {
      Variable *var;
    } var;
    struct
    {
      OpKind;
      Expression *operand;
    } unop;
    struct
    {
      OpKind;
      Expression *operand1;
      Expression *operand2;
    } binop;
  } data;
}
Добавлено через 1 минуту 31 секунду
Цитата Сообщение от #pragma Посмотреть сообщение
Правая часть присваивания может быть выражением
Я описывал простой случай (чтобы понятно было). Реально правая часть ОБЯЗАНА (а не "может быть") выражением. Константа - это же тоже выражение. Построение дерева аккуратно укладывается в твой парсер (смотри мой пример про выражения)

Добавлено через 8 минут 59 секунд
Не забудь сделать отладочную печать представления. При этом каждому Statement'у надо присваимвать уникальный номер. Тогда в операциях перехода можно будет его печатать, чтобы визуально понять, куда у тебя реально идёт переход

Добавлено через 4 минуты 11 секунд
И сразу думай о том, как работать с массивами. Ибо, навскидку, это последний невыясненный момент

Ну и надо разобраться, как же реально в Q-Basic'е идёт работа с типом. Потму как надо уже под это дело подстраиваться

Добавлено через 20 часов 44 минуты 14 секунд
Смотрю исходники. По оформлению читать уже гораздо проще чем раньше. Но три пробела в начале строки по прежнему убивают наповал

Внутри syntax_ParseTerm ты в цикле вызываешь syntax_ParseTerm, хотя мне кажется, что надо всё-таки syntax_ParsePrimary. Проверить не могу, т.к. по тем исходникам из #87 ничего не могу запустить. На твой пример из #86 ругается "Wrong definition", а напрямую подавfть выражение в PRINT пока не работает. По идее то, что написано рабочее, но глубины воображения что-то не хватает понять, где это можетнакосячить (если накосячит вообще)

Добавлено через 2 минуты 46 секунд
AND, OR и XOR ты назвал BINARY_OP. Название этого термина переводится как "двухаргументная операция". Правильное названия - "побитовая операция" (BIT_OP или BITWISE_OP)

Добавлено через 2 минуты 19 секунд
Из syntax_parserIdentLValue ты вызываешь syntax_parserBinaryExpr, что не есть верно, т.к. у тебя в этом случае будет по синтаксису разрешена операция типа "A&B = 5"

Добавлено через 3 минуты 32 секунды
Фрагмент процедуры void syntax_parserStmtKwLET:

C++
1
2
        defined_vars.at(i) = syntax_parserBinaryExpr();
        defined_vars.at(i).SetInitialization(true);
По логике вещей как только ты в переменную делаешь присваивание, то внутри класса переменной должен автоматически взводиться признак инициализации, а не втавлять это в каждое место инициализации
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
19.08.2009, 22:37  [ТС]     Пишем свой интерпретатор языка BASIC #94
Насчёт wrong definition,скорее всего ты скопипастил код прямо из сайта,я уже писал,что названия типов должны быть маленькими буквами,это движок сайта меняет на большие,я уже задумываюсь о том,чтобы сменить с int на INT.

Насчёт 3-х пробелов - это я подглядел в где-то в исходниках GNU,и мне понравилось,потому как в той IDE,где я работаю,folding находится очень близко к краю,и часто код закрывается случайно.Ещё причина,почему это показалось удобным-так лучше видно препроцессорные команды из общей массы.То есть это не религиозное что-то,причина есть

Добавлено через 31 минуту 38 секунд
Насчёт LET A&B = 5 ,вроде у меня не пропускает подобные выражения,пишет "Wrong let statement".То есть LET гарантирует присваивание сразу после первого идетификатора.А вот с goto есть проблема,и правда.Как раз о чём ты говорил,внутри IF.Это происходит в момент,если например,после одного прыжка мы уже не заходим в IF,а пропускаем этот блок.
Насчёт выражений в PRINT-так и было задумано,выражения только в LET
Я начал что-то ваять с промежуточным кодом,пока только интерфейс tree.h - идёт туговато )
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
19.08.2009, 23:03     Пишем свой интерпретатор языка BASIC #95
Цитата Сообщение от #pragma Посмотреть сообщение
Насчёт wrong definition,скорее всего ты скопипастил код прямо из сайта,я уже писал,что названия типов должны быть маленькими буквами,это движок сайта меняет на большие,я уже задумываюсь о том,чтобы сменить с int на INT.
Точно. Совсем забыл. Надо будет потом тряхануть народ на предмет этого. Подозреваю, что бэйсику должно быть до фонара INT, int или InT

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

Цитата Сообщение от #pragma Посмотреть сообщение
Насчёт выражений в PRINT-так и было задумано,выражения только в LET
Нелогично

Цитата Сообщение от #pragma Посмотреть сообщение
Я начал что-то ваять с промежуточным кодом,пока только интерфейс tree.h - идёт туговато )
А что туго? Может опять начать с простых вещей (считать, что у нас есть только LET и PRINT)? И зачем tree.h? Выражения уже сами по своей структуре являются деревоподобными. Statement'ы будут в виде списка. Как-то деревья как некое универсальное понятие особо и не нужно

Добавлено через 4 минуты 59 секунд
С заменой INT'а на int заработало. При вводе когда нажал Ctrl^D (т.е. фактически конец ввода), прога ушла в бесконечный цикл и начала срать на экран, потом терминал завис

Добавлено через 1 минуту 42 секунды
Для такого кода напечаталось 0. ОШибка

PureBasic
1
2
3
DIM int a;
LET a = 6 * 5 / 11;
PRINT a
Добавлено через 13 минут 32 секунды
Я кстати понял причину этой ошибки. Текст спрячу. Если интересно - попробуй для начала сам разобраться

qqq
То, как у тебя построена процедура syntax_parserTerm даёт эффект, что операции начинают считаться справа налево, хотя должны слева направо. 5 * 6 / 11 должно считаться как "(5*6)/11" а у тебя считается как "5*(6/11)" - почитай внимательно формальное описание синтаксиса в посте #29
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
20.08.2009, 01:36  [ТС]     Пишем свой интерпретатор языка BASIC #96
Вот это новость! Как теперь это исправить,я даже не знаю )) Наверное я зря убрал динамическую память,помнится тогда всё работало (ну вроде )

Добавлено через 12 минут 55 секунд
Скорее всего это связано с тем,что передаваемое значение-это не просто значение,а объект.Видимо,объект по значению целиком не передаётся,если он не в динамической памяти,ведь это некая составная единица,и похоже,что так делать с объектами нельзя.То есть получается,что все промежуточные значения теперь теряются при передаче из функции,я попробую переделать.Но имеет ли смысл,вроде решено делать промежуточный код ?

Добавлено через 1 час 1 минуту 0 секунд
А может,ты прав,я заметил,что у меня выражение вроде как по другому сворачивается,но как теперь переделать-ума не приложу.

Деревья как отдельное понятие не нужны,но ведь надо же куда-то свалить все эти структуры?

Добавлено через 13 минут 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
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
    static Value syntax_parserPrimary()
    {
#if DEBUG && SYNTAX_PARSER_DEBUG
        debugger_Print(SYNTAX_PARSER_PRIMARY);
#endif
        switch (parser_GetToken())
        {
            case TOKEN_CONST_INT: {
                Value int_x(::parser_CurTokenStr, INT);
                return int_x;
            }
            case TOKEN_CONST_FLOAT: {
                Value float_x(::parser_CurTokenStr, FLOAT);
                return float_x;
            }
            case TOKEN_CONST_STRING: {
                Value string_x(::parser_CurTokenStr, STRING);
                return string_x;
            }
            case TOKEN_IDENT: {
                return syntax_parserIdentRValue();
            }
            case TOKEN_DELIM_MINUS: {
                return -syntax_parserPrimary();
            }
            case TOKEN_BINARY_NOT: {
                return ~syntax_parserBinaryExpr();
            }
            case TOKEN_LEFT_PARENTH: {
                Value lvalue;
                lvalue = syntax_parserBinaryExpr();
 
                if (::parser_CurToken != TOKEN_RIGHT_PARENTH)
                   error(RIGHTP_EXPECTED);
                return lvalue;
                }
            default:
                error(WRONG_EXPRESSION);
        }
        // This is bad
        //это просто чтобе не получать предупр.компилятора
        Value null_x("0",INT);
        return null_x;
    }
/* ************************************************************************** */
    static Value syntax_parserTerm ()
    {
#if DEBUG && SYNTAX_PARSER_DEBUG
        debugger_Print(SYNTAX_PARSER_TERM);
#endif
        // Начальное значение.
        Value lvalue;
        lvalue = syntax_parserPrimary();
 
        while (true){
            switch (parser_GetToken())
            {
                case TOKEN_DELIM_MULTIPLY:
                    lvalue = lvalue * syntax_parserPrimary();
                break;
                case TOKEN_DELIM_DIVIDE:
                     lvalue = lvalue / syntax_parserPrimary();
                break;
                default: return lvalue;
            }
        }
        return lvalue;
    }
Тесты прогнал-работает.
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
20.08.2009, 14:12     Пишем свой интерпретатор языка BASIC #97
С косяком при подсчёте выраения до конца разобрался? Или надо пояснить?

> Деревья как отдельное понятие не нужны,но ведь надо же куда-то свалить все эти структуры?

В каком смысле сваливать? Создаёшь новый узел Expression (через new), цепляешь его к представлению. Т.е. в Statement'е будет торчать ссылка на созданный узел Expression. А из него будут торчать ссылки на другие Expression'ы и Variable'ы. Или я вопрос как-то не так понял

Добавлено через 7 минут 58 секунд
Не забудь на данную ошибку тест написать, чтобы в будущем не проявилось

Добавлено через 4 часа 50 минут 57 секунд
У меня всё-таки $опа зачесалась и я накатал-таки упрощённый вариант интерпретатора с промежуточным представлением. Если интересно будет - можешь попробовать посмотреть
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
20.08.2009, 16:24  [ТС]     Пишем свой интерпретатор языка BASIC #98
Цитата Сообщение от Evg Посмотреть сообщение
С косяком при подсчёте выраения до конца разобрался? Или надо пояснить?
Да вроде,у меня неправильно до этого было,я и раньше замечал,но,видимо не заметил ошибку.Дело было по ходу как раз в в том что в цикле я вызывал syntax_parserTerm,а надо было Primary.
Цитата Сообщение от Evg Посмотреть сообщение
В каком смысле сваливать? Создаёшь новый узел Expression (через new), цепляешь его к представлению. Т.е. в Statement'е будет торчать ссылка на созданный узел Expression. А из него будут торчать ссылки на другие Expression'ы и Variable'ы. Или я вопрос как-то не так понял
Я имел ввиду что структуры данных Expression,Statement надо же где-то пристроить,так пусть лучше будут в отдельном файле.И потом уже при исполнении уже другая единица (по-моему этим уже не должен заниматься парсер синтаксиса)эти данные тоже будет подключать.
Цитата Сообщение от Evg Посмотреть сообщение
Добавлено через 4 часа 50 минут 57 секунд
У меня всё-таки $опа зачесалась и я накатал-таки упрощённый вариант интерпретатора с промежуточным представлением. Если интересно будет - можешь попробовать посмотреть
Интересно,конечно,прикрепи поглядеть.Я,конечно,растянул написание своей программы неимоверно долго,всё это можно было написать намного быстрее,я полагаю...
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
20.08.2009, 16:57     Пишем свой интерпретатор языка BASIC #99
Цитата Сообщение от #pragma Посмотреть сообщение
Я,конечно,растянул написание своей программы неимоверно долго,всё это можно было написать намного быстрее,я полагаю...
Интерпретатор - это всё-таки сложная программа. То, что ты растянул во времени - это не страшно. Объём большой, а в голове, не имея соотвествующего опыта, уложить всё сразу сложно. Главное - всё-таки дописать его до такого состояния, когда реально будет видно, что дальнейшее наращивание языка - это тупое добавление кода, а вся структура уже будет в устаканенном состоянии. Исходники выложу чуть позже (комментарии допишу)

Оговорю сразу несколько моментов, которые у меня пока не сделаны:
  • Нет нормальной поддержки условных выражений. Пока на их месте используются обычные арифметические, если там ненулевое значение, то считаем true, если нулевое false.
  • Метки и GOTO пока не делал
  • Ошибки, возникающие в момент интерпретации представления пока выдаются без привязки к исходнику
  • Список операторов сделал более похожим на Q-Basic: т.е. не с фигрными скобками, а через двоеточие
  • EOL считается концом оператора, т.е. у меня пока нельзя альтернативы THEN размазывать по нескольким строкам. Я пока не пытался строго поддержать синтаксис. В первую очередь хотелпоказать пример того, как работать с промежуточным представлением
  • PRINT пока кастрированный, в качестве параметра принимает только одно выражение

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

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

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
' Простая арифметика
LET A1 = 10 : LET A2 = 100
LET B1 = A1 + 5 * A2 - 50
PRINT B1
 
' Условное исполнение
' Пока сделано коряво. Как таковых операций сравнения просто нет, а потому
' на позиции условного выражения пока присутсвует фрифметическое выражение,
' а дпльше смотрим 0 (false) или не 0 (true)
IF B1 THEN LET C=1: LET D=2 ELSE LET C=10: LET D=20
PRINT C
PRINT D
 
' Повторяем предыдущий код, но с таким условием, чтобы исполнилось ELSE
IF B1-460 THEN LET C=1: LET D=2 ELSE LET C=10: LET D=20
PRINT C
PRINT D
 
' Считаем сумму чисел от 1 до 100
LET COUNT=0
LET SUM=0
WHILE COUNT-100 DO LET COUNT=COUNT+1: LET SUM=SUM+COUNT
PRINT COUNT
PRINT SUM
Код
s1. LET        A1 = i:10
s2. LET        A2 = i:100
s3. LET        B1 = ((A1 + (i:5 * A2)) - i:50)
s4. PRINT      B1
s8. CBRANCH    B1 -> t=s5 f=s6
s5. LABEL      #then
s9. LET        C = i:1
s10. LET       D = i:2
s11. BRANCH    -> s7
s6. LABEL      #else
s12. LET       C = i:10
s13. LET       D = i:20
s7. LABEL      #finish
s14. PRINT     C
s15. PRINT     D
s19. CBRANCH   (B1 - i:460) -> t=s16 f=s17
s16. LABEL     #then
s20. LET       C = i:1
s21. LET       D = i:2
s22. BRANCH    -> s18
s17. LABEL     #else
s23. LET       C = i:10
s24. LET       D = i:20
s18. LABEL     #finish
s25. PRINT     C
s26. PRINT     D
s27. LET       COUNT = i:0
s28. LET       SUM = i:0
s29. LABEL     #start
s32. CBRANCH   (COUNT - i:100) -> t=s30 f=s31
s30. LABEL     #loop
s33. LET       COUNT = (COUNT + i:1)
s34. LET       SUM = (SUM + COUNT)
s35. BRANCH    -> s29
s31. LABEL     #finish
s36. PRINT     COUNT
s37. PRINT     SUM
ну и результат исполнения

Код
460
1
2
10
20
100
5050
Добавлено через 15 минут 4 секунды
Вот исходники. interp.rar - в KOI-8, interp_w.rar - в WIN1251 (с виндовыми энтерами)

Трассировочные макросы:
lex.c - LEX_TRACE_TOKEN
statement.c - STMT_TRACE
interp.c - INTERP_TRACE

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

Добавлено через 1 минуту 25 секунд
В печати промежуточного представления надо смотреть именнопорядок, в котором всё печатается. То, что номера печатаются не по порядку - это фича, связанная с механизмом создания меток для переходов вперёд
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
20.08.2009, 16:58     Пишем свой интерпретатор языка BASIC #100
Фалй забыл приаттачить
Вложения
Тип файла: rar interp.rar (17.0 Кб, 51 просмотров)
Тип файла: rar interp_w.rar (17.4 Кб, 43 просмотров)
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
20.08.2009, 20:43     Пишем свой интерпретатор языка BASIC #101
Цитата Сообщение от #pragma Посмотреть сообщение
Я имел ввиду что структуры данных Expression,Statement надо же где-то пристроить,так пусть лучше будут в отдельном файле.И потом уже при исполнении уже другая единица (по-моему этим уже не должен заниматься парсер синтаксиса)эти данные тоже будет подключать.
Ну да, так оно и есть. Просто исходники условно можно разделить на две категории. Первые - это исхожники, описывающие некие хранимые данные. Это value, variable, сюда же добавятся expression и statement. Вторая категория - это исзодники, которые функциональные, т.е. осуществляют некоторую работу над этими данными - лексический анализатор, разбор синтаксиса, интерпретация промежуточного представления

Добавлено через 3 часа 9 минут 34 секунды
Цитата Сообщение от #pragma Посмотреть сообщение
А что обязательно следовать строго синтаксису basic? можно сделать просто свой диалект,какая разница,цель же не сделать полностью язык,а просто потренироваться... или в твоих планах сделать (точнее чтобы я сделал) что-то серьёзное,что потом кто-то будет использовать?
Язык же можно придумывать по ходу написания,или этот номер не пройдёт?
Понятное дело, что ты только учишься и главное понятьпринцип и сделать хоть что-то рабочее. Но чем точнее мы поддержим оригинальный синтаксис, тем больше сможем нарыть готовых тестовых примеров. Хотя можно просто переделывать их под себя. Т.е. НЕ обязательно поддерживать всё как надо. Мне уже для себя весь этот бардак стал интересен

Сейчас твоя цель - разобраться с промежуточным представлением: правильно его строить и интерпретировать. Когда это всё будет работать ннормально, чисто теоретически для интересе можно попробовать поддержать синтаксис близко к оригиналу. Но, повторюсь, это непринципиально. Задача-минимум - реализовать хоть хоть в каком-то виде. При этом желательно, чтобы поддерживались IF, FOR, WHILE (в любом синтаксическом проявлении) и крайне желательна поддержка массивов.

Свой вариант я набросал в первую очередь как пример на промежуточное представление. Пока с ходу не соображу, как _аккуратно_ туда ввинтить массивы. При этом появилось некоторое понимание, что держать строковые константы внутри Value - слишком геморойно получается
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
20.08.2009, 21:22  [ТС]     Пишем свой интерпретатор языка BASIC #102
Не мог бы ты прокомментировать,как именно работает данная интересная конструкция и по каким соображениям она сделана?
C++
1
2
3
4
5
6
7
8
9
10
11
typedef enum
 
{
 
#define LEX_DEF_TOKEN_KIND(k,s) k,
 
#include "lex.def"
 
#undef LEX_DEF_TOKEN_KIND
 
} lex_Token_t;
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
21.08.2009, 14:32     Пишем свой интерпретатор языка BASIC #103
Оформил в отдельную тему http://www.cyberforum.ru/cpp/thread47863.html
Если что непонятно - спрашивай там

Добавлено через 14 часов 20 минут 53 секунды
В общем думал, как работать с массивами, в итоге получилась примерно следующая концепция. У нас есть две независимые вещи: Value (числовое значение, которое может быть целым или плавающим) и String (строка, которую, как и во многих бэйсиках, ограничиваем размером в 255 байт). У переменной есть признак, она хранит Value или String. При этом в Value нельзя записывать String и наоборот.

Технически и в моей и в твоей реализации переменная содержит Value по значению. Думаю, что лучше делать по косвенности. Т.е. Value, хранимая в переменной, выделяется динамически (т.е. имеем поле Value_t *value). При этом выделяем фактически массив Value'ов. Если переменная скалярная (т.е. НЕ массив), то мы выделяем массив из 1 элемента, если же наша переменная массив - то мы выделяем массив из нужного количества элементов. Далее при разборе левой части присваивания мы фактически получаем указатель на нужное нам Value_t* (хоть для скалярной переменной, хоть для произвольного элемента массива).

Всё аналогично делается со String'ами, а переменная внутри себя через union рядышком хранит Valut_t* и String_t*. При этом признак Value или String хранится на всю переменную. Т.е. если мы имеем массив, то нельзя в одном элементе хранить строку, а вдругом число

Что касается синтаксиса, то видимо будет удобнее делать как у людей. Строковые переменные отмечать признаком $ на конце (т.е. этот символ входит в имя переменной, при этом в момент заведения новой переменной по этому символу мы понимаем, что будет храниться в переменной - строка или число)

Что касается регистра букв, то проще всего это локализовать в лексическом анализаторе. При чтении токена из файла для keyword'ов и ident'ов сразу переводить их в заглавные буквы. Тогда будет работать и "int" и "INT" и всё, что пожелаешь

По поводу типа переменной. У себя я предполагаю сделать примерно так: переменную можно использовать без дополнительного объявления. В этом случае тип будет "AUTO" (т.е. тип будет определяться записанным значением). Так же можно будет объявлять переменные через "DIM A AS INTEGER" (или как там правильно в синтаксисе). В этом случае тип переменной будет INT, FLOAT или STRING

Добавлено через 20 минут 32 секунды
Надо ещё при этом в промежуточном представлении как-то левую часть по другому описывать. Теперь там может быть не только переменная, но и элемент массива

Добавлено через 2 часа 12 минут 19 секунд
> Надо ещё при этом в промежуточном представлении как-то левую часть по другому описывать

В общем думать тут особо и нечего. Левая часть так же описывается в виде Expression. Только нужно при разборе понимать, что там допустимо только обращение к переменной или к элементу массива. А так же при обходе в момент интерпретации должно возвращаться не значение, а указатель на Value
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
23.08.2009, 15:08     Пишем свой интерпретатор языка BASIC #104
Тестовый пример, на котором моя версия ведёт себя некорректно. Я работал в таком ключе, что запись в переменную (т.е. появление переменной в левой части присваивания) автоматически означает её определение (т.е. этим мы задаём новую переменную). Я полагал, что в момент разбора синтаксиса если встретили несуществующую переменную в правой части присваивания - то это ошибка (по сути использование неинициализированной переменной)

PureBasic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
' Ситуация, когда по тексту переменная сначала появляется в правой части
' присваивания, а потом влевой (т.е. типа якобы сначала использование,
' а потом запись). Однако по управлению (в run-time) всё нормально:
' сначала идёт запись, и только потом чтение
LET C = 0
WHILE C < 2 DO
  IF C = 1 THEN
    LET B = SHAMAN ' сюда попадаем только на второй итерации цикла
  ELSE
    LET SHAMAN = 999 ' а сюда - на первой
  END_IF
  LET C = C + 1
END_WHILE
PRINT SHAMAN ' expected 999
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
24.08.2009, 16:17     Пишем свой интерпретатор языка BASIC
Еще ссылки по теме:
C++ Написать интерпретатор программного языка -помощь
C++ Интерпретатор музыки стандарта BASIC PLAY на С++
Задание: разработать "Интерпретатор языка". С чего начать? C++
C++ Перепишите пожалуйста код программы с языка Visual Basic в C++
По русскому названию языка программирования определить английское название этого языка C++

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

Или воспользуйтесь поиском по форуму:
Evg
Эксперт CАвтор FAQ
17537 / 5775 / 370
Регистрация: 30.03.2009
Сообщений: 15,904
Записей в блоге: 26
24.08.2009, 16:17     Пишем свой интерпретатор языка BASIC #105
> У нас есть две независимые вещи: Value (числовое значение, которое может быть
> целым или плавающим) и String (строка, которую, как и во многих бэйсиках,
> ограничиваем размером в 255 байт).

Что-то фигово получается, когда одно делишь от другого. Буду дальше экспериментировать с тем, как делать "хорошо"
Yandex
Объявления
24.08.2009, 16:17     Пишем свой интерпретатор языка BASIC
Закрытая тема Создать тему
Опции темы

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