Личный проект: интерпретатор скриптового языка 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.