mirror of
https://github.com/ProgramSnail/lang_modes_check.git
synced 2025-12-06 00:58:42 +00:00
day1 progress: typecheck template, mode check for unique
This commit is contained in:
commit
1f8071c89b
11 changed files with 525 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
build
|
||||||
|
.cache
|
||||||
21
CMakeLists.txt
Normal file
21
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
project(Lang)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER clang)
|
||||||
|
set(CMAKE_CXX_COMPILER clang++)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
include
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(lang src/main.cpp
|
||||||
|
src/parsing_tree.cpp
|
||||||
|
src/type_check.cpp
|
||||||
|
src/mode_check.cpp)
|
||||||
20
compile_commands.json
Normal file
20
compile_commands.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"directory": "/home/dragon/Containers/Lang/mini-rust/build",
|
||||||
|
"command": "clang++ -I/home/dragon/Containers/Lang/mini-rust/include -Wall -Wextra -std=gnu++20 -o CMakeFiles/lang.dir/src/main.cpp.o -c /home/dragon/Containers/Lang/mini-rust/src/main.cpp",
|
||||||
|
"file": "/home/dragon/Containers/Lang/mini-rust/src/main.cpp",
|
||||||
|
"output": "CMakeFiles/lang.dir/src/main.cpp.o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"directory": "/home/dragon/Containers/Lang/mini-rust/build",
|
||||||
|
"command": "clang++ -I/home/dragon/Containers/Lang/mini-rust/include -Wall -Wextra -std=gnu++20 -o CMakeFiles/lang.dir/src/parsing_tree.cpp.o -c /home/dragon/Containers/Lang/mini-rust/src/parsing_tree.cpp",
|
||||||
|
"file": "/home/dragon/Containers/Lang/mini-rust/src/parsing_tree.cpp",
|
||||||
|
"output": "CMakeFiles/lang.dir/src/parsing_tree.cpp.o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"directory": "/home/dragon/Containers/Lang/mini-rust/build",
|
||||||
|
"command": "clang++ -I/home/dragon/Containers/Lang/mini-rust/include -Wall -Wextra -std=gnu++20 -o CMakeFiles/lang.dir/src/typechecker.cpp.o -c /home/dragon/Containers/Lang/mini-rust/src/typechecker.cpp",
|
||||||
|
"file": "/home/dragon/Containers/Lang/mini-rust/src/typechecker.cpp",
|
||||||
|
"output": "CMakeFiles/lang.dir/src/typechecker.cpp.o"
|
||||||
|
}
|
||||||
|
]
|
||||||
2
include/mode_check.hpp
Normal file
2
include/mode_check.hpp
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
114
include/parsing_tree.hpp
Normal file
114
include/parsing_tree.hpp
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace types {
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Type;
|
||||||
|
using TypePtr = shared_ptr<Type>;
|
||||||
|
|
||||||
|
struct ArrowType {
|
||||||
|
vector<TypePtr> types;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoolType {};
|
||||||
|
struct IntType {};
|
||||||
|
// struct UnitType {};
|
||||||
|
|
||||||
|
struct AnyType {};
|
||||||
|
|
||||||
|
struct Type {
|
||||||
|
static constexpr size_t ARROW_TYPE_INDEX = 0;
|
||||||
|
variant<ArrowType, BoolType, IntType, AnyType> type;
|
||||||
|
|
||||||
|
enum class Loc { GLOBAL, LOCAL } loc = Loc::GLOBAL;
|
||||||
|
enum class Uniq { SHARED, UNIQUE, EXCL } uniq = Uniq::SHARED;
|
||||||
|
enum class Lin { MANY, ONCE, SEP } lin = Lin::MANY;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
Type make_type(Args&&... args) {
|
||||||
|
return Type{T{std::forward<Args>(args)...}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace types
|
||||||
|
|
||||||
|
namespace nodes {
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
optional<types::Type> type = std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Expr;
|
||||||
|
using ExprPtr = shared_ptr<Expr>;
|
||||||
|
using ExprPtrV = std::vector<ExprPtr>;
|
||||||
|
|
||||||
|
struct Arg : public Node {
|
||||||
|
string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Const : public Node {
|
||||||
|
int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Var : public Node {
|
||||||
|
string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Let : public Node {
|
||||||
|
Arg name;
|
||||||
|
ExprPtr body;
|
||||||
|
ExprPtr where;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Lambda : public Node {
|
||||||
|
|
||||||
|
vector<Arg> args;
|
||||||
|
ExprPtr expr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Call : public Node {
|
||||||
|
ExprPtr func;
|
||||||
|
vector<ExprPtr> args;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Condition : public Node {
|
||||||
|
ExprPtr condition;
|
||||||
|
ExprPtr then_case;
|
||||||
|
ExprPtr else_case;
|
||||||
|
};
|
||||||
|
|
||||||
|
// struct FunctionDecl {
|
||||||
|
// string name;
|
||||||
|
// vector<Arg> args;
|
||||||
|
// ExprPtr expr;
|
||||||
|
// };
|
||||||
|
|
||||||
|
struct Expr {
|
||||||
|
variant<Const, Var, Let, Lambda, Call, Condition> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
ExprPtr make_expr(Args&&... args) {
|
||||||
|
return std::make_shared<Expr>(T{std::forward<Args>(args)...});
|
||||||
|
}
|
||||||
|
|
||||||
|
static ExprPtr lambda1(string name, ExprPtr expr) {
|
||||||
|
return make_expr<Lambda>(vector<Arg>{{name}}, std::move(expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprPtr operator_call(string name, ExprPtr left, ExprPtr right) {
|
||||||
|
return make_expr<Call>(make_expr<Var>(name), ExprPtrV{left, right});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: all constructors
|
||||||
|
|
||||||
|
} // namespace nodes
|
||||||
0
include/type_check.hpp
Normal file
0
include/type_check.hpp
Normal file
6
src/main.cpp
Normal file
6
src/main.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "parsing_tree.hpp"
|
||||||
|
#include "typechecker.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
236
src/mode_check.cpp
Normal file
236
src/mode_check.cpp
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
#include "mode_check.hpp"
|
||||||
|
|
||||||
|
#include "parsing_tree.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <source_location>
|
||||||
|
|
||||||
|
namespace mode_check {
|
||||||
|
|
||||||
|
// C++ 23
|
||||||
|
[[noreturn]] inline void unreachable() {
|
||||||
|
// Uses compiler specific extensions if possible.
|
||||||
|
// Even if no extension is used, undefined behavior is still raised by
|
||||||
|
// an empty function body and the noreturn attribute.
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__) // MSVC
|
||||||
|
__assume(false);
|
||||||
|
#else // GCC, Clang
|
||||||
|
__builtin_unreachable();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace types;
|
||||||
|
|
||||||
|
struct VarState {
|
||||||
|
Type type;
|
||||||
|
size_t count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModeError {
|
||||||
|
enum Error {
|
||||||
|
UNKNOWN,
|
||||||
|
NO_VAR,
|
||||||
|
NO_VAR_TYPE,
|
||||||
|
NO_TYPE,
|
||||||
|
WRONG_TYPE,
|
||||||
|
LOCAL,
|
||||||
|
UNIQUE,
|
||||||
|
EXCL,
|
||||||
|
ONCE,
|
||||||
|
SEP
|
||||||
|
} error = UNKNOWN;
|
||||||
|
|
||||||
|
source_location location;
|
||||||
|
|
||||||
|
ModeError(Error error, source_location location)
|
||||||
|
: type(type), location(location) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
friend struct Context;
|
||||||
|
|
||||||
|
State() { vars_stack.emplace_back(); }
|
||||||
|
|
||||||
|
void set_error(ModeError::Error error,
|
||||||
|
source_location location = source_location::current()) {
|
||||||
|
if (first_error.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
first_error = ModeError(std::move(error), location);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<VarState *> get_var_state(const std::string &name,
|
||||||
|
bool last_context_only = false) {
|
||||||
|
for (auto vars_it = vars_stack.rbegin(); vars_it != vars_stack.rend();
|
||||||
|
++vars_it) {
|
||||||
|
auto var_it = vars_it->find(name);
|
||||||
|
if (var_it == vars_it->end()) {
|
||||||
|
if (last_context_only) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return &var_it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_error(ModeError::NO_VAR);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_var(std::string name, Type type) {
|
||||||
|
vars_stack.back().insert({std::move(name), VarState{std::move(type)}});
|
||||||
|
// TODO: check existance
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ModeError> get_first_error() { return first_error; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void enter_context() { vars_stack.emplace_back(); }
|
||||||
|
|
||||||
|
void exit_context() { vars_stack.pop_back(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
vector<map<string, VarState>> vars_stack;
|
||||||
|
std::optional<ModeError> first_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
Context(State &state) : state_(state) { state_.enter_context(); }
|
||||||
|
|
||||||
|
~Context() { state_.exit_context(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
State &state_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// struct ExclVarScope {
|
||||||
|
// ExclVarScope(std::string name, State &state)
|
||||||
|
// : name_(std::move(name)), state_(state) {}
|
||||||
|
|
||||||
|
// ~ExclVarScope() {}
|
||||||
|
|
||||||
|
// private:
|
||||||
|
// std::string name_;
|
||||||
|
// State &state_;
|
||||||
|
// };
|
||||||
|
|
||||||
|
void check_expr(nodes::ExprPtr expr, State &state);
|
||||||
|
|
||||||
|
void check_const(const nodes::Const &, State &) {}
|
||||||
|
|
||||||
|
void check_var(const nodes::Var &expr, State &state) {
|
||||||
|
if (not expr.type.has_value()) {
|
||||||
|
state.set_error(ModeError::NO_TYPE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto type = expr.type.value();
|
||||||
|
|
||||||
|
if (auto maybe_var_state = state.get_var_state(expr.name);
|
||||||
|
maybe_var_state.has_value()) {
|
||||||
|
auto &var_state = *maybe_var_state.value();
|
||||||
|
|
||||||
|
if (var_state.type.uniq == Type::Uniq::UNIQUE) {
|
||||||
|
++var_state.count;
|
||||||
|
if (var_state.count > 1 || type.uniq != Type::Uniq::UNIQUE) {
|
||||||
|
state.set_error(ModeError::UNIQUE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: other modes
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_let(const nodes::Let &expr, State &state) {
|
||||||
|
{
|
||||||
|
Context context(state);
|
||||||
|
check_expr(expr.body, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Context context(state);
|
||||||
|
|
||||||
|
if (not expr.name.type.has_value()) {
|
||||||
|
state.set_error(ModeError::NO_VAR_TYPE);
|
||||||
|
}
|
||||||
|
state.add_var(expr.name.name, expr.name.type.value());
|
||||||
|
|
||||||
|
check_expr(expr.where, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_lambda(const nodes::Lambda &expr, State &state) {
|
||||||
|
Context context(state);
|
||||||
|
|
||||||
|
for (const auto &arg : expr.args) {
|
||||||
|
if (not arg.type.has_value()) {
|
||||||
|
state.set_error(ModeError::NO_VAR_TYPE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
state.add_var(arg.name, arg.type.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
check_expr(expr.expr, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_call(const nodes::Call &expr, State &state) {
|
||||||
|
// if (not expr.type.has_value()) {
|
||||||
|
// state.set_error(ModeError::NO_TYPE);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// auto type = expr.type.value();
|
||||||
|
|
||||||
|
// if (not holds_alternative<types::ArrowType>(type.type)) {
|
||||||
|
// state.set_error(ModeError::WRONG_TYPE);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const auto &arrow_type = get<types::ArrowType>(type.type);
|
||||||
|
|
||||||
|
// if (arrow_type.types.size() != expr.args.size() + 1) {
|
||||||
|
// state.set_error(ModeError::WRONG_TYPE);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// not required, because types are propogated to Vars in type check
|
||||||
|
|
||||||
|
check_expr(expr.func, state);
|
||||||
|
|
||||||
|
for (const auto &arg : expr.args) {
|
||||||
|
check_expr(arg, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_condition(const nodes::Condition &expr, State &state) {
|
||||||
|
check_expr(expr.condition, state);
|
||||||
|
check_expr(expr.then_case, state);
|
||||||
|
check_expr(expr.else_case, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_expr(nodes::ExprPtr expr, State &state) {
|
||||||
|
switch (expr->value.index()) {
|
||||||
|
case 0: // Const
|
||||||
|
check_const(std::get<0>(expr->value), state);
|
||||||
|
break;
|
||||||
|
case 1: // Var
|
||||||
|
check_var(std::get<1>(expr->value), state);
|
||||||
|
break;
|
||||||
|
case 2: // Let
|
||||||
|
check_let(std::get<2>(expr->value), state);
|
||||||
|
break;
|
||||||
|
case 3: // Lambda
|
||||||
|
check_lambda(std::get<3>(expr->value), state);
|
||||||
|
break;
|
||||||
|
case 4: // Call
|
||||||
|
check_call(std::get<4>(expr->value), state);
|
||||||
|
break;
|
||||||
|
case 5: // Condition
|
||||||
|
check_condition(std::get<5>(expr->value), state);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mode_check
|
||||||
1
src/parsing_tree.cpp
Normal file
1
src/parsing_tree.cpp
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "parsing_tree.hpp"
|
||||||
112
src/type_check.cpp
Normal file
112
src/type_check.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
#include "type_check.hpp"
|
||||||
|
|
||||||
|
#include "parsing_tree.hpp"
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
namespace type_check {
|
||||||
|
|
||||||
|
using namespace types;
|
||||||
|
|
||||||
|
// C++ 23
|
||||||
|
[[noreturn]] inline void unreachable() {
|
||||||
|
// Uses compiler specific extensions if possible.
|
||||||
|
// Even if no extension is used, undefined behavior is still raised by
|
||||||
|
// an empty function body and the noreturn attribute.
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__) // MSVC
|
||||||
|
__assume(false);
|
||||||
|
#else // GCC, Clang
|
||||||
|
__builtin_unreachable();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// bool eq(Type x, Type y) {
|
||||||
|
// if (x.type.index() != y.type.index()) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (x.type.index() != Type::ARROW_TYPE_INDEX) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const auto &x_types = std::get<Type::ARROW_TYPE_INDEX>(x.type);
|
||||||
|
// const auto &y_types = std::get<Type::ARROW_TYPE_INDEX>(y.type);
|
||||||
|
|
||||||
|
// if (x_types.size() != )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using Typechecked = std::pair<T /*expr*/, types::Type /*type*/>;
|
||||||
|
|
||||||
|
Typechecked<nodes::ExprPtr> typecheck_expr(nodes::ExprPtr expr);
|
||||||
|
|
||||||
|
Typechecked<nodes::Const> typecheck_const(nodes::Const expr) {
|
||||||
|
// TODO
|
||||||
|
return {std::move(expr), make_type<AnyType>()};
|
||||||
|
}
|
||||||
|
|
||||||
|
Typechecked<nodes::Var> typecheck_var(nodes::Var expr) {
|
||||||
|
// TODO
|
||||||
|
return {std::move(expr), make_type<AnyType>()};
|
||||||
|
}
|
||||||
|
|
||||||
|
Typechecked<nodes::Let> typecheck_let(nodes::Let expr) {
|
||||||
|
// TODO
|
||||||
|
return {std::move(expr), make_type<AnyType>()};
|
||||||
|
}
|
||||||
|
|
||||||
|
Typechecked<nodes::Lambda> typecheck_lambda(nodes::Lambda expr) {
|
||||||
|
// TODO
|
||||||
|
return {std::move(expr), make_type<AnyType>()};
|
||||||
|
}
|
||||||
|
|
||||||
|
Typechecked<nodes::Call> typecheck_call(nodes::Call expr) {
|
||||||
|
// TODO
|
||||||
|
return {std::move(expr), make_type<AnyType>()};
|
||||||
|
}
|
||||||
|
|
||||||
|
Typechecked<nodes::Condition> typecheck_condition(nodes::Condition expr) {
|
||||||
|
// const auto [condition_expr, condition_type] = typecheck_expr(expr.condition);
|
||||||
|
// expr.condition = std::move(condition_expr);
|
||||||
|
|
||||||
|
// const auto [then_case_expr, then_case_type] = typecheck_expr(expr.then_case);
|
||||||
|
// expr.then_case = std::move(then_case_expr);
|
||||||
|
|
||||||
|
// const auto [else_case_expr, else_type_type] = typecheck_expr(expr.else_case);
|
||||||
|
// expr.else_case = std::move(else_case_expr);
|
||||||
|
|
||||||
|
return {std::move(expr), make_type<AnyType>()};
|
||||||
|
}
|
||||||
|
|
||||||
|
Typechecked<nodes::ExprPtr> typecheck_expr(nodes::ExprPtr expr) {
|
||||||
|
types::Type type;
|
||||||
|
|
||||||
|
switch (expr->value.index()) {
|
||||||
|
case 0: // Const
|
||||||
|
std::tie(expr->value, type) = typecheck_const(std::get<0>(expr->value));
|
||||||
|
break;
|
||||||
|
case 1: // Var
|
||||||
|
std::tie(expr->value, type) = typecheck_var(std::get<1>(expr->value));
|
||||||
|
break;
|
||||||
|
case 2: // Let
|
||||||
|
std::tie(expr->value, type) = typecheck_let(std::get<2>(expr->value));
|
||||||
|
break;
|
||||||
|
case 3: // Lambda
|
||||||
|
std::tie(expr->value, type) = typecheck_lambda(std::get<3>(expr->value));
|
||||||
|
break;
|
||||||
|
case 4: // Call
|
||||||
|
std::tie(expr->value, type) = typecheck_call(std::get<4>(expr->value));
|
||||||
|
break;
|
||||||
|
case 5: // Condition
|
||||||
|
std::tie(expr->value, type) = typecheck_condition(std::get<5>(expr->value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {std::move(expr), std::move(type)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace type_check
|
||||||
11
tests/tests.cpp
Normal file
11
tests/tests.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "parsing_tree.hpp"
|
||||||
|
|
||||||
|
using namespace nodes;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
const auto program =
|
||||||
|
Expr{Let{Arg{"f", {}},
|
||||||
|
lambda1("x", operator_call("+", make_expr<Var>("x"),
|
||||||
|
make_expr<Var>("x"))),
|
||||||
|
make_expr<Var>("f")}};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue