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

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

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

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

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

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

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

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

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

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

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

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

C++ Написать интерпретатор программного языка -помощь
Написать Интерпретатор Программного Языка(собственного) C++
C++ пишем свой троян с нуля
C++ Интерпретатор небольшого языка программирования на С++
C++ Интерпретатор музыки стандарта BASIC PLAY на С++
Интерпретатор/компилятор ассемблер-подобного языка C++
Пишем свой чекер C++
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Evg
Эксперт CАвтор FAQ
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 26
14.08.2009, 20:43     Пишем свой интерпретатор языка BASIC #81
Цитата Сообщение от #pragma Посмотреть сообщение
Ну например
PureBasic
1
2
3
4
IF () {
     IF (){
          IF(){
#   Неизвестное количество раз
Но ведь тебе никто не мешает распарсить выражение a+a+a+...<неизвестное количество раз> и построить его в виде дерева. Так же и здесь если в лоб, что всё будет в виде дерева (без бумажки нарисовать трудно). Либо всё делать линейно (т.е. вообще отказаться от оператора IF в промежуточном представлении, заменив егона пару операций BRANCH и COND_BRANCH). В любом случае в процессе распарсивания у тебя будет рекурсивный вызов одного и того же синтаксического правила, а за счёт этого построится либо нужное количество уровней в дереве, либо нужное количество операций перехода

Цитата Сообщение от #pragma Посмотреть сообщение
Есть такое поверье-фирмы хайтек падают не при создании первой версии,а при переходе на вторую,иногда получается,что переделывать слишком много
Я потому тебя и веду по тому пути, чтобы пришлось переделывать. Ибо только занимаясь этим на практике можно реально понять КАК надо писать программу, чтобы она в будущем легко переделывалась, а так же сразу закладываться на какие-то переделки, которые сейчас не нужны, но теоретически могут понадобиться в будущем

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

Добавлено через 5 минут 26 секунд
Закинул сюда Вопрос про разные реализации бэйсиков

Добавлено через 1 час 16 минут 21 секунду
Пока не забыл. Функции тем не менее остаются, но только встроенные (SIN, COS и т.п.). С точки зрения парсера удобнее считать, что это keyword'ы - проще получается разбор. С точки зрения дальнейшей поддержки функций лучше считать, что это IDENT, а дальше уже разбираться, у нас идёт обращение к переменной или вызов функции

Добавлено через 4 часа 31 минуту 32 секунды
Кстати, звучит как парадокс, но вариант с промежуточным представлением проще, чем с двумя проходами. Хотя тебе полезно сделать оба варианта (сначала "плохой")
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
15.08.2009, 23:25  [ТС]     Пишем свой интерпретатор языка BASIC #82
Так,думаю,нужно подвести некоторые итоги.Я сейчас недаром застрял,я думал над реализацией промежуточного представления,но что-то никак не укладывается в голове,как вообще это организовать,то есть не вижу реализации за идеей.Вот смотрю на твой пример реализации и просто не понимаю,что с чем там будет связываться,если будет строиться дерево,то есть как после построения дерева его потом разгребать.Самое непонятное для меня это то,что переменные по ходу работы программы могу меняться,и как это связывается с деревом,вообщем бардак полный в голове Вариант с пропуском кода не лучше,то есть как-то не видится вариант реализации,делать флаги какие-то,что-ли,что-бы передавались в функции,а потом их менять после выхода из пропускаемого блока... это тоже не так просто.Это надо тогда подпортить все функции этими флагами. Понять бы получше механизм промежуточного представления,в каком точно виде всё это дерево будет и что будет его читать.

Насчёт системы тестирования-реально полезная вещь,которая позволила мне исправить кучу логических ошибок без особых затрат!Теперь даже можно сделать такой исходник
PureBasic
1
DIM int a,int b,int c;LET a = 6;LET b = 3;IF (b>2){WHILE (b<4){IF (b<4){PRINT a;}ELSE{PRINT "Hello";}FI;PRINT b," ";PRINT " ",a," ";}LOOP}ELSE{PRINT b;}FI
То есть главное,чтобы были разделители команд,или перевод строки.
Правда пришлось делать скидку на то,что блоки в IF и WHILE пропускаются без разбора,но даже и так много изменил всяких мелочей.Пока ещё вложенность условий не поменял везде(имею ввиду непроинвертированные),но кое-что подправил.Код прилагаю,посмотри,как выглядит syntax_parser.cpp,там я поменял больше всего с точки зрения отступов и т.д.Меня очень интересует мнение по оформлению,стал ли код менее читабельным.Я расположил строки немного плотнее.
Вложения
Тип файла: rar Interpreter.rar (12.9 Кб, 47 просмотров)
Evg
Эксперт CАвтор FAQ
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 26
16.08.2009, 00:27     Пишем свой интерпретатор языка BASIC #83
Я вроде уже писал. Посмотри тут посты 3 и 5. Фактически там у меня сначала строится промежуточное представление (когда выражение представлено в виде дерева), а потом обходится и обсчитывается (по сути происходит исполнение)

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

Добавлено через 11 минут 13 секунд
Исходники потом гляну
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.08.2009, 16:55  [ТС]     Пишем свой интерпретатор языка BASIC #84
Я посмотрел твои исходники,вроде всё понятно,но я не представляю,что изменить именно в моей реализации,как-то смутно всё.Как будто снова вернулся к отправной точке,когда только начинал перекраивать калькулятор.Просто не могу представить механизм работы после промежуточного представления. Может,я просто морально не готов писать всё заново Но если бы точно представлял,что и как сделать,то наверное переделал бы.
Я решил проблему с динамической памятью,оказалось,всё просто.С самого начала я не пользовался динамической памятью,но тогда прога вылетала,и я ошибочно подумал,что это необходимо,использовать new.Теперь я просто передаю сами объекты из функций.Всё использование динамической памяти было полностью убрано из программы.
Ещё я добавил оператор GOTO.
Evg
Эксперт CАвтор FAQ
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 26
17.08.2009, 17:52     Пишем свой интерпретатор языка BASIC #85
Цитата Сообщение от #pragma Посмотреть сообщение
Я посмотрел твои исходники,вроде всё понятно,но я не представляю,что изменить именно в моей реализации,как-то смутно всё.Как будто снова вернулся к отправной точке,когда только начинал перекраивать калькулятор.Просто не могу представить механизм работы после промежуточного представления. Может,я просто морально не готов писать всё заново Но если бы точно представлял,что и как сделать,то наверное переделал бы.
Морально быть готовым написать заново - тоже важно И в этом вопросе я тебя понимаю.

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

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

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

PureBasic
1
2
3
4
LET a = 5
LET b = 3
PRINT a
PRINT b
Считаем, что у анс есть простая реализация языка, в которой в переменную можно присвоить значение (пока без выражения), и можно распечатать переменную. Итого, у нас есть два оператора: "LET <var> = <value>" и "PRINT <var>". Теперь нам эту программу нужно сохранить в некотором внутреннем виде, чтобы дальше можно было работать без файла.

Промежуточное представление будет состоять из statement'ов. Все они провязаны в список по очереди через поле next (см. ниже). Структура для хранения представления statement'а в таком упрощённом случае следующая

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
enum StatementKind
{
  STMK_LET,
  STMK_PRINT
};
 
struct Statement
{
  // Через это поле мы провязываем всё в список
  struct Statement *next;
 
  // Тип statement'а
  StatementKind kind;
 
  union
  {
    // Данные для LET
    struct
    {
      Variable *var; // левая часть присваивания
      Value *val;  // правая часть присваивания
    } let;
 
    // Данные для PRINT
    struct
    {
      Variable *var; // в простейшем случае у нас только один аргумент в виде переменной
    } print;
  } data;
}
Я здесь для переменных испольхзую ссылку Variable. Ты у себя в коде использовал некое целое число, которое определяет положение в векторе. Можно хранить и число, но на мой взгляд удобнее хранить непосредственно ссылку на Variable. Ты для начала делай так, как тебе более понятно, а потом уже будем пределывать "как правильно"

При таком раскладе привожу код, который "вручную" создаёт промежуточное представление. Чтобы понятно было, что у нас в итоге получится. Понятно, что аналогичное представление у тебя будет строиться в процессе работы парсера. Разделяю построение каждого оператора отдельно (ибо реально они будут отдельно), перменная Last моделирует некую глобальную переменную

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
Statement *s1 = new (хз как там правильно), *s2, *s3, *s4 - создаются аналогично динамически
Statement *Last = NULL;
 
// Создание промежуточного представдения оператора "LET a=5"
s1->next = NULL;
s1->kind = STMK_LET;
s1->data.let.var = var; // подразумеваем, что в 'var' уже лежит указаель на созданную Variable, соотвествующую нашей 'a'
s1->data.let.val = val; // подразумеваем, что в 'val' уже создана Value, соотвествующая константе 5
Last = s1;
 
// Создание промежуточного представдения оператора "LET b=3"
s2->next = NULL;
s2->kind = STMK_LET;
s2->data.let.var = var; // подразумеваем, что в 'var' уже лежит указаель на созданную Variable, соотвествующую нашей 'b'
s2->data.let.val = val; // подразумеваем, что в 'val' уже создана Value, соотвествующая константе 3
Last->next = s2; // прицепляем к последнему оператору
Last = s2;
 
// Создание промежуточного представдения оператора "PRINT a"
s3->next = NULL;
s3->kind = STMK_PRINT;
s3->data.print.var = var; // подразумеваем, что в 'var' уже лежит указаель на созданную Variable, соотвествующую нашей 'a' (точно такой же указатель, как и в "LET a=5")
Last->next = s3; // прицепляем к последнему оператору
Last = s3;
 
// Создание промежуточного представдения оператора "PRINT b"
s4->next = NULL;
s4->kind = STMK_PRINT;
s4->data.print.var = var; // подразумеваем, что в 'var' уже лежит указаель на созданную Variable, соотвествующую нашей 'b' (точно такой же указатель, как и в "LET b=3")
Last->next = s4; // прицепляем к последнему оператору
Last = s4;
В итоге мы получили в памяти интерпретатора некий набор данных, который целиком отразил нашу исходную программу. С этого момента файл нам больше не нужен. После построения промежточного представления запускается второй проход, который его интерпретирует. В нашем простом случае мы просто проходимся по цепочке операций, в каждой операции смотрим поле kind, понимаем, что это за операция, смотрим в data для соотвествующего типа (kind) операции. Когда мы видим операцию присваивания, мы понимаем, что нам нужно исполнить что-то типа "s->data.let.var->SetValue(s->data.let.val)" (ну или как там оно у тебя реализовано, с ходу не помню). Когда мы видим операцию печати, то делаем "(s->data.print.var->GetValue()).PrintValue". Я всё это пишу условно, без детальной привязки к тому, как у тебя реализованы сейчас Variable и Value

Давай пока на этом остановлюсь. И жду уже более конкретных вопросов

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

Цитата Сообщение от #pragma Посмотреть сообщение
Ещё я добавил оператор GOTO.
Вперёд он тоже прыгает? И приведи пример исходника, который уже работает (попробуй включить GOTO вовнутрь IF'а)
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.08.2009, 18:35  [ТС]     Пишем свой интерпретатор языка BASIC #86
Пыгает вроде.. у меня сами метки создаются в глобальной таблице,так что неважно,куда прыгать..В сообщение твоё сейчас буду вникать.
А вот пример:
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
#
# Это пример небольшой программы
# на языке,подражающем
# языку BASIC.
#
#
 
DIM int a,int b,int c;
 
LET a = 6;
LET b = 3;
 
 
 
IF (b>2)
{   label:;
    INPUT "Введите значение с : ",c;
    WHILE (b<4)
    {
        IF (b<4)
        {
            PRINT a;
            GOTO label:;
        }
        ELSE
        {
            PRINT "Hello";
        }
        FI;
        PRINT "Значение b: ",b;
        PRINT "Значение a: ",a;
    }
    LOOP
}
ELSE
{
    LET b=b+1;
    PRINT b;
    GOTO label;
}
FI
Реализация такая:
В главной функции:
C++
1
2
3
                 case TOKEN_KW_GOTO:
                     syntax_parserStmtKwGOTO ();
                 break;
сама таблица:
C++
1
map <const std::string,s_count> parser_GotoCases;
Поместил её в парсер вместе с другими..s_count это порядковый номер символа в коде,где находится метка(typedef long s_count)
А это в syntax_parser.cpp:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* ************************************************************************** */
   static void syntax_parserStmtKwGOTO ()
   {
       StmtKwGOTO1:;
       switch (parser_GetToken())
       {
           case TOKEN_EOL:   goto StmtKwGOTO1;
           case TOKEN_IDENT: {
               map <const string,s_count>::iterator i =
                                    parser_GotoCases.find(::parser_CurTokenStr);
               if (i == parser_GotoCases.end())
                   error(UNKNOWN_CASE_LABEL,::parser_CurTokenStr);
               // Goto symbol that label pointing to
               source.seekg(parser_GotoCases[::parser_CurTokenStr]);
               ::parser_CurSymbol = source.tellg();
           break;
           }
           default: error(IDENTIFIER_EXPECTED);
       }
       // Now token is label name
       // in place of label creation.
       // Need to skip it and colon
       // will be skipped in Main cycle
       parser_GetToken();
   }
/* ************************************************************************** */
   static void syntax_parserLabelCreate ()
   {
       map <const string,s_count>::iterator i =
                                    parser_GotoCases.find(::parser_CurTokenStr);
       if (i != parser_GotoCases.end())
           error(DUPLICATE_CASE_LABEL,::parser_CurTokenStr);
       parser_GotoCases[::parser_CurTokenStr] = ::parser_CurSymbol;
 
       LabelCreate1:;
       switch(parser_GetToken())
       {
           case TOKEN_EOL:   goto LabelCreate1;
           case TOKEN_COLON: parser_GetToken();
                             break;
           default: error(COLON_EXPECTED);
       }
   }
/* ************************************************************************** */
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.08.2009, 18:51  [ТС]     Пишем свой интерпретатор языка BASIC #87
А да,та версия,что я выкладывал,ещё без GOTO,так что вот новая
Вложения
Тип файла: rar Interpreter.rar (13.2 Кб, 47 просмотров)
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.08.2009, 19:41  [ТС]     Пишем свой интерпретатор языка BASIC #88
Да,теперь мне понятна идея реализации,по крайней мере,направление.Незнаю почему,но когда кто-то целенаправленно объясняет,мозг воспринимает лучше,хотя в исходниках было то же самое.))
Что мне не понятно: как будет организован while ,а также goto при такой реализации?Мы же уже не будем работать с файлом,тогда как узнать,на какую строку/символ прыгать? А так вроде доходит потихоньку,что у нас будут данные,но присваивания и вообще опрерации над ними ещё не произведены,и будут сделаны после.
Как всегда,перекраивать надо с ног до головы

Добавлено через 19 минут 44 секунды
P.S.GOTO прыгает только вперёд.Потому что сначала ему надо узнать метку и создать её,а уже потом прыгать .Я понял о чём ты,скорее всего это разрешается промежуточным представлением.

Добавлено через 19 минут 45 секунды
Я кажется понимаю,goto просто будет хранить порядковый номер следующей инструкции.
Evg
Эксперт CАвтор FAQ
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 26
17.08.2009, 19:52     Пишем свой интерпретатор языка BASIC #89
Цитата Сообщение от #pragma Посмотреть сообщение
Пыгает вроде.. у меня сами метки создаются в глобальной таблице,так что неважно,куда прыгать..
Я почему спросил. Если идёт GOTO вперёд, то в момент, когда ты его парсишь, метка ещё не создана, а потому куда переходить пока непонятно. Это тоже непростой момент

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

Цитата Сообщение от #pragma Посмотреть сообщение
Что мне не понятно: как будет организован while ,а также goto при такой реализации?
Если идея более-меняя ясна, сейчас попробую про операции передачи управления пояснить (чуть позже)

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

Что касается компилятора - теоретически ничего не мешает нам его в будущем написать. Пусть код будет медленным и гавёным, но это будет рабочий код. В крайнем случае можно написать конвертор Бэйсик->Си. Но это так, к сведению. Пока мы пишем интерпретатор

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

Добавлено через 6 минут 35 секунд
Вот так выглядят метка и операция GOTO. Всё это так же является частью поля Statement.data

C
1
2
3
4
5
6
7
8
9
10
11
struct
{
  // Тут по большому счёту для интерпретации ничего не надо, т.к. метка по своей
  // сути это пустой оператор, выполняющий точку привязки. Тут всякая отладочная
  // информация и больше ничего
} label;
struct
{
  // Указатель на statement, на который мы переходим
  Satament *label;
} goto;
Тут вроде бы с одной стороны просто, с другой стороны может показаться чемто нетривиальным и непонятным. Этот момент дальше нужно пояснять?
#pragma
Временно недоступен
952 / 223 / 6
Регистрация: 12.04.2009
Сообщений: 921
17.08.2009, 20:05  [ТС]     Пишем свой интерпретатор языка BASIC #90
Да,всё вроде понятно теперь. Пока что у меня GOTO действительно только назад прыгает.
Evg
Эксперт CАвтор FAQ
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 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
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 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
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 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
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 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
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 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 секунд
В печати промежуточного представления надо смотреть именнопорядок, в котором всё печатается. То, что номера печатаются не по порядку - это фича, связанная с механизмом создания меток для переходов вперёд
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
20.08.2009, 16:58     Пишем свой интерпретатор языка BASIC
Еще ссылки по теме:

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

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

Или воспользуйтесь поиском по форуму:
Evg
Эксперт CАвтор FAQ
17305 / 5553 / 347
Регистрация: 30.03.2009
Сообщений: 15,112
Записей в блоге: 26
20.08.2009, 16:58     Пишем свой интерпретатор языка BASIC #100
Фалй забыл приаттачить
Вложения
Тип файла: rar interp.rar (17.0 Кб, 51 просмотров)
Тип файла: rar interp_w.rar (17.4 Кб, 43 просмотров)
Yandex
Объявления
20.08.2009, 16:58     Пишем свой интерпретатор языка BASIC
Закрытая тема Создать тему
Опции темы

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