Compare commits
6
Commits
f6f8207e1a
...
c6ea42bc5d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6ea42bc5d | ||
|
|
5c5a766b7e | ||
|
|
da0b0a91a6 | ||
|
|
b51a3bf15b | ||
|
|
27e584ea84 | ||
|
|
0fea075953 |
+5
-1
@@ -74,7 +74,7 @@ target_link_libraries(${PROJECT} ruc)
|
||||
# Execute target
|
||||
|
||||
add_custom_target(run
|
||||
COMMAND ${PROJECT}
|
||||
COMMAND ${PROJECT} -c
|
||||
DEPENDS ${PROJECT})
|
||||
|
||||
# ------------------------------------------
|
||||
@@ -87,3 +87,7 @@ add_dependencies(test0 ${PROJECT})
|
||||
add_custom_target(test1
|
||||
COMMAND env STEP=step1_read_print MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step1_read_print.mal -- ./${PROJECT})
|
||||
add_dependencies(test1 ${PROJECT})
|
||||
|
||||
add_custom_target(test2
|
||||
COMMAND env STEP=step_eval MAL_IMPL=js ../vendor/mal/runtest.py --deferrable --optional ../vendor/mal/tests/step2_eval.mal -- ./${PROJECT})
|
||||
add_dependencies(test2 ${PROJECT})
|
||||
|
||||
+52
@@ -5,8 +5,10 @@
|
||||
*/
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
#include <string>
|
||||
|
||||
#include "ast.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
@@ -24,6 +26,13 @@ void Collection::addNode(ASTNode* node)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void HashMap::addElement(const std::string& key, ASTNode* value)
|
||||
{
|
||||
m_elements.emplace(key, value);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
String::String(const std::string& data)
|
||||
: m_data(data)
|
||||
{
|
||||
@@ -31,6 +40,13 @@ String::String(const std::string& data)
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Keyword::Keyword(const std::string& data)
|
||||
: m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Number::Number(int64_t number)
|
||||
: m_number(number)
|
||||
{
|
||||
@@ -43,4 +59,40 @@ Symbol::Symbol(const std::string& symbol)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Value::Value(const std::string& value)
|
||||
: m_value(value)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
Function::Function(Lambda lambda)
|
||||
: m_lambda(lambda)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void Formatter<blaze::ASTNode*>::format(Builder& builder, blaze::ASTNode* value) const
|
||||
{
|
||||
if (is<blaze::String>(value)) {
|
||||
return Formatter<std::string>::format(builder, static_cast<blaze::String*>(value)->data());
|
||||
}
|
||||
if (is<blaze::Keyword>(value)) {
|
||||
return Formatter<std::string>::format(builder, ":" + static_cast<blaze::Keyword*>(value)->keyword().substr(1));
|
||||
}
|
||||
else if (is<blaze::Number>(value)) {
|
||||
Formatter<int64_t> formatter { .specifier = specifier };
|
||||
return formatter.format(builder, static_cast<blaze::Number*>(value)->number());
|
||||
}
|
||||
else if (is<blaze::Value>(value)) {
|
||||
return Formatter<std::string>::format(builder, static_cast<blaze::Value*>(value)->value());
|
||||
}
|
||||
else if (is<blaze::Symbol>(value)) {
|
||||
return Formatter<std::string>::format(builder, static_cast<blaze::Symbol*>(value)->symbol());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
#include <cstdint> // int64_t
|
||||
#include <functional> // std::function
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <typeinfo> // typeid
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "ruc/format/formatter.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
class ASTNode {
|
||||
@@ -28,9 +33,11 @@ public:
|
||||
virtual bool isHashMap() const { return false; }
|
||||
virtual bool isList() const { return false; }
|
||||
virtual bool isString() const { return false; }
|
||||
virtual bool isKeyword() const { return false; }
|
||||
virtual bool isNumber() const { return false; }
|
||||
virtual bool isSpecialSymbol() const { return false; }
|
||||
virtual bool isValue() const { return false; }
|
||||
virtual bool isSymbol() const { return false; }
|
||||
virtual bool isFunction() const { return false; }
|
||||
|
||||
protected:
|
||||
ASTNode() {}
|
||||
@@ -47,6 +54,8 @@ public:
|
||||
void addNode(ASTNode* node);
|
||||
|
||||
const std::vector<ASTNode*>& nodes() const { return m_nodes; }
|
||||
size_t size() const { return m_nodes.size(); }
|
||||
bool empty() const { return m_nodes.size() == 0; }
|
||||
|
||||
protected:
|
||||
Collection() {}
|
||||
@@ -57,6 +66,18 @@ private:
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// ()
|
||||
class List final : public Collection {
|
||||
public:
|
||||
List() = default;
|
||||
virtual ~List() = default;
|
||||
|
||||
virtual bool isCollection() const override { return false; }
|
||||
virtual bool isList() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// []
|
||||
class Vector final : public Collection {
|
||||
public:
|
||||
@@ -70,25 +91,21 @@ public:
|
||||
// -----------------------------------------
|
||||
|
||||
// {}
|
||||
class HashMap final : public Collection {
|
||||
class HashMap final : public ASTNode {
|
||||
public:
|
||||
HashMap() = default;
|
||||
virtual ~HashMap() = default;
|
||||
|
||||
virtual bool isCollection() const override { return false; }
|
||||
virtual bool isHashMap() const override { return true; }
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
void addElement(const std::string& key, ASTNode* value);
|
||||
|
||||
// ()
|
||||
class List final : public Collection {
|
||||
public:
|
||||
List() = default;
|
||||
virtual ~List() = default;
|
||||
const std::unordered_map<std::string, ASTNode*>& elements() const { return m_elements; }
|
||||
size_t size() const { return m_elements.size(); }
|
||||
bool empty() const { return m_elements.size() == 0; }
|
||||
|
||||
virtual bool isCollection() const override { return false; }
|
||||
virtual bool isList() const override { return true; }
|
||||
private:
|
||||
std::unordered_map<std::string, ASTNode*> m_elements;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
@@ -109,6 +126,21 @@ private:
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// :keyword
|
||||
class Keyword final : public ASTNode {
|
||||
public:
|
||||
Keyword(const std::string& data);
|
||||
virtual ~Keyword() = default;
|
||||
|
||||
virtual bool isKeyword() const override { return true; }
|
||||
|
||||
const std::string& keyword() const { return m_data; }
|
||||
|
||||
private:
|
||||
std::string m_data;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
// 123
|
||||
class Number final : public ASTNode {
|
||||
public:
|
||||
@@ -125,21 +157,7 @@ private:
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// true, false, nil
|
||||
class SpecialSymbol final : public ASTNode {
|
||||
public:
|
||||
SpecialSymbol();
|
||||
virtual ~SpecialSymbol();
|
||||
|
||||
virtual bool isSpecialSymbol() const override { return true; }
|
||||
|
||||
private:
|
||||
std::string m_symbol;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Other symbols
|
||||
// Symbols
|
||||
class Symbol final : public ASTNode {
|
||||
public:
|
||||
Symbol(const std::string& symbol);
|
||||
@@ -147,7 +165,7 @@ public:
|
||||
|
||||
virtual bool isSymbol() const override { return true; }
|
||||
|
||||
std::string symbol() const { return m_symbol; }
|
||||
const std::string& symbol() const { return m_symbol; }
|
||||
|
||||
private:
|
||||
std::string m_symbol;
|
||||
@@ -155,10 +173,46 @@ private:
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// true, false, nil
|
||||
class Value final : public ASTNode {
|
||||
public:
|
||||
Value(const std::string& value);
|
||||
virtual ~Value() = default;
|
||||
|
||||
virtual bool isValue() const override { return true; }
|
||||
|
||||
const std::string& value() const { return m_value; }
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
using Lambda = std::function<ASTNode*(std::span<ASTNode*>)>;
|
||||
|
||||
class Function final : public ASTNode {
|
||||
public:
|
||||
Function(Lambda lambda);
|
||||
virtual ~Function() = default;
|
||||
|
||||
virtual bool isFunction() const override { return true; }
|
||||
|
||||
Lambda lambda() const { return m_lambda; }
|
||||
|
||||
private:
|
||||
Lambda m_lambda;
|
||||
};
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// clang-format off
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Collection>() const { return isCollection(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<List>() const { return isList(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Vector>() const { return isVector(); }
|
||||
|
||||
@@ -166,19 +220,29 @@ template<>
|
||||
inline bool ASTNode::fastIs<HashMap>() const { return isHashMap(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<List>() const { return isList(); }
|
||||
inline bool ASTNode::fastIs<String>() const { return isString(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<String>() const { return isString(); }
|
||||
inline bool ASTNode::fastIs<Keyword>() const { return isKeyword(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Number>() const { return isNumber(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<SpecialSymbol>() const { return isSpecialSymbol(); }
|
||||
inline bool ASTNode::fastIs<Symbol>() const { return isSymbol(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Symbol>() const { return isSymbol(); }
|
||||
inline bool ASTNode::fastIs<Value>() const { return isValue(); }
|
||||
|
||||
template<>
|
||||
inline bool ASTNode::fastIs<Function>() const { return isFunction(); }
|
||||
// clang-format on
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
template<>
|
||||
struct ruc::format::Formatter<blaze::ASTNode*> : public Formatter<std::string> {
|
||||
void format(Builder& builder, blaze::ASTNode* value) const;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // int64_t
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "error.h"
|
||||
#include "ruc/format/color.h"
|
||||
#include "ruc/singleton.h"
|
||||
|
||||
#include "ast.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
class Environment {
|
||||
public:
|
||||
Environment() = default;
|
||||
virtual ~Environment() = default;
|
||||
|
||||
ASTNode* lookup(const std::string& symbol)
|
||||
{
|
||||
m_current_key = symbol;
|
||||
return m_values.find(symbol) != m_values.end() ? m_values[symbol] : nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string m_current_key;
|
||||
std::unordered_map<std::string, ASTNode*> m_values;
|
||||
};
|
||||
|
||||
class GlobalEnvironment final : public Environment {
|
||||
public:
|
||||
GlobalEnvironment()
|
||||
{
|
||||
auto add = [](std::span<ASTNode*> nodes) -> ASTNode* {
|
||||
int64_t result = 0;
|
||||
|
||||
for (auto node : nodes) {
|
||||
if (!is<Number>(node)) {
|
||||
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result += static_cast<Number*>(node)->number();
|
||||
}
|
||||
|
||||
return new Number(result);
|
||||
};
|
||||
|
||||
auto sub = [](std::span<ASTNode*> nodes) -> ASTNode* {
|
||||
int64_t result = 0;
|
||||
|
||||
if (nodes.size() == 0) {
|
||||
return new Number(0);
|
||||
}
|
||||
|
||||
for (auto node : nodes) {
|
||||
if (!is<Number>(node)) {
|
||||
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Start with the first number
|
||||
result += static_cast<Number*>(nodes[0])->number();
|
||||
|
||||
// Skip the first node
|
||||
for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) {
|
||||
result -= static_cast<Number*>(*it)->number();
|
||||
}
|
||||
|
||||
return new Number(result);
|
||||
};
|
||||
|
||||
auto mul = [](std::span<ASTNode*> nodes) -> ASTNode* {
|
||||
int64_t result = 1;
|
||||
|
||||
for (auto node : nodes) {
|
||||
if (!is<Number>(node)) {
|
||||
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result *= static_cast<Number*>(node)->number();
|
||||
}
|
||||
|
||||
return new Number(result);
|
||||
};
|
||||
|
||||
auto div = [this](std::span<ASTNode*> nodes) -> ASTNode* {
|
||||
double result = 0;
|
||||
|
||||
if (nodes.size() == 0) {
|
||||
Error::the().addError(format("wrong number of arguments: {}, 0", m_current_key));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto node : nodes) {
|
||||
if (!is<Number>(node)) {
|
||||
Error::the().addError(format("wrong type argument: number-or-marker-p, '{}'", node));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Start with the first number
|
||||
result += static_cast<Number*>(nodes[0])->number();
|
||||
|
||||
// Skip the first node
|
||||
for (auto it = std::next(nodes.begin()); it != nodes.end(); ++it) {
|
||||
result /= static_cast<Number*>(*it)->number();
|
||||
}
|
||||
|
||||
return new Number((int64_t)result);
|
||||
};
|
||||
|
||||
m_values.emplace("+", new Function(add));
|
||||
m_values.emplace("-", new Function(sub));
|
||||
m_values.emplace("*", new Function(mul));
|
||||
m_values.emplace("/", new Function(div));
|
||||
}
|
||||
virtual ~GlobalEnvironment() = default;
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// associative data structure that maps symbols (the keys) to values
|
||||
|
||||
// values = anything, including other symbols.
|
||||
// an environment is like a hash table
|
||||
|
||||
// value can map to:
|
||||
// list
|
||||
// vector
|
||||
// hash-map
|
||||
// symbol
|
||||
// number
|
||||
// string
|
||||
// function
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <span> // std::span
|
||||
|
||||
#include "ast.h"
|
||||
#include "environment.h"
|
||||
#include "eval.h"
|
||||
#include "ruc/meta/assert.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
Eval::Eval(ASTNode* ast, Environment* env)
|
||||
: m_ast(ast)
|
||||
, m_env(env)
|
||||
{
|
||||
}
|
||||
|
||||
void Eval::eval()
|
||||
{
|
||||
m_ast = evalImpl(m_ast, m_env);
|
||||
}
|
||||
|
||||
ASTNode* Eval::evalImpl(ASTNode* ast, Environment* env)
|
||||
{
|
||||
if (!is<List>(ast)) {
|
||||
return evalAst(ast, env);
|
||||
}
|
||||
if (static_cast<List*>(ast)->empty()) {
|
||||
return ast;
|
||||
}
|
||||
|
||||
return apply(static_cast<List*>(evalAst(ast, env)));
|
||||
}
|
||||
|
||||
ASTNode* Eval::evalAst(ASTNode* ast, Environment* env)
|
||||
{
|
||||
if (is<Symbol>(ast)) {
|
||||
auto result = env->lookup(static_cast<Symbol*>(ast)->symbol());
|
||||
if (!result) {
|
||||
Error::the().addError(format("'{}' not found", ast));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (is<List>(ast)) {
|
||||
auto result = new List();
|
||||
auto nodes = static_cast<List*>(ast)->nodes();
|
||||
for (auto node : nodes) {
|
||||
result->addNode(evalImpl(node, env));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (is<Vector>(ast)) {
|
||||
auto result = new Vector();
|
||||
auto nodes = static_cast<Vector*>(ast)->nodes();
|
||||
for (auto node : nodes) {
|
||||
result->addNode(evalImpl(node, env));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (is<HashMap>(ast)) {
|
||||
auto result = new HashMap();
|
||||
auto elements = static_cast<HashMap*>(ast)->elements();
|
||||
for (auto& element : elements) {
|
||||
result->addElement(element.first, evalImpl(element.second, env));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
ASTNode* Eval::apply(List* evaluated_list)
|
||||
{
|
||||
auto nodes = evaluated_list->nodes();
|
||||
|
||||
if (!is<Function>(nodes[0])) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// car
|
||||
auto lambda = static_cast<Function*>(nodes[0])->lambda();
|
||||
// cdr
|
||||
std::span<ASTNode*> span { nodes.data() + 1, nodes.size() - 1 };
|
||||
|
||||
return lambda(span);
|
||||
}
|
||||
|
||||
} // namespace blaze
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ast.h"
|
||||
#include "environment.h"
|
||||
|
||||
namespace blaze {
|
||||
|
||||
class Eval {
|
||||
public:
|
||||
Eval(ASTNode* ast, Environment* env);
|
||||
virtual ~Eval() = default;
|
||||
|
||||
void eval();
|
||||
|
||||
ASTNode* ast() const { return m_ast; }
|
||||
|
||||
private:
|
||||
ASTNode* evalImpl(ASTNode* ast, Environment* env);
|
||||
ASTNode* evalAst(ASTNode* ast, Environment* env);
|
||||
ASTNode* apply(List* evaluated_list);
|
||||
|
||||
ASTNode* m_ast { nullptr };
|
||||
Environment* m_env { nullptr };
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
+69
-18
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string> // std::to_string
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ruc/format/print.h"
|
||||
@@ -38,6 +38,12 @@ void Lexer::tokenize()
|
||||
case '~': // ~@ or ~
|
||||
consumeSpliceUnquoteOrUnquote();
|
||||
break;
|
||||
case '(':
|
||||
m_tokens.push_back({ Token::Type::ParenOpen, m_line, m_column, "(" });
|
||||
break;
|
||||
case ')':
|
||||
m_tokens.push_back({ Token::Type::ParenClose, m_line, m_column, ")" });
|
||||
break;
|
||||
case '[':
|
||||
m_tokens.push_back({ Token::Type::BracketOpen, m_line, m_column, "[" });
|
||||
break;
|
||||
@@ -50,12 +56,6 @@ void Lexer::tokenize()
|
||||
case '}':
|
||||
m_tokens.push_back({ Token::Type::BraceClose, m_line, m_column, "}" });
|
||||
break;
|
||||
case '(':
|
||||
m_tokens.push_back({ Token::Type::ParenOpen, m_line, m_column, "(" });
|
||||
break;
|
||||
case ')':
|
||||
m_tokens.push_back({ Token::Type::ParenClose, m_line, m_column, ")" });
|
||||
break;
|
||||
case '\'':
|
||||
m_tokens.push_back({ Token::Type::Quote, m_line, m_column, "'" });
|
||||
break;
|
||||
@@ -73,6 +73,11 @@ void Lexer::tokenize()
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
if (!consumeKeyword()) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case ';':
|
||||
consumeComment();
|
||||
break;
|
||||
@@ -162,14 +167,28 @@ bool Lexer::consumeString()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lexer::consumeComment()
|
||||
bool Lexer::consumeKeyword()
|
||||
{
|
||||
size_t column = m_column;
|
||||
std::string comment = "";
|
||||
std::string keyword;
|
||||
keyword += 0x7f; // 127
|
||||
|
||||
ignore(); // ;
|
||||
ignore(); // :
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'[',
|
||||
']',
|
||||
'{',
|
||||
'}',
|
||||
'(',
|
||||
')',
|
||||
'\'',
|
||||
'`',
|
||||
',',
|
||||
'"',
|
||||
';',
|
||||
' ',
|
||||
'\t',
|
||||
'\r',
|
||||
'\n',
|
||||
'\0',
|
||||
@@ -183,17 +202,13 @@ bool Lexer::consumeComment()
|
||||
break;
|
||||
}
|
||||
|
||||
comment += character;
|
||||
keyword += character;
|
||||
ignore();
|
||||
}
|
||||
|
||||
// Trim comment
|
||||
comment.erase(comment.begin(),
|
||||
std::find_if(comment.begin(), comment.end(), [](char c) { return !std::isspace(c); }));
|
||||
comment.erase(std::find_if(comment.rbegin(), comment.rend(), [](char c) { return !std::isspace(c); }).base(),
|
||||
comment.end());
|
||||
m_tokens.push_back({ Token::Type::Keyword, m_line, column, keyword });
|
||||
|
||||
m_tokens.push_back({ Token::Type::Comment, m_line, column, comment });
|
||||
retreat();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -201,7 +216,7 @@ bool Lexer::consumeComment()
|
||||
bool Lexer::consumeValue()
|
||||
{
|
||||
size_t column = m_column;
|
||||
std::string value = "";
|
||||
std::string value;
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'[',
|
||||
@@ -241,6 +256,42 @@ bool Lexer::consumeValue()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Lexer::consumeComment()
|
||||
{
|
||||
size_t column = m_column;
|
||||
std::string comment;
|
||||
|
||||
ignore(); // ;
|
||||
|
||||
static std::unordered_set<char> exit = {
|
||||
'\r',
|
||||
'\n',
|
||||
'\0',
|
||||
};
|
||||
|
||||
char character = 0;
|
||||
for (;;) {
|
||||
character = peek();
|
||||
|
||||
if (exit.find(character) != exit.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
comment += character;
|
||||
ignore();
|
||||
}
|
||||
|
||||
// Trim comment
|
||||
comment.erase(comment.begin(),
|
||||
std::find_if(comment.begin(), comment.end(), [](char c) { return !std::isspace(c); }));
|
||||
comment.erase(std::find_if(comment.rbegin(), comment.rend(), [](char c) { return !std::isspace(c); }).base(),
|
||||
comment.end());
|
||||
|
||||
m_tokens.push_back({ Token::Type::Comment, m_line, column, comment });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lexer::dump() const
|
||||
{
|
||||
print("tokens: {}\n", m_tokens.size());
|
||||
|
||||
+6
-45
@@ -20,20 +20,21 @@ struct Token {
|
||||
enum class Type : uint8_t {
|
||||
None,
|
||||
Special, // ~@
|
||||
ParenOpen, // (
|
||||
ParenClose, // )
|
||||
BracketOpen, // [
|
||||
BracketClose, // ]
|
||||
BraceOpen, // {
|
||||
BraceClose, // }
|
||||
ParenOpen, // (
|
||||
ParenClose, // )
|
||||
Quote, // '
|
||||
Backtick, // `
|
||||
Tilde, // ~
|
||||
Caret, // ^
|
||||
At, // @
|
||||
String, // "foobar"
|
||||
Keyword, // :keyword
|
||||
Value, // numbers, "true", "false", and "nil", symbols
|
||||
Comment, // ;
|
||||
Value, // symbols, numbers, "true", "false", and "nil"
|
||||
Error,
|
||||
};
|
||||
|
||||
@@ -58,8 +59,9 @@ public:
|
||||
private:
|
||||
bool consumeSpliceUnquoteOrUnquote(); // ~@ or ~
|
||||
bool consumeString();
|
||||
bool consumeComment();
|
||||
bool consumeKeyword();
|
||||
bool consumeValue();
|
||||
bool consumeComment();
|
||||
|
||||
size_t m_column { 0 };
|
||||
size_t m_line { 0 };
|
||||
@@ -68,44 +70,3 @@ private:
|
||||
};
|
||||
|
||||
} // namespace blaze
|
||||
|
||||
// ~^@
|
||||
// (+ 2 (* 3 4))
|
||||
|
||||
// Lexing -> creates tokens
|
||||
// Parsing -> creates AST
|
||||
|
||||
// class Thing1 {
|
||||
// public:
|
||||
// std::vector<int>& numbers() { return m_numbers; }
|
||||
|
||||
// private:
|
||||
// std::vector<int> m_numbers;
|
||||
// };
|
||||
|
||||
// class Thing2 {
|
||||
// public:
|
||||
// std::vector<int>&& numbers() { return std::move(m_numbers); }
|
||||
|
||||
// private:
|
||||
// std::vector<int> m_numbers;
|
||||
// };
|
||||
|
||||
// class OtherThing {
|
||||
// public:
|
||||
// OtherThing(std::vector<int>&& numbers) noexcept
|
||||
// : m_numbers(std::move(numbers))
|
||||
// {
|
||||
// }
|
||||
|
||||
// private:
|
||||
// std::vector<int> m_numbers;
|
||||
// };
|
||||
|
||||
// int main()
|
||||
// {
|
||||
// Thing1 thing1;
|
||||
// Thing2 thing2;
|
||||
// OtherThing other_thing(std::move(thing1.numbers()));
|
||||
// OtherThing other_thing2(thing2.numbers());
|
||||
// }
|
||||
|
||||
+20
-15
@@ -48,7 +48,19 @@ void Printer::dumpImpl(ASTNode* node)
|
||||
}
|
||||
};
|
||||
|
||||
if (is<Vector>(node)) {
|
||||
if (is<List>(node)) {
|
||||
printSpacing();
|
||||
print("(");
|
||||
m_firstNode = false;
|
||||
m_previousNodeIsList = true;
|
||||
List* list = static_cast<List*>(node);
|
||||
for (size_t i = 0; i < list->nodes().size(); ++i) {
|
||||
dumpImpl(list->nodes()[i]);
|
||||
m_previousNodeIsList = false;
|
||||
}
|
||||
print(")");
|
||||
}
|
||||
else if (is<Vector>(node)) {
|
||||
printSpacing();
|
||||
print("[");
|
||||
m_firstNode = false;
|
||||
@@ -66,28 +78,21 @@ void Printer::dumpImpl(ASTNode* node)
|
||||
m_firstNode = false;
|
||||
m_previousNodeIsList = true;
|
||||
HashMap* hash_map = static_cast<HashMap*>(node);
|
||||
for (size_t i = 0; i < hash_map->nodes().size(); ++i) {
|
||||
dumpImpl(hash_map->nodes()[i]);
|
||||
for (auto element : hash_map->elements()) {
|
||||
print("{} ", element.first.front() == 0x7f ? ":" + element.first.substr(1) : element.first); // 127
|
||||
dumpImpl(element.second);
|
||||
m_previousNodeIsList = false;
|
||||
}
|
||||
print("}}");
|
||||
}
|
||||
else if (is<List>(node)) {
|
||||
printSpacing();
|
||||
print("(");
|
||||
m_firstNode = false;
|
||||
m_previousNodeIsList = true;
|
||||
List* list = static_cast<List*>(node);
|
||||
for (size_t i = 0; i < list->nodes().size(); ++i) {
|
||||
dumpImpl(list->nodes()[i]);
|
||||
m_previousNodeIsList = false;
|
||||
}
|
||||
print(")");
|
||||
}
|
||||
else if (is<String>(node)) {
|
||||
printSpacing();
|
||||
print("{}", static_cast<String*>(node)->data());
|
||||
}
|
||||
else if (is<Keyword>(node)) {
|
||||
printSpacing();
|
||||
print(":{}", static_cast<Keyword*>(node)->keyword().substr(1));
|
||||
}
|
||||
else if (is<Number>(node)) {
|
||||
printSpacing();
|
||||
print("{}", static_cast<Number*>(node)->number());
|
||||
|
||||
+48
-22
@@ -117,14 +117,17 @@ ASTNode* Reader::readImpl()
|
||||
case Token::Type::At: // @
|
||||
return readDeref();
|
||||
break;
|
||||
case Token::Type::String:
|
||||
case Token::Type::String: // "foobar"
|
||||
return readString();
|
||||
break;
|
||||
case Token::Type::Keyword: // :keyword
|
||||
return readKeyword();
|
||||
break;
|
||||
case Token::Type::Comment: // ;
|
||||
ignore();
|
||||
return nullptr;
|
||||
break;
|
||||
case Token::Type::Value:
|
||||
case Token::Type::Value: // true, false, nil
|
||||
return readValue();
|
||||
break;
|
||||
default:
|
||||
@@ -152,6 +155,23 @@ ASTNode* Reader::readSpliceUnquote()
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readList()
|
||||
{
|
||||
ignore(); // (
|
||||
|
||||
List* list = new List();
|
||||
while (!isEOF() && peek().type != Token::Type::ParenClose) {
|
||||
list->addNode(readImpl());
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::ParenClose })) { // )
|
||||
m_error_character = ')';
|
||||
m_is_unbalanced = true;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readVector()
|
||||
{
|
||||
ignore(); // [
|
||||
@@ -173,9 +193,27 @@ ASTNode* Reader::readHashMap()
|
||||
{
|
||||
ignore(); // {
|
||||
|
||||
HashMap* vector = new HashMap();
|
||||
HashMap* hash_map = new HashMap();
|
||||
while (!isEOF() && peek().type != Token::Type::BraceClose) {
|
||||
vector->addNode(readImpl());
|
||||
ASTNode* key = readImpl();
|
||||
ASTNode* value = readImpl();
|
||||
|
||||
if (!key && !value) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!key || !value) {
|
||||
Error::the().addError("hash-map requires an even-sized list");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!is<String>(key) && !is<Keyword>(key)) {
|
||||
Error::the().addError(format("{} is not a string or keyword", key));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string keyString = is<String>(key) ? static_cast<String*>(key)->data() : static_cast<Keyword*>(key)->keyword();
|
||||
hash_map->addElement(keyString, value);
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::BraceClose })) { // }
|
||||
@@ -183,24 +221,7 @@ ASTNode* Reader::readHashMap()
|
||||
m_is_unbalanced = true;
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readList()
|
||||
{
|
||||
ignore(); // (
|
||||
|
||||
List* list = new List();
|
||||
while (!isEOF() && peek().type != Token::Type::ParenClose) {
|
||||
list->addNode(readImpl());
|
||||
}
|
||||
|
||||
if (!consumeSpecific(Token { .type = Token::Type::ParenClose })) { // )
|
||||
m_error_character = ')';
|
||||
m_is_unbalanced = true;
|
||||
}
|
||||
|
||||
return list;
|
||||
return hash_map;
|
||||
}
|
||||
|
||||
ASTNode* Reader::readQuote()
|
||||
@@ -301,6 +322,11 @@ ASTNode* Reader::readString()
|
||||
return new String(symbol);
|
||||
}
|
||||
|
||||
ASTNode* Reader::readKeyword()
|
||||
{
|
||||
return new Keyword(consume().symbol);
|
||||
}
|
||||
|
||||
ASTNode* Reader::readValue()
|
||||
{
|
||||
Token token = consume();
|
||||
|
||||
+3
-2
@@ -36,16 +36,17 @@ private:
|
||||
|
||||
ASTNode* readImpl();
|
||||
ASTNode* readSpliceUnquote(); // ~@
|
||||
ASTNode* readList(); // ()
|
||||
ASTNode* readVector(); // []
|
||||
ASTNode* readHashMap(); // {}
|
||||
ASTNode* readList(); // ()
|
||||
ASTNode* readQuote(); // '
|
||||
ASTNode* readQuasiQuote(); // `
|
||||
ASTNode* readUnquote(); // ~
|
||||
ASTNode* readWithMeta(); // ^
|
||||
ASTNode* readDeref(); // @
|
||||
ASTNode* readString(); // "foobar"
|
||||
ASTNode* readValue();
|
||||
ASTNode* readKeyword(); // :keyword
|
||||
ASTNode* readValue(); // true, false, nil
|
||||
|
||||
void dumpImpl(ASTNode* node);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include "reader.h"
|
||||
#include "settings.h"
|
||||
|
||||
#if 1
|
||||
#if 0
|
||||
auto read(std::string_view input) -> blaze::ASTNode*
|
||||
{
|
||||
blaze::Lexer lexer(input);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#include <csignal> // std::signal
|
||||
#include <cstdlib> // std::exit
|
||||
#include <iostream> // std::cin
|
||||
#include <string> // std::getline
|
||||
#include <string_view>
|
||||
|
||||
#include "environment.h"
|
||||
#include "eval.h"
|
||||
#include "ruc/argparser.h"
|
||||
#include "ruc/format/color.h"
|
||||
|
||||
#include "ast.h"
|
||||
#include "error.h"
|
||||
#include "lexer.h"
|
||||
#include "printer.h"
|
||||
#include "reader.h"
|
||||
#include "settings.h"
|
||||
|
||||
#if 1
|
||||
auto read(std::string_view input) -> blaze::ASTNode*
|
||||
{
|
||||
blaze::Lexer lexer(input);
|
||||
lexer.tokenize();
|
||||
if (blaze::Settings::the().get("dump-lexer") == "1") {
|
||||
lexer.dump();
|
||||
}
|
||||
|
||||
blaze::Reader reader(std::move(lexer.tokens()));
|
||||
reader.read();
|
||||
if (blaze::Settings::the().get("dump-reader") == "1") {
|
||||
reader.dump();
|
||||
}
|
||||
|
||||
return reader.node();
|
||||
}
|
||||
|
||||
auto eval(blaze::ASTNode* ast) -> blaze::ASTNode*
|
||||
{
|
||||
blaze::GlobalEnvironment env;
|
||||
blaze::Eval eval(ast, &env);
|
||||
eval.eval();
|
||||
return eval.ast();
|
||||
}
|
||||
|
||||
auto print(blaze::ASTNode* exp) -> void
|
||||
{
|
||||
blaze::Printer printer(exp);
|
||||
printer.dump();
|
||||
}
|
||||
|
||||
auto rep(std::string_view input) -> void
|
||||
{
|
||||
blaze::Error::the().clearErrors();
|
||||
blaze::Error::the().setInput(input);
|
||||
|
||||
print(eval(read(input)));
|
||||
}
|
||||
|
||||
static auto cleanup(int signal) -> void
|
||||
{
|
||||
print("\033[0m");
|
||||
std::exit(signal);
|
||||
}
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
{
|
||||
bool dump_lexer = false;
|
||||
bool dump_reader = false;
|
||||
bool pretty_print = false;
|
||||
|
||||
// CLI arguments
|
||||
ruc::ArgParser arg_parser;
|
||||
arg_parser.addOption(dump_lexer, 'l', "dump-lexer", nullptr, nullptr);
|
||||
arg_parser.addOption(dump_reader, 'r', "dump-reader", nullptr, nullptr);
|
||||
arg_parser.addOption(pretty_print, 'c', "color", nullptr, nullptr);
|
||||
arg_parser.parse(argc, argv);
|
||||
|
||||
// Set settings
|
||||
blaze::Settings::the().set("dump-lexer", dump_lexer ? "1" : "0");
|
||||
blaze::Settings::the().set("dump-reader", dump_reader ? "1" : "0");
|
||||
blaze::Settings::the().set("pretty-print", pretty_print ? "1" : "0");
|
||||
|
||||
// Signal callbacks
|
||||
std::signal(SIGINT, cleanup);
|
||||
std::signal(SIGTERM, cleanup);
|
||||
|
||||
while (true) {
|
||||
if (!pretty_print) {
|
||||
print("user> ");
|
||||
}
|
||||
else {
|
||||
print(fg(ruc::format::TerminalColor::Blue), "user>");
|
||||
print(" \033[1m");
|
||||
}
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
if (pretty_print) {
|
||||
print("\033[0m");
|
||||
}
|
||||
|
||||
// Exit with Ctrl-D
|
||||
if (std::cin.eof() || std::cin.fail()) {
|
||||
break;
|
||||
}
|
||||
|
||||
rep(line);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// - Add AST node printing support to ruc::format
|
||||
Reference in New Issue
Block a user