Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
1

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

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

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

Репозиторий с проектом находится тут, там же есть возможность в браузере посмотреть историю ревизий (английский в логах весьма примитивен,комментарии и рекомендации можете писать в личку),а также скачать самый последний архив репозитория в формате .tar.gz
Если кто-то пользуется Subversion,скачать исходники можно так:
Код
svn co https://basin.svn.sourceforge.net/svnroot/basin basin
Эти темы возникли в результате моих вопросов по ходу написания:
Технический приём для формирования согласованных данных
https://www.cyberforum.ru/c-linux/thread46096.html
Вопрос по svn (Subversion)
Создание системы тестирования ПО.
Вопрос про разные реализации бэйсиков
Можно ли выразить порядковый номер элемента массива через индексы?
[C++] Какие флаги указать линкеру для компиляции программы?
Как можно определить переменную в файле configure.in,чтобы её можно было использовать в Makefile?
Странный SIGSEGV, или что зависит от порядка написания интерфейса класса
https://www.cyberforum.ru/c-linux/thread61324.html
Альтернативная версия интерпретатора от 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;
31
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
20.06.2009, 20:03
Ответы с готовыми решениями:

Пишем свой интерпретатор языка BASIC
Добрый день. Я смотрю, тут на форуме была тема коллективного написания интерпретатора BASIC на...

Пишем свой strlen
Всем привет, вырвал часть задание из общего задание по написанию своего string. На данном этапе...

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

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

464
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
20.08.2009, 20:43 101
Author24 — интернет-сервис помощи студентам
Цитата Сообщение от #pragma Посмотреть сообщение
Я имел ввиду что структуры данных Expression,Statement надо же где-то пристроить,так пусть лучше будут в отдельном файле.И потом уже при исполнении уже другая единица (по-моему этим уже не должен заниматься парсер синтаксиса)эти данные тоже будет подключать.
Ну да, так оно и есть. Просто исходники условно можно разделить на две категории. Первые - это исхожники, описывающие некие хранимые данные. Это value, variable, сюда же добавятся expression и statement. Вторая категория - это исзодники, которые функциональные, т.е. осуществляют некоторую работу над этими данными - лексический анализатор, разбор синтаксиса, интерпретация промежуточного представления

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

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

Свой вариант я набросал в первую очередь как пример на промежуточное представление. Пока с ходу не соображу, как _аккуратно_ туда ввинтить массивы. При этом появилось некоторое понимание, что держать строковые константы внутри Value - слишком геморойно получается
1
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
20.08.2009, 21:22  [ТС] 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;
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
21.08.2009, 14:32 103
Оформил в отдельную тему https://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
1
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
23.08.2009, 15:08 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
1
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
24.08.2009, 16:17 105
> У нас есть две независимые вещи: Value (числовое значение, которое может быть
> целым или плавающим) и String (строка, которую, как и во многих бэйсиках,
> ограничиваем размером в 255 байт).

Что-то фигово получается, когда одно делишь от другого. Буду дальше экспериментировать с тем, как делать "хорошо"
1
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
25.08.2009, 15:29 106
Итого, примерно надумал следующее. Ввожу понятие типа, который может быть целочисленный (NUMERIC), строковой (STRING) и логический (LOGICAL). Переменные могут быть только первых двух типов, промежуточное значение выражения - всех трёх. Тип изначально является свойством значения (Value), но при этом засовывается в Variable и Expression, поскольку они так же оперируют значениями. Целочисленный тип разбиваю далее на два подтипа: целый и плавающий. Любые двухаргументные операции разрешены только над одинаковыми типами (в этом отношении int и float имеют одинаковый тип NUMERIC). Присваивание разрешено только для одинаковых типов

На текущий момент есть вроде бы всё основное: переменные, арифметика, операции управления, строки, массивы (в том числе и многомерные). Для полного сачтья надо поддержать встроенные функции (обычно их называют термином "builtin functions" или сокращённо просто "builtin"). Ну и с технической стороны надо поддержать пользовательские метки, FOR'ы, нормально поддержать двухсимвольные операции сравнения (">=", "<=", "<>"), но это всё уже мелкая работа, т.к. структуру уже затрагивать не должно - просто будет допиливаться код.

Что мне сейчас не нравится принципиально - это реализация строк. Она сделана в виде массива, а потому структура Value получается большой. Нормально строку надо хранить не по значению, а по косвенности, но в Си это означает мегагеморрой с динамической памятью, слежение за удалением и копированием и т.п. А вот на Си++ это делается нормально за счёт деструкторов и реализации конструктора копирования (либо ваще тупо использовать стандартный String)

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

Выкладываю опять в двух вариантах: basic.rar в кодировке KOI-8 и basic_w.rar в кодировке WIN-1251
Вложения
Тип файла: rar basic.rar (27.0 Кб, 31 просмотров)
Тип файла: rar basic_w.rar (27.4 Кб, 36 просмотров)
1
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
25.08.2009, 16:12 107
Для такого кода

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
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
' Простая арифметика
LET A1 = 10
LET A2 = 100
LET B1 = A1 + 5 * A2 - 50
PRINT B1 ' expected 460
 
' Неявные преобразования типов
LET A1 = 1 + 2.7
PRINT A1 ' expected 3
LET A1 = 2.7 + 1
PRINT A1 ' expected 3.7
 
' Ситуация, когда по тексту переменная сначала появляется в правой части
' присваивания, а потом влевой (т.е. типа якобы сначала использование,
' а потом запись). Однако по управлению (в run-time) всё нормально:
' сначала идёт запись, и только потом чтение
' Заодно и проверка работы IF
LET C = 0
WHILE C < 2 DO
  IF C = 1 THEN
    LET B = SHAMAN + 1 ' сюда попадаем только на второй итерации цикла
  ELSE
    LET SHAMAN = 999 ' а сюда - на первой
  END_IF
  LET C = C + 1
END_WHILE
PRINT SHAMAN ' expected 999
PRINT B      ' expected 1000
 
' Считаем сумму чисел от 1 до 100
LET COUNT=0
LET SUM=0
WHILE COUNT < 100 DO
  LET COUNT=COUNT+1
  LET SUM=SUM+COUNT
END_WHILE
PRINT COUNT ' expected 100
PRINT SUM   ' expected 5050
 
' Работа с одномерным массивом
DIM A(4)
LET C=0
WHILE C < 4 DO
  LET A(C+1) = (C+1) * (C+1)
  LET C = C + 1
END_WHILE
PRINT A(1) ' expected 1
PRINT A(2) ' expected 4
PRINT A(3) ' expected 9
PRINT A(4) ' expected 16
 
' Работа с многомерным массивом
DIM AA(2,3,4)
LET COUNT = 100
LET I1 = 0
WHILE I1 < 2 DO
  LET I2 = 0
  WHILE I2 < 3 DO
    LET I3 = 0
    WHILE I3 < 4 DO
      LET COUNT = COUNT + 1
      LET AA(I1+1,I2+1,I3+1) = COUNT
      LET I3 = I3 + 1
    END_WHILE
    LET I2 = I2 + 1
  END_WHILE
  LET I1 = I1 + 1
END_WHILE
PRINT COUNT     ' expected 124
PRINT AA(2,1,2) ' expected 114
 
' Операция сложения над строками
DIM R$(3)
LET R$(1) = "ERT"
LET R$(2) = "zxcvbnm"
LET R$(3) = "1234567"
LET A$ = ""
LET C = 0
WHILE C < 3 DO
  LET A$ = A$ + R$(C+1)
  LET C = C + 1
END_WHILE
PRINT A$
выдаём вот такой результат

Код
-> 460
-> 3
-> 3.700000
-> 999
-> 1000
-> 100
-> 5050
-> 1
-> 4
-> 9
-> 16
-> 124
-> 114
-> ERTzxcvbnm1234567
Добавлено через 8 минут
Вот ещё что забыл сказать. При работе в качестве интерпретирующей части можно повесить модуль, который вместо интерпретации будет генерить код на Си. Например, для исходника

PureBasic
1
2
LET A=B+1
PRINT A
На выходе будем получать код (условно говоря, реально есть свои ньюансы)

C
1
2
3
4
5
6
7
8
9
10
11
12
13
/* Это всё файлы, являющиеся исходниками интерпретатора */
#include "value.c"
#include "variable.c"
#include "interp.c"
 
var_Variable_t A;
var_Variable_t B;
 
main()
{
  A.vals[0] = val_ValueBinOp (VAL_OP_ADD, B.vals[0], val_CreateNumericInt (1));
  val_UserPrintValue (A.vals[0]);
}
Т.е. получается очень неоптимально написанный код, построенный на тех же структурах, на которых работает интерпретатор (чтобы два раза не писать). Но тем не менее код будет рабочим, дальше его моджо будет скомпилить и получить настоящий бинарник, который будет работать.

По такой технологии сейчас поддерживаются некоторые устаревшие языки программирования. Особенно у военных, которые ни за что не пойдут на то, чтобы переписать какие-то военные программы, написанные много лет назад, потому как писали их гении того времени, а потому в коде разобраться невозможно. И гораздо более надёжным вариантом оказывается написать интерпретатор или конвертер в Си
1
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
25.08.2009, 19:01  [ТС] 108
Вот про конвертер в Си немного не понял идею,как именно это будет работать.Мне теперь остаётся только наблюдать за твоим ходом написания,и потихоньку ковырять свой вариант.
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
25.08.2009, 22:07 109
Цитата Сообщение от #pragma Посмотреть сообщение
Вот про конвертер в Си немного не понял идею,как именно это будет работать.
Ну... для начала надо понять, как это на уровне промежуточного представлнеия выглядит. Далее понимаешь, как это представление интерпретируется, а дпльше попросту сгенерить код, который по большому счёту работает в терминах Value и Variable. Суть в том, что из исходника на одном языке получается исходник на другом. В идеальном варианте исходник на Си для того примера будет вот таким:

C
1
2
3
4
5
6
#include <stdio.h>
int A, B;
int main (void)
{
  A = B + 1;
}
Но для генерации "хорошего" исходника в сложных случаях придётся долго ковыряться. А "плохой" исходник генерится чуть ли не с полпинка. Но ты пока этим не заморачивайся. Просто имей в виду, что такое возможно. Со временем сам начнёшь понимать. Чем-то похожее состояние у тебя было в самом начале, когда тебе совсем непонятно было, как вообще можно сделать хоть что-то работающее, содержашие LET'ы, PRINT'ы, IF'ы. Но в какой-то момент ты это просёк и с ходу сделал сразу кучу всего. ВОт и здесь будет тоже самое: поначалу всё дико непонятно и сложно, а потом вдруг поймёшь и дальше всё попрёт. Просто здесь начальный период понимания будет побольше, ибо сама постановка задачи стала сложнее.

Это при том, что я себе совершенно чётко представлял, что должно быть, у меня ушло на это неделя времени. Так что твои два месяца - это вполне нормально. Как говорится "тяжело в учении..."

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

Так что если есть вопросы - задавай, до вечера пятницы я ещё тут.

Добавлено через 1 час 18 минут
По поводу генерации кода на Си (Си++). Относительно совтояния твоих исходников из поста #86

Допустим, имеем исходник на бэйсике:

PureBasic
1
2
3
4
5
6
7
8
9
DIM INT A
DIM INT B
DIM INT C
LET A=10
LET B=11
LET C=A*A + B*B
PRINT "A=",A
PRINT "B=",B
PRINT "C=",C
А теперь в каталоге с твоими исходниками создадис вот такой вот файлик Си++

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
// Эта шапка необходима, чтобы исходник нормально компилился и слинковался
#include <iostream>
#include <fstream>
using std::ifstream;
using std::cout;
using std::endl;
using std::cerr;
ifstream source;
#include "parser.cpp"
#include "error_handler.cpp"
 
// А с этого места "полезный" код
#include "value_class.cpp"
#include "variable_class.cpp"
 
int main (void)
{
  // DIM INT A
  Variable A ("A", INT);
 
  // DIM INT B
  Variable B ("B", INT);
 
  // DIM INT C
  Variable C ("C", INT);
 
  // LET A=10
  A = Value("10",INT);
 
  // LET B=11
  B = Value("11",INT);
 
  // LET C=A*A + B*B
  C = (A.GetValue() * A.GetValue()) + (B.GetValue() * B.GetValue());
 
  // PRINT "A=",A
  cout << "A=" << A.GetValue() << endl;
 
  // PRINT "B=",B
  cout << "B=" << B.GetValue() << endl;
 
  // PRINT "C=",C
  cout << "C=" << C.GetValue() << endl;
 
  return 0;
}
Этот исходник отлично компиляется и получается бинарник. Который при исполнении печатает то же самое, что и интерпретатор

Код
$ g++ a.cc
$ ./a.out 
A=10
B=11
C=221
При этом, работая через промежуточное представлени (да и можно для такого простого исходника напрямую), твой интерпретатор сам может сгенерить этот самый код на Си++. В итоге получаешь некую систему, когда на входе имеешь текст на бэйсике, а на выходе имеешь бинарный файл. Код по скороксти получается неэффективный, но зато техническая реализация в таком виде в разы проще, чем писать собственный компилятор с бэйсика

Надеюсь, так понятнее. Если непонятно - забей. Если понятно - имей в виду. Такую хрень можно будет потом привинтить к интерпретатору. Можно конечно и сразу такое ваять, но сначала написать интерпретатор будет проще.
1
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
25.08.2009, 22:47  [ТС] 110
Да,это круто! Как я понял просто работаем с выходным файлом и записываем туда строки в зависимости от интерпретации файла на псевдо-бейсике.Затем можно дать команду типа system("g++ a.cc") (если в линукс,ну в другой среде можно команду поменять) и он скомпилируется на месте.
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
25.08.2009, 23:33 111
Ну как и что потом запустить - уже не важно. В общем суть главное понял. Это большой бонус удобства работы с промежуточным представлением. Хочешь - интерпретируй, хочешь - генери с него текст на Си\Си++, хочешь - можешь напрямую делать компиляцию (фактически формирование ассемблерного файла). Представление в процессе построения заведомо очищается от пользовательских ошибок и, в отличие от файла, по нему можно ходить взад и вперёд, в том числе и заниматься оптимизациями (типа "A*4" заменять на "A<<2" или "A+5+6" заменять на "A+11"). Так что после того, как ты поймёшь, что же это за зверь такой, у тебя опять появится оптимизм
1
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
29.08.2009, 08:35  [ТС] 112
Не знаю,зачем,но сделал проект на sourceforge.net - https://sourceforge.net/projects/basin/ (basin - Basic Interpreter),теперь у кого установлена Subversion,смогут просто скачать исходники командой
Код
svn co https://basin.svn.sourceforge.net/svnroot/basin basin
Там пока только та часть ветки,не затронутая изменениями с промежуточным представлением,то есть до того момента,как я начал добавлять файлы expression,statement и т.д.
Всё это дело вывешено под лицензией "BSD license",надеюсь возражений не будет? Просто хотел добавить опцию для скачивания,да и чтобы не лазить по теме в поисках исходников.Заодно снять лишний трафик с форума.Предлагаю держать исходники там.Можно попросить модераторов просто отредактировать первый пост,чтобы там были все ссылки относящиеся к данной теме.
Если,например,нужно просто посмотреть историю изменений,то сделать это можно отсюда http://basin.svn.sourceforge.net/viewvc/basin/
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
16.09.2009, 09:22 113
Цитата Сообщение от #pragma Посмотреть сообщение
Не знаю,зачем,но сделал проект на sourceforge.net
Оп-па. А это сообщение у меня почему-то не было отмечено, как непрочитанное

Добавлено через 16 минут
Уж коли пошло на то, что начал делать как серьёзную программу, то тебе надо бы удалить признак "exec" из исходников (т.е. часть файлов имеет права на исполнение). Видимо, ты пользуешься каким-то аналогом винlузового проводника, где это не видно

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

Пока особо не смотрел, но в бэйсиках вроде бы сделано так, что переменная без суффикса по умолчанию имеет плавающий тип, а у тебя целый. Но это мелочь

Добавлено через 6 минут
В value у тебя присуствуют поля типа int и long. Но для 32-битных машин как правило их размеры совпадают. Более аккуратно было бы в файле с настройками host'ового компилятора завести typedef'ы с именами тиа int32_t, int64_t и везде использовать их. И в начале исполнения программы поставить run-time контроль, что "assert (sizieof(int32_t) == 4); assert (sizieof(int64_t) == 8);". Аналогично для плавающих. Вроде бы как это мелочь, но такие вещи лучше делать сразу, чтобы потом в миллион местах не исправлять

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

Добавлено через 15 минут
И если у тебя есть какой-то набор тестовых ситуаций - для порядка его тоже можно под cvs положить (создать отдельный каталог и свалить туда). ТОже полезно, а то когда работаешь с одним файлом, добавляешь туда и удаляешь, то интересные тесты пропадают
1
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
16.09.2009, 12:58  [ТС] 114
Цитата Сообщение от Evg Посмотреть сообщение
Уж коли пошло на то, что начал делать как серьёзную программу, то тебе надо бы удалить признак "exec" из исходников (т.е. часть файлов имеет права на исполнение). Видимо, ты пользуешься каким-то аналогом винlузового проводника, где это не видно
Хм у меня права по умолчанию стояли и были drwxr-xr-x,ну я поменял их на drwxr--r-- а последний x не даёт убрать - тогда файлы в папке не видно,не знаю,с чем это связано :\
Цитата Сообщение от Evg Посмотреть сообщение
Ещё где-то для порядку надо описать, какие конструкции понимает твой интерпретатор. Чтобы хоть можно было поэкспериментировать и самому что-то написать. Я в репозитории храню исходник, в котором присутсвуют все образцы поддерживаемых конструкций
Да вроде кинул туда исходник,буду его обновлять
Цитата Сообщение от Evg Посмотреть сообщение
Пока особо не смотрел, но в бэйсиках вроде бы сделано так, что переменная без суффикса по умолчанию имеет плавающий тип, а у тебя целый. Но это мелочь
В той теме,что ты сделал про бейсики я прочитал что как раз int,но сменить не проблема,если что
Цитата Сообщение от Evg Посмотреть сообщение
Добавлено через 6 минут
В value у тебя присуствуют поля типа int и long. Но для 32-битных машин как правило их размеры совпадают. Более аккуратно было бы в файле с настройками host'ового компилятора завести typedef'ы с именами тиа int32_t, int64_t и везде использовать их. И в начале исполнения программы поставить run-time контроль, что "assert (sizieof(int32_t) == 4); assert (sizieof(int64_t) == 8);". Аналогично для плавающих. Вроде бы как это мелочь, но такие вещи лучше делать сразу, чтобы потом в миллион местах не исправлять
Я написал себе заметку сделать систему типов,но то что ты говоришь-я что-то слабо представляю,о чём это и где точно прописывается..У меня в-общем при печатании слова int в среде выпадает менюшка со всякими int32_t и прочими,но я не в курсе,что это.
Цитата Сообщение от Evg Посмотреть сообщение
Добавлено через 1 минуту
И сделай при интерпретации print'а чтобы энтеры рисовались, а то лепит всё в одну кучу - неудобно. Или как-то можно самому энтер напечатать?
Я прочитал про print-ы,там по-моему они не могут печатать EOL,но можно напечатать пустой print,и будет перевод строки.А если нужно пробел между значениями,то у меня можно напечатать строку с пробелом в качестве выражения .Формат записи: PRINT <выражение> [,] или [;] [<выражение>] а пустой print переводит строку.
Цитата Сообщение от Evg Посмотреть сообщение
Добавлено через 15 минут
И если у тебя есть какой-то набор тестовых ситуаций - для порядка его тоже можно под cvs положить (создать отдельный каталог и свалить туда). ТОже полезно, а то когда работаешь с одним файлом, добавляешь туда и удаляешь, то интересные тесты пропадают
Сделаю.

Добавлено через 26 минут
Напиши,какая маска прав в восьмеричном формате должна быть у папок и файлов,я поставлю...

Добавлено через 27 минут
Как мне это сделать вообще с правами,сначала все файлы удалить из репов,а потом опять закачать с новыми правами?В репозиторий руками же не залезешь.
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
16.09.2009, 13:44 115
Цитата Сообщение от #pragma Посмотреть сообщение
Я написал себе заметку сделать систему типов,но то что ты говоришь-я что-то слабо представляю,о чём это и где точно прописывается..У меня в-общем при печатании слова int в среде выпадает менюшка со всякими int32_t и прочими,но я не в курсе,что это.
Ну, грубо говоря проблема такая, что в языке Си не определён размер базовых типов. На каждой архитектуре он определяется по своему. Но в общей своей массе эти настройки совпадают. И тем не менее, где нужен размер типа какого-то фиксированного размера, заводят typedef. Например "typedef int int32_t; typedef long long int64_t" и везде в программе используют int32_t и int64_t. Если ты отнесёшь свою программу на архитектуру с другиминастройками базового типа, то тебе нужно просто в одном месте изменить typedef, всё остальное срастётся автоматически. Обычно все эти настроки делаются через систему конфигурации, но тебе пока можно этим не заморачиваться, просто принять к сведению и работать через ручкаминаписанный typedef

Цитата Сообщение от #pragma Посмотреть сообщение
Я прочитал про print-ы,там по-моему они не могут печатать EOL,но можно напечатать пустой print,и будет перевод строки.А если нужно пробел между значениями,то у меня можно напечатать строку с пробелом в качестве выражения .Формат записи: PRINT <выражение> [,] или [;] [<выражение>] а пустой print переводит строку.
Я уже плохо помню, но что-то в глубине памяти маячит, что "PRINT A" печатает A с переводом строки, а "PRINT A;" - без перевода. Но опять-таки не суть как делать, я у себя всегда принудительно влепил перевод строки, чтобы в процессе отладки глаза не ломать

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

Цитата Сообщение от #pragma Посмотреть сообщение
Как мне это сделать вообще с правами,сначала все файлы удалить из репов,а потом опять закачать с новыми правами?В репозиторий руками же не залезешь.
Интересный вопрос. Хз как это делать. Видимо надо читать мануал, с ходу и не скажу
0
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
16.09.2009, 14:06  [ТС] 116
Цитата Сообщение от Evg Посмотреть сообщение
Ну, грубо говоря проблема такая, что в языке Си не определён размер базовых типов. На каждой архитектуре он определяется по своему. Но в общей своей массе эти настройки совпадают. И тем не менее, где нужен размер типа какого-то фиксированного размера, заводят typedef. Например "typedef int int32_t; typedef long long int64_t" и везде в программе используют int32_t и int64_t. Если ты отнесёшь свою программу на архитектуру с другиминастройками базового типа, то тебе нужно просто в одном месте изменить typedef, всё остальное срастётся автоматически. Обычно все эти настроки делаются через систему конфигурации, но тебе пока можно этим не заморачиваться, просто принять к сведению и работать через ручкаминаписанный typedef
Я просто не пойму,чем мешает такой вариант
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   // Type will be used for loops
   typedef int  count;
   // Type for counting symbols in program
   typedef long s_count;
 
   typedef char size1;// TODO Types system(basic,changeable etc.)
 
   typedef int  size2;
 
   typedef long size3;
 
   typedef unsigned long size4;
 
   typedef size2  stmtId;
 
   typedef vector <string>       str_Arr_t;
 
   typedef vector <size4>        sz4_Arr_t;
 
   typedef vector <sz4_Arr_t *>  sz4_MDim_t;
И почему важны номера 32 и 64 я ведь нигде на размеры не полагаюсь в программе?
P.S. А вообще у меня уже навроде паранойи насчёт типов везде где можно было поставил size3 для счётчиков привязанных к размерам векторов,потому что поглядел,какой у них max_size() на моей машине.
Насчёт репов-боюсь попортить репозиторий,поэтому можно попробовать сделать это постепенно,по одному,просто удаляя старые и добавляя новые переименованные файлы.Потом можно попробовать переименовать их обратно.
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
16.09.2009, 14:28 117
Просто я смотрел revision 16 (ибо 20 у меня не компилится), а там в классе Value используются int и long

32 и 64 необязательно (просто удобно), можно назвать venik и prjanik, лишь бы не использовать напрямую базовые типы (чтобы править нужно было в одном месте)
1
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
16.09.2009, 14:32  [ТС] 118
На что ругается компилятор?У меня всё ок,работает.Видимо,тут уже сказывается разность версий компиляторов?А-а,может надо makefile подправить,ты наверное только через командную строку работаешь?так я makefile только иногда обновляю,проверь
0
Evg
Эксперт CАвтор FAQ
21279 / 8301 / 637
Регистрация: 30.03.2009
Сообщений: 22,659
Записей в блоге: 30
16.09.2009, 14:39 119
Цитата Сообщение от #pragma Посмотреть сообщение
На что ругается компилятор?У меня всё ок,работает.Видимо,тут уже сказывается разность версий компиляторов?
Не помню. На линковке вроде бы падало.

Вот такой код на исполнении должен зацикливаться, но он не зацикливается

Код
   LET B1 = 5
2:
   IF B1 THEN GOTO 2
   PRINT B1
а если меняем местами первые две строки

Код
2:
   LET B1 = 5
   IF B1 THEN GOTO 2
   PRINT B1
то зацикливается. Однако если внесём PRINT вовнутрь

Код
2:
   LET B1 = 5
   PRINT B1
   IF B1 THEN GOTO 2
То на печать ничего не выводится

Я глубоко не копался, просто мысль для такого теста появилась после того, как я увидел реализацию того, как ты IF делаешь. Я ожидал увидеть переполнение стека в процессе интерпретации (из-за возникающей рекурсии в твоей схеме), но тут похоже какой-то другой косяк
1
Временно недоступен
957 / 228 / 14
Регистрация: 12.04.2009
Сообщений: 926
16.09.2009, 16:00  [ТС] 120
Да,косяк вроде в том,что при создании IF сначала проверяются (и создаются,конечно) его под-программы,а уже потом только сама инструкция IF,а это меняет глобальный порядок инструкций в программе.Выход-сначала создавать инструкции ,у которых есть свои подпрограммы "всухую",а уже потом создавать лист и проверять его на синтаксис,причем не меняя порядка следования,то есть сначала создаются все инструкции для THEN,а потом для ELSE,когда IF уже создан(ну в принципе это логично).А затем уже эти подпрограммы записывать в IF.Я ещё в начале заметил,что у меня в логах творится беспорядок: инструкции печатаются не по порядку,просто сначала не придал такого значения,что это баг.
Такой вариант должен исправить проблему,я полагаю.
Цитата Сообщение от Evg Посмотреть сообщение
Я глубоко не копался, просто мысль для такого теста появилась после того, как я увидел реализацию того, как ты IF делаешь. Я ожидал увидеть переполнение стека в процессе интерпретации (из-за возникающей рекурсии в твоей схеме), но тут похоже какой-то другой косяк
Ты видишь что неправильно реализован IF? Если я исправлю проблему, как описал выше,ты всё равно считаешь,что реализация неверная? Т.е. перегрузка стека неизбежна? Я считал,что будет верным делать подпрограммы для IF и WHILE,специально сделал функции интерпретации для закрытых блоков,надеясь что в будущем это поможет при составлении функций и т.п.

P.S.Я кажется понял,почему ты так подумал.Нет-нет,у меня GOTO работает только в runtime,то есть создание IF будет только 1 раз.
0
16.09.2009, 16:00
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
16.09.2009, 16:00
Помогаю со студенческими работами здесь

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

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

Не удается откомпилировать интерпретатор М-языка
Задача: взять интерпретатор М-языка на сайте...

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

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

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


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

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

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru