Личный проект: интерпретатор скриптового языка IScript, реализованный на C++23.
IScript — легковесный интерпретируемый язык программирования с динамической типизацией, вдохновлённый идеями языков высокого уровня (Python, Lua). Этот проект представляет собой полноценный интерпретатор IScript: он считывает файл с расширением .is, разбирает исходный код, строит абстрактное синтаксическое дерево (AST) и выполняет программы «на лету».
Главная цель проекта — продемонстрировать навыки разработки компилятора/интерпретатора: реализовать лексер, парсер, AST, систему значений и окружений, встроенную стандартную библиотеку функций и механизм обработки ошибок.
-
Типы данных
- Числа:
double(числа с плавающей точкой двойной точности), включая поддержку экспоненциальной нотации (например,1.23e-4). - Булевы значения: литералы
trueиfalse. - Строки: литералы в двойных кавычках, поддержка escape-последовательностей (
\n,\t,\",\\и т. д.). - Списки: динамические массивы, литералы в квадратных скобках (
[1, 2, 3]), с нулевой индексацией, поддержка срезов (list[1:4],str[:3]). - Функции как объекты первого класса: можно присваивать переменным, передавать в качестве аргументов, создавать «анонимные» функции.
- Числа:
-
Операторы
- Арифметические:
+,-,*,/,%(остаток),^(возведение в степень); унарные+и-. - Сравнения:
==,!=,<,>,<=,>=. - Логические:
and,or,not. - Присваивания: простое (
=) и составные (+=,-=,*=,/=,%=,^=). - Постфиксные и префиксные инкремент/декремент:
x++,--y. - Индексация и срезы:
expr[index],expr[start:end].
- Арифметические:
-
Управляющие конструкции
- Условные операторы
if условие then … else if условие then … else … end if - Циклы
while условие … end whilefor переменная in последовательность … end for
- Операторы прерывания:
break,continue.
- Условные операторы
-
Функции
- Определяются через ключевое слово
function, возвращают значение через операторreturn. - Неограниченное число параметров, возможность вложенного объявления функций (без замыканий).
- Лексическая область видимости, передача контекста (лексические окружения) для каждой функции.
- Определяются через ключевое слово
-
Стандартная библиотека
- Числовые функции:
abs(x),ceil(x),floor(x),round(x),sqrt(x),rnd([min,] max),max(...),min(...),parse_num(s),to_string(x). - Строковые функции:
len(s),lower(s),upper(s),split(s, delim),join(list, delim),replace(s, old, new). - Функции для работы со списками:
range(start, end[, step]),push(list, x),pop(list),insert(list, index, x),remove(list, index),sort(list). - Системные функции:
print(...),println(...),read(),stacktrace().
- Числовые функции:
-
Модель выполнения
- Динамическая типизация: все проверки типов происходят во время выполнения.
- Автоматическое управление памятью: списки, строки и функции хранятся в
std::shared_ptr. Примитивные типы копируются по значению. - Лексическая область видимости: переменные видны внутри того блока, где объявлены; вложенные функции получают копию окружения родителя, но без поддержки замыканий.
- Обработка ошибок: ошибки лексики, синтаксиса и выполнения (деление на ноль, выход за границы и т. д.) перехватываются и выводятся в поток ошибок.
Проект разбит на несколько ключевых модулей:
├── lexer.h — определение класса Lexer
├── lexer.cpp — реализация лексического анализатора
├── token.h — перечисление типов токенов и структура Token
├── keywords.h — таблица ключевых слов языка
│
├── parser.h — интерфейс парсера, прототипы функций разбора
├── parser.cpp — реализация синтаксического анализатора (рекурсивный спуск)
├── AST.h — описание узлов абстрактного синтаксического дерева (AST)
│
├── value.h — класс Value (вариантное значение), FunctionValue, базовые операции
├── value.cpp — реализация арифметических и логических операций, toString, typeName
│
├── environment.h — класс Environment для хранения переменных (с поддержкой родительского окружения)
│
├── interpreter.h — прототип главной функции `interpret`
├── interpreter.cpp — инициализация окружения, регистрация встроенных функций, запуск интерпретации
│
└── README.md — документация проекта
- Lexer (
lexer.*) проходит по исходному потоку символов, разбивая его на токены: числа, идентификаторы, строки, операторы, разделители и комментарии (// … до конца строки). - Parser (
parser.*) реализует рекурсивный спуск для разбора первичных выражений, бинарных операторов, условных конструкций, циклов, функций и блоков. - AST (
AST.h) описывает все узлы дерева: литералы (числа, строки, булевы), переменные, бинарные/унарные операции, вызовы функций, индексацию/срезы, условные выражения, циклы, присваивания, блоки и операторы управления (break/continue/return). - Value (
value.*) инкапсулирует «все возможные» значения IScript: отnilдоdouble,bool,std::string,std::vector<Value>и функций (FunctionValue). Здесь же определены перегруженные операторы:+,-,*,/,%,^, сравнения, логические&&/||/!, доступ по индексу и срезы. - Environment (
environment.h) хранит отображение имя →Value, включает ссылку на родительское окружение. - Interpreter (
interpreter.*)- Создаёт
LexerиParser, строит список всех функций (включая «топ-левел» выражения) векторомstd::vector<std::unique_ptr<FunctionAST>>. - Инициализирует глобальное окружение: регистрирует встроенные функции (
print,println, математические, строковые, список-функции и т. д.). - Сохраняет все определённые пользователем функции в глобальном окружении.
- Исполняет «анонимные» топ-левел выражения (код вне функций).
- Обрабатывает исключения (
std::runtime_error,ReturnException,BreakException,ContinueException) и выводит сообщения об ошибках.
- Создаёт
Исполняемый файл iscript_interpreter читает код из стандартного ввода:
./iscript_interpreter < script.isНиже несколько классических задач, продемонстрированных на IScript. Сохраните каждую в отдельный файл с расширением .is.
function fib(n)
if n < 2 then
return n
end if
return fib(n-1) + fib(n-2)
end function
for i in range(0, 10)
println(fib(i))
end for
Что происходит:
- Рекурсивная функция
fibвычисляет число Фибоначчи. - Цикл
for … inитерируется по списку, возвращаемомуrange(0, 10). - Каждый результат выводится на новой строке.
function maximum(lst)
if len(lst) == 0 then
return nil
end if
max_val = lst[0]
for x in lst
if x > max_val then
max_val = x
end if
end for
return max_val
end function
nums = [3, 42, 7, 19, 0, -5, 100, 23]
println("Maximum is: " + to_string(maximum(nums)))
function fizzbuzz(n)
for i in range(1, n + 1)
if i % 15 == 0 then
println("FizzBuzz")
else if i % 3 == 0 then
println("Fizz")
else if i % 5 == 0 then
println("Buzz")
else
println(to_string(i))
end if
end for
end function
fizzbuzz(30)
-
lexer.h/.cpp
Лексический анализатор: преобразует поток символов в последовательность токенов (Token). -
token.h
Описаниеenum class TokenType, структураToken(тип, лексема, значение, номер строки). -
keywords.h
Таблица соответствия строковых ключевых слов (например,"if","while") и их типовTokenType. -
parser.h/.cpp
Синтаксический анализ: рекурсивный спуск для разбора выражений, условных конструкций, циклов, функций и блоков. -
AST.h
Иерархия классов для узлов AST: литералы (NumberExprAST,StringExprAST,BooleanExprAST), переменные (VariableExprAST), бинарные/унарные операции (BinaryExprAST,UnaryExprAST), вызовы функций (CallExprAST), присваивания, циклы (WhileExprAST,ForExprAST), условные (IfExprAST), блоки (BlockExprAST), управляющие исключения (BreakExprAST,ContinueExprAST,ReturnExprAST). -
value.h/.cpp
КлассValue— контейнер для любого значения IScript. Поддерживает арифметику, сравнения, логику, индексацию и срезы. Также хранитFunctionValueдля встроенных и пользовательских функций. -
environment.h
КлассEnvironmentс отображением имя переменной →Value, включает ссылку на родительское окружение. -
interpreter.h/.cpp
Функцияinterpretзапускает лексер и парсер, затем создаёт глобальное окружение, регистрирует встроенные функции, сохраняет в нём все пользовательские функции и выполняет «анонимные» выражения. Обрабатывает исключения и выводит ошибки. -
README.md
Документация проекта (этот файл).
-
abs(x)
Возвращает абсолютное значение числаx. -
ceil(x)
Округляет числоxвверх до ближайшего целого. -
floor(x)
Округляет числоxвниз до ближайшего целого. -
round(x)
Округляетxдо ближайшего целого по математическим правилам. -
sqrt(x)
Вычисляет квадратный корень из числаx. -
rnd(max)илиrnd(min, max)
Возвращает случайное число (вещественное) из диапазона[0, max)или[min, max). -
max(a, b, c, …)
Возвращает максимальное из переданных числовых аргументов. -
min(a, b, c, …)
Возвращает минимальное из переданных числовых аргументов. -
parse_num(s)
Преобразует строкуsв числоdouble. -
to_string(x)
Преобразует число или логическое значениеxв строку.
-
len(s)
Возвращает длину строкиs. -
lower(s)
Преобразует все символы строкиsв нижний регистр. -
upper(s)
Преобразует все символы строкиsв верхний регистр. -
split(s, delim)
Разбивает строкуsпо разделителюdelimи возвращает список строк. -
join(list, delim)
Объединяет элементы списка строкlist, вставляя между ними разделительdelim. -
replace(s, old, new)
Заменяет все вхождения подстрокиoldв строкеsнаnew.
-
range(end)
Возвращает список чисел от0доend - 1с шагом1. -
range(start, end)
Возвращает список чисел отstartдоend - 1с шагом1. -
range(start, end, step)
Возвращает список чисел отstartдо тех пор, покаv < end, еслиstep > 0v > end, еслиstep < 0.
-
push(list, x)
Добавляет элементxв конец спискаlist. -
pop(list)
Удаляет и возвращает последний элемент спискаlist. -
insert(list, index, x)
Вставляет элементxв списокlistпо индексуindex. -
remove(list, index)
Удаляет и возвращает элемент по индексуindexиз спискаlist. -
sort(list)
Сортирует списокlist«на месте» по возрастанию.
-
print(...)
Выводит аргументы без переноса строки. -
println(...)
Выводит аргументы с последующим переходом на новую строку. -
read()
Считывает строку из стандартного ввода и возвращает её. -
stacktrace()
Возвращает строку с трассировкой стека в момент вызова функции.
Важно! Этот проект предусматривает модульное тестирование для проверки корректности работы интерпретатора. Ниже приведены рекомендации по организации и запуску тестов.
Запуск тестов
- После сборки перейдите в директорию
buildи выполните:ctest --output-on-failure
- Все тесты будут запущены автоматически. В случае неудачных тестов выводятся подробные сообщения.
Проект распространяется под лицензией MIT — см. файл LICENSE.