#pragma once
|
|
#include <variant>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <sstream>
|
|
#include <functional>
|
|
#include <cassert>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <unordered_map>
|
|
#include <cstdlib>
|
|
#include <cmath>
|
|
|
|
namespace lispy {
|
|
class cons;
|
|
class function;
|
|
|
|
struct empty {};
|
|
struct cons_start{};
|
|
struct cons_end{};
|
|
|
|
struct atom {
|
|
int value;
|
|
};
|
|
|
|
struct sp_coords {
|
|
bool is_relative = false;
|
|
size_t x, y;
|
|
};
|
|
|
|
struct sp_range {
|
|
bool is_relative = false;
|
|
size_t x, y;
|
|
size_t width, height;
|
|
};
|
|
|
|
using lvalue = std::variant<int64_t, double, std::string, atom, empty, std::shared_ptr<cons>, sp_coords, sp_range, std::shared_ptr<function>>;
|
|
|
|
using token = std::variant<cons_start, cons_end, lvalue>;
|
|
|
|
struct context {
|
|
std::unordered_map<std::string, int> atoms;
|
|
int last_atom = 0;
|
|
bool error_crash = false;
|
|
|
|
int get_atom(const std::string& key)
|
|
{
|
|
if(atoms.count(key)) {
|
|
return atoms[key];
|
|
} else {
|
|
return atoms[key] = ++last_atom;
|
|
}
|
|
}
|
|
};
|
|
|
|
class cons {
|
|
public:
|
|
lvalue self = empty{};
|
|
std::unique_ptr<cons> other{};
|
|
|
|
cons()
|
|
{
|
|
self = empty{};
|
|
other = std::unique_ptr<cons>{};
|
|
}
|
|
|
|
cons(lvalue first)
|
|
: self(first)
|
|
, other()
|
|
{ }
|
|
|
|
cons(std::vector<lvalue> data)
|
|
{
|
|
if(data.size() == 0) {
|
|
self = empty{};
|
|
other = std::unique_ptr<cons>{};
|
|
} else if(data.size() >= 1) {
|
|
self = data[0];
|
|
for(auto it = data.begin()+1; it != data.end(); ++it)
|
|
this->append(*it);
|
|
}
|
|
}
|
|
|
|
cons(const cons& oth)
|
|
: self(oth.self)
|
|
, other(oth.other ? std::make_unique<cons>(*(oth.other)) : nullptr)
|
|
{}
|
|
void operator=(const cons& oth)
|
|
{
|
|
self = oth.self;
|
|
if(oth.other) {
|
|
other = std::make_unique<cons>(*oth.other);
|
|
} else {
|
|
other = std::unique_ptr<cons>{};
|
|
}
|
|
}
|
|
|
|
cons(cons&& oth)
|
|
: self(oth.self)
|
|
, other(oth.other ? std::move(std::make_unique<cons>(*(oth.other))) : std::unique_ptr<cons>{})
|
|
{}
|
|
|
|
void append(lvalue value)
|
|
{
|
|
if(!other) {
|
|
other = std::make_unique<cons>(value);
|
|
} else {
|
|
other->append(value);
|
|
}
|
|
}
|
|
};
|
|
|
|
class function {
|
|
public:
|
|
virtual lvalue operator() (cons arguments) = 0;
|
|
};
|
|
|
|
inline char hexdigit(char v)
|
|
{
|
|
if(v >= '0' && v<='9') {
|
|
return v - '0';
|
|
} else if(v >= 'a' && v <= 'f') {
|
|
return 10 + v - 'a';
|
|
} else if(v >= 'A' && v <= 'F') {
|
|
return 10 + v - 'A';
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
inline std::string escape(std::string_view v)
|
|
{
|
|
auto it = v.begin();
|
|
std::stringstream stream;
|
|
stream<<'"';
|
|
while(it != v.end())
|
|
{
|
|
switch(*it)
|
|
{
|
|
case '"':
|
|
{
|
|
stream << "\\\"";
|
|
}
|
|
break;
|
|
case '\n':
|
|
{
|
|
stream << "\\n";
|
|
}
|
|
break;
|
|
case '\t':
|
|
{
|
|
stream << "\\t";
|
|
}
|
|
break;
|
|
case '\v':
|
|
{
|
|
stream << "\\v";
|
|
}
|
|
break;
|
|
case '\a':
|
|
{
|
|
stream << "\\a";
|
|
}
|
|
break;
|
|
default:
|
|
stream << *it;
|
|
}
|
|
++it;
|
|
}
|
|
stream<<'"';
|
|
return stream.str();
|
|
}
|
|
|
|
inline void print_visitor(std::ostream& stream, const lvalue& value, const context& ctx) {
|
|
std::visit([&](auto arg) {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
if constexpr (std::is_same_v<T, int64_t>) {
|
|
stream << arg;
|
|
} else if constexpr (std::is_same_v<T, double>) {
|
|
stream << arg;
|
|
} else if constexpr (std::is_same_v<T, empty>) {
|
|
stream << "()";
|
|
} else if constexpr (std::is_same_v<T, function>) {
|
|
stream << "#func"<<&*arg;
|
|
} else if constexpr (std::is_same_v<T, std::string>) {
|
|
stream << escape(arg);
|
|
} else if constexpr (std::is_same_v<T, std::shared_ptr<cons>>) {
|
|
stream << "(";
|
|
bool is_first = true;
|
|
cons p = *arg;
|
|
do{
|
|
|
|
if(!is_first)
|
|
{
|
|
p = *p.other;
|
|
}
|
|
print_visitor(stream, p.self, ctx);
|
|
if(p.other)
|
|
{
|
|
stream << " ";
|
|
}
|
|
is_first = false;
|
|
} while(p.other);
|
|
stream << ")";
|
|
}
|
|
else if constexpr (std::is_same_v<T, sp_coords>){
|
|
assert(false);
|
|
} else if constexpr (std::is_same_v<T, sp_range>){
|
|
assert(false);
|
|
} else if constexpr (std::is_same_v<T, atom>) {
|
|
for(auto& v : ctx.atoms)
|
|
{
|
|
if(v.second == arg.value)
|
|
{
|
|
stream << v.first;
|
|
return;
|
|
}
|
|
}
|
|
assert(false);
|
|
} else {
|
|
std::cerr << typeid(T).name() << " detected in print_visitor ?" << std::endl;
|
|
if(ctx.error_crash)
|
|
{
|
|
std::exit(-1);
|
|
}
|
|
}
|
|
}, value);
|
|
}
|
|
|
|
inline void print_types_visitor(std::ostream& stream, const lvalue& value, const context& ctx) {
|
|
std::visit([&](auto arg) {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
if constexpr (std::is_same_v<T, int64_t>) {
|
|
stream << "integer";
|
|
} else if constexpr (std::is_same_v<T, double>) {
|
|
stream << "double";
|
|
} else if constexpr (std::is_same_v<T, empty>) {
|
|
stream << "nil";
|
|
} else if constexpr (std::is_same_v<T, function>) {
|
|
stream << "function";
|
|
} else if constexpr (std::is_same_v<T, std::string>) {
|
|
stream << "string";
|
|
} else if constexpr (std::is_same_v<T, std::shared_ptr<cons>>){
|
|
stream << "(";
|
|
bool is_first = true;
|
|
cons p = *arg;
|
|
do{
|
|
|
|
if(!is_first)
|
|
{
|
|
p = *p.other;
|
|
}
|
|
print_types_visitor(stream, p.self, ctx);
|
|
if(p.other)
|
|
{
|
|
stream << " ";
|
|
}
|
|
is_first = false;
|
|
} while(p.other);
|
|
stream << ")";
|
|
} else if constexpr (std::is_same_v<T, sp_coords>){
|
|
stream << "coords";
|
|
} else if constexpr (std::is_same_v<T, sp_range>){
|
|
stream << "range";
|
|
} else if constexpr (std::is_same_v<T, atom>) {
|
|
stream << "atom";
|
|
} else {
|
|
std::cerr << typeid(T).name() << " detected in print_types_visitor ?" << std::endl;
|
|
if(ctx.error_crash)
|
|
{
|
|
std::exit(-1);
|
|
}
|
|
}
|
|
}, value);
|
|
}
|
|
|
|
inline std::pair<lvalue, std::string_view> parse_string(std::string_view data)
|
|
{
|
|
auto it = data.begin();
|
|
assert(*it == '\"');
|
|
++it;
|
|
std::stringstream value(std::string(data.begin(), data.end()));
|
|
std::string ret;
|
|
value >> std::quoted(ret);
|
|
return std::make_pair<lvalue, std::string_view>(ret, std::string_view{data.begin(), (size_t)value.rdbuf()->in_avail()});
|
|
}
|
|
|
|
inline std::pair<lvalue, std::string_view> parse_atom(std::string_view data, context& ctx)
|
|
{
|
|
assert(!iswspace(data[0]));
|
|
size_t idx = 1;
|
|
while(!iswspace(data[idx]))
|
|
{
|
|
idx++;
|
|
}
|
|
atom v;
|
|
v.value = ctx.get_atom(std::string(data.begin(), data.begin()+idx));
|
|
return std::make_pair(lvalue{v}, std::string_view{data.begin(), idx});
|
|
}
|
|
|
|
inline std::pair<lvalue, std::string_view> parse_number(std::string_view data)
|
|
{
|
|
char* end_f;
|
|
char* end_d;
|
|
double try_f = strtod (data.data(), &end_f);
|
|
int err_f = errno;
|
|
int64_t try_d = strtoll(data.data(), &end_d, 10);
|
|
int err_d = errno;
|
|
|
|
if(err_d == ERANGE)
|
|
{
|
|
return std::make_pair(lvalue{(double)try_f}, std::string_view{data.begin(), (size_t)(end_f-data.data())});
|
|
}
|
|
|
|
if(try_f != std::trunc(try_f))
|
|
{
|
|
return std::make_pair(lvalue{(double)try_f}, std::string_view{data.begin(), (size_t)(end_f-data.data())});
|
|
}
|
|
|
|
return std::make_pair(lvalue{int64_t(try_d)}, std::string_view{data.begin(), (size_t)(end_f-data.data())});
|
|
}
|
|
|
|
inline std::pair<lvalue, std::string_view> parse_selector(std::string_view data)
|
|
{
|
|
return std::make_pair(lvalue{}, std::string_view{data.begin(), 0});
|
|
}
|
|
|
|
inline size_t find_matching(const std::basic_string_view<token>& data, const size_t idx)
|
|
{
|
|
size_t try_idx = idx;
|
|
int mass = 1;
|
|
|
|
do{
|
|
try_idx++;
|
|
std::visit([&](auto arg) {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
if constexpr (std::is_same_v<T, cons_start>) {
|
|
++mass;
|
|
} else if constexpr (std::is_same_v<T, cons_end>) {
|
|
--mass;
|
|
} else {}
|
|
}, data[try_idx]);
|
|
} while(mass != 0 && try_idx < data.size());
|
|
|
|
if(try_idx<data.size())
|
|
{
|
|
return try_idx;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
inline std::pair<size_t, lvalue> parse(const std::basic_string_view<token>& data, context& ctx)
|
|
{
|
|
auto ret = std::make_shared<cons>();
|
|
size_t sz = 0;
|
|
|
|
if(data.size() == 0)
|
|
{
|
|
return std::make_pair(0,(lvalue)empty{});
|
|
}
|
|
|
|
size_t skip = 0;
|
|
|
|
size_t idx = 0;
|
|
|
|
while(idx < data.size())
|
|
{
|
|
if(skip)
|
|
{
|
|
skip--;
|
|
++idx;
|
|
continue;
|
|
}
|
|
|
|
std::visit([&](const auto& arg) {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
if constexpr (std::is_same_v<T, cons_start>) {
|
|
auto matching = find_matching(data, idx);
|
|
auto res = parse(std::basic_string_view<token>{data.begin()+1, matching-idx-1}, ctx);
|
|
ret->append(res.second);
|
|
skip = matching - idx;
|
|
++sz;
|
|
} else if constexpr (std::is_same_v<T, cons_end>) {
|
|
std::cerr << typeid(T).name() << " mismatched parenthesis" << std::endl;
|
|
} else if constexpr (std::is_same_v<T, lvalue>) {
|
|
ret->append(arg);
|
|
++sz;
|
|
} else {
|
|
std::cerr << typeid(T).name() << " cat in the parser ?" << std::endl;
|
|
if(ctx.error_crash)
|
|
{
|
|
std::exit(-1);
|
|
}
|
|
}
|
|
}, data[idx]);
|
|
++idx;
|
|
}
|
|
return std::make_pair(sz, (lvalue)(std::make_shared<cons>(*ret->other)));
|
|
}
|
|
|
|
inline std::vector<token> lex(const std::string_view data, context& ctx)
|
|
{
|
|
std::vector<token> ret;
|
|
|
|
auto it = data.begin();
|
|
bool is_done = false;
|
|
do{
|
|
if (it == data.end()) {
|
|
is_done = true;
|
|
} else if(isdigit(*it) || (*it == '-' && isdigit(*(it+1)))) {
|
|
auto value = parse_number(std::string_view{it, (size_t)(data.end() - it)});
|
|
ret.push_back(value.first);
|
|
it += value.second.size()+1;
|
|
} else if(*it == '\"') {
|
|
auto value = parse_string(std::string_view{it, (size_t)(data.end() - it)});
|
|
ret.push_back(value.first);
|
|
size_t forward_jump = std::string_view{it, (size_t)(data.end() - it)}.size()-value.second.size();
|
|
it += forward_jump;
|
|
} else if (*it == '(') {
|
|
ret.push_back(cons_start{});
|
|
++it;
|
|
} else if (*it == ')') {
|
|
ret.push_back(cons_end{});
|
|
++it;
|
|
} else if (iswspace(*it)) {
|
|
++it;
|
|
} else if (*it == '$') {
|
|
auto value = parse_selector(std::string_view{it, (size_t)(data.end() - it)});
|
|
ret.push_back(value.first);
|
|
it += value.second.size()+1;
|
|
} else {
|
|
auto value = parse_atom(std::string_view{it, (size_t)(data.end() - it)}, ctx);
|
|
ret.push_back(value.first);
|
|
it += value.second.size();
|
|
}
|
|
}while(!is_done);
|
|
|
|
return ret;
|
|
}
|
|
|
|
inline lvalue eval(const std::string_view& data, context& ctx)
|
|
{
|
|
auto n = lex(data, ctx);
|
|
auto p = parse(std::basic_string_view<token>(n.data(), n.size()), ctx);
|
|
return p.second;
|
|
}
|
|
}
|