Architecture¶
Этот документ описывает текущую архитектуру LispNT: интерпретатора небольшого Lisp-like функционального языка на F#.
Pipeline¶
CLI (src/Cli) читает .x файл, передаёт текст в parser, при успешном parse
создаёт начальное окружение со стандартной библиотекой и запускает evaluator.
Опционально CLI может напечатать AST (--ast) и включить trace (--trace).
Основные контракты¶
Ключевые типы живут в src/Language:
| Файл | Роль |
|---|---|
Ast.fs |
AST типа Expr |
Values.fs |
runtime-значения Value, окружение Env, thunk и builtin contract |
EvalError.fs |
ошибки parser/evaluator |
Environment.fs |
операции над immutable environment |
Parser.fs |
tokenizer, S-expression parser и перевод в Expr |
Evaluator.fs |
eval/apply interpreter |
Builtins.fs |
стандартная библиотека |
PrettyPrinter.fs |
печать AST и runtime values |
ValueFormatting.fs |
имена runtime-типов для ошибок |
Trace.fs |
trace-события вычисления |
Общие контракты Expr, Value, Env, ParseError, EvalError, parse и
eval являются границами между parser, evaluator, CLI, tests и документацией.
Parser¶
Parser.parse : string -> Result<Expr, ParseError> работает в два шага:
tokenizeразбивает входной текст на скобки и атомы.parseSExprстроит промежуточное S-expression дерево.toExprпереводит S-expression в AST.
Parser поддерживает специальные формы:
if;letи sugar-формуlet name = value body;let*;letrec;cond;lambda;- lambda sugar
=>; delay;force;- binary
and/or.
let*, cond, =>, and и or не добавляют новых AST nodes. Они
разворачиваются parser-ом в уже существующие ELet, EIf и ELambda.
Parser не поддерживает строки, комментарии и несколько top-level выражений в одном файле. Обычная программа должна быть одним выражением.
AST¶
AST намеренно небольшой:
- literals:
ENumber,EBool; - variables:
ESymbol; - control flow:
EIf; - bindings:
ELet,ELetRec; - functions:
ELambda,EApply; - explicit laziness:
EDelay,EForce; - direct list AST:
EList.
Большая часть пользовательского синтаксиса является sugar поверх этих форм.
Environment¶
Окружение имеет тип:
Environment.lookup возвращает UnboundVariable, если имя не найдено.
extend и extendMany создают новое immutable окружение на базе старого.
Замыкания хранят окружение создания:
Поэтому функции используют lexical scope, а не окружение места вызова.
Evaluator¶
Evaluator.eval : Env -> Expr -> Result<Value, EvalError> реализует
eval/apply interpreter:
- literals вычисляются в соответствующие
Value; - symbol ищется в
Env; ifсначала вычисляет condition и требуетBool;letвычисляет value expression и расширяет окружение для body;letrecподдерживает рекурсивную lambda-привязку;lambdaсоздаётVClosure;- application вычисляет callee и аргументы, затем вызывает closure или builtin;
delayсоздаёт thunk без вычисления expression;forceвычисляет thunk и memoize-ит результат;EListвычисляет элементы слева направо и возвращаетVList.
Ошибки выполнения возвращаются как Error EvalError; обычные ошибки языка не
пробрасываются исключениями.
Builtins¶
Builtins.makeBuiltins создаёт начальное окружение стандартной библиотеки.
Builtins имеют контракт:
Стандартная библиотека включает:
- арифметику:
+,-,*,/; - сравнения:
=,<,>; - boolean builtin:
not; - списки:
list,head,tail,cons,empty?; - higher-order functions:
map,filter,fold; Maybe:just,nothing,fmap,bind.
Higher-order builtins умеют применять как closures, так и builtins через общий
helper внутри Builtins.fs.
CLI¶
CLI живёт в src/Cli/Program.fs.
Поведение:
- без аргументов или с
--helpпечатает справку; - если файл не найден, возвращает exit code
1; - parse error печатается как
Parse error: ...; - runtime error печатается как
Runtime error: ...; - успешный результат печатается как
Result: <value>.
CLI является единственной IO-частью проекта. В самом языке нет print,
строковых literals и builtins для чтения/записи файлов.
Trace¶
Trace.fs содержит глобальный флаг Trace.enabled и функции:
enter;exitWithResult;exitWithError.
Evaluator вызывает trace hooks на входе в eval, на application и при
force thunk. Если trace выключен, hooks ничего не печатают.
Trace предназначен для демонстрации и debugging, а не для изменения семантики языка.
Tests¶
Тесты лежат в tests/Language.Tests и покрывают:
- parser и syntax sugar;
- evaluator semantics;
- builtins;
- CLI-related examples через parser/evaluator pipeline;
- runnable programs из
examples/.
Основная команда:
Ограничения текущей реализации¶
- Нет string literals.
- Нет комментариев в
.xфайлах. - Нет нескольких top-level выражений в одном файле.
- Нет пользовательских algebraic data types.
- Нет IO builtins внутри языка.
andиorбинарные.letrecпринимает только lambda value expression.