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