More testing & keywords
This commit is contained in:
parent
c1493a75c2
commit
5b3cfca2a8
2
Makefile
2
Makefile
@ -48,7 +48,7 @@ unit-test-coverage:
|
|||||||
rm -rf coverage
|
rm -rf coverage
|
||||||
mkdir coverage
|
mkdir coverage
|
||||||
gcovr -e '.*\.hpp' --html --html-details -o coverage/index.html
|
gcovr -e '.*\.hpp' --html --html-details -o coverage/index.html
|
||||||
rm -rf bin
|
rm -rf bin/debug
|
||||||
xdg-open coverage/index.html
|
xdg-open coverage/index.html
|
||||||
|
|
||||||
.PHONY: doc
|
.PHONY: doc
|
||||||
|
@ -7,10 +7,6 @@ var x = pair 100 200;
|
|||||||
say (car x);
|
say (car x);
|
||||||
say (cdr x);
|
say (cdr x);
|
||||||
|
|
||||||
-- This two values should be builtin, but currently is not
|
|
||||||
var true = (1 == 1);
|
|
||||||
var false = (1 != 1);
|
|
||||||
|
|
||||||
-- LIST definition
|
-- LIST definition
|
||||||
var null = pair true true;
|
var null = pair true true;
|
||||||
var is_empty = car;
|
var is_empty = car;
|
||||||
|
@ -83,7 +83,7 @@ Result<Value> Interpreter::eval(Ast &&ast)
|
|||||||
case Ast::Type::Literal:
|
case Ast::Type::Literal:
|
||||||
switch (ast.token.type) {
|
switch (ast.token.type) {
|
||||||
case Token::Type::Symbol:
|
case Token::Type::Symbol:
|
||||||
if (ast.token.source != "nil") {
|
{
|
||||||
auto const value = env->find(std::string(ast.token.source));
|
auto const value = env->find(std::string(ast.token.source));
|
||||||
assert(value, "Missing variable error is not implemented yet: variable: "s + std::string(ast.token.source));
|
assert(value, "Missing variable error is not implemented yet: variable: "s + std::string(ast.token.source));
|
||||||
return *value;
|
return *value;
|
||||||
|
30
src/lexer.cc
30
src/lexer.cc
@ -9,6 +9,13 @@ constexpr std::string_view Valid_Operator_Chars =
|
|||||||
"<>=!" // comparisons
|
"<>=!" // comparisons
|
||||||
;
|
;
|
||||||
|
|
||||||
|
constexpr auto Keywords = std::array {
|
||||||
|
"false"sv,
|
||||||
|
"nil"sv,
|
||||||
|
"true"sv,
|
||||||
|
"var"sv
|
||||||
|
};
|
||||||
|
|
||||||
void Lexer::skip_whitespace_and_comments()
|
void Lexer::skip_whitespace_and_comments()
|
||||||
{
|
{
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@ -154,7 +161,11 @@ auto Lexer::next_token() -> Result<Token>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token t = { Token::Type::Symbol, finish(), token_location };
|
Token t = { Token::Type::Symbol, finish(), token_location };
|
||||||
if (t.source == "v") t.type = Token::Type::Operator;
|
if (std::find(Keywords.begin(), Keywords.end(), t.source) != Keywords.end()) {
|
||||||
|
t.type = Token::Type::Keyword;
|
||||||
|
} else if (t.source == "v") {
|
||||||
|
t.type = Token::Type::Operator;
|
||||||
|
}
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,16 +262,17 @@ std::ostream& operator<<(std::ostream& os, Token const& token)
|
|||||||
std::ostream& operator<<(std::ostream& os, Token::Type type)
|
std::ostream& operator<<(std::ostream& os, Token::Type type)
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Token::Type::Open_Block: return os << "OPEN BLOCK";
|
|
||||||
case Token::Type::Close_Block: return os << "CLOSE BLOCK";
|
|
||||||
case Token::Type::Open_Paren: return os << "OPEN PAREN";
|
|
||||||
case Token::Type::Close_Paren: return os << "CLOSE PAREN";
|
|
||||||
case Token::Type::Parameter_Separator: return os << "PARAMETER SEPARATOR";
|
|
||||||
case Token::Type::Chord: return os << "CHORD";
|
case Token::Type::Chord: return os << "CHORD";
|
||||||
case Token::Type::Numeric: return os << "NUMERIC";
|
case Token::Type::Close_Block: return os << "CLOSE BLOCK";
|
||||||
case Token::Type::Symbol: return os << "SYMBOL";
|
case Token::Type::Close_Paren: return os << "CLOSE PAREN";
|
||||||
case Token::Type::Operator: return os << "OPERATOR";
|
|
||||||
case Token::Type::Expression_Separator: return os << "EXPRESSION SEPARATOR";
|
case Token::Type::Expression_Separator: return os << "EXPRESSION SEPARATOR";
|
||||||
|
case Token::Type::Keyword: return os << "KEYWORD";
|
||||||
|
case Token::Type::Numeric: return os << "NUMERIC";
|
||||||
|
case Token::Type::Open_Block: return os << "OPEN BLOCK";
|
||||||
|
case Token::Type::Open_Paren: return os << "OPEN PAREN";
|
||||||
|
case Token::Type::Operator: return os << "OPERATOR";
|
||||||
|
case Token::Type::Parameter_Separator: return os << "PARAMETER SEPARATOR";
|
||||||
|
case Token::Type::Symbol: return os << "SYMBOL";
|
||||||
}
|
}
|
||||||
unreachable();
|
unreachable();
|
||||||
}
|
}
|
||||||
|
@ -244,6 +244,9 @@ struct Token
|
|||||||
// like repeat or choose or chord
|
// like repeat or choose or chord
|
||||||
Symbol,
|
Symbol,
|
||||||
|
|
||||||
|
// like true, false, nil
|
||||||
|
Keyword,
|
||||||
|
|
||||||
// like + - ++ < >
|
// like + - ++ < >
|
||||||
Operator,
|
Operator,
|
||||||
|
|
||||||
|
@ -3,6 +3,12 @@
|
|||||||
|
|
||||||
static Ast wrap_if_several(std::vector<Ast> &&ast, Ast(*wrapper)(std::vector<Ast>));
|
static Ast wrap_if_several(std::vector<Ast> &&ast, Ast(*wrapper)(std::vector<Ast>));
|
||||||
|
|
||||||
|
constexpr auto Literal_Keywords = std::array {
|
||||||
|
"false"sv,
|
||||||
|
"nil"sv,
|
||||||
|
"true"sv,
|
||||||
|
};
|
||||||
|
|
||||||
enum class At_Least : bool
|
enum class At_Least : bool
|
||||||
{
|
{
|
||||||
Zero,
|
Zero,
|
||||||
@ -15,7 +21,6 @@ static Result<std::vector<Ast>> parse_many(
|
|||||||
std::optional<Token::Type> separator,
|
std::optional<Token::Type> separator,
|
||||||
At_Least at_least);
|
At_Least at_least);
|
||||||
|
|
||||||
|
|
||||||
Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
|
Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
|
||||||
{
|
{
|
||||||
Lexer lexer{source};
|
Lexer lexer{source};
|
||||||
@ -55,7 +60,7 @@ Result<Ast> Parser::parse_expression()
|
|||||||
|
|
||||||
Result<Ast> Parser::parse_variable_declaration()
|
Result<Ast> Parser::parse_variable_declaration()
|
||||||
{
|
{
|
||||||
if (!expect(Token::Type::Symbol, "var")) {
|
if (!expect(Token::Type::Keyword, "var")) {
|
||||||
return errors::expected_keyword(Try(peek()), "var");
|
return errors::expected_keyword(Try(peek()), "var");
|
||||||
}
|
}
|
||||||
auto var = consume();
|
auto var = consume();
|
||||||
@ -88,6 +93,11 @@ Result<Ast> Parser::parse_infix_expression()
|
|||||||
Result<Ast> Parser::parse_atomic_expression()
|
Result<Ast> Parser::parse_atomic_expression()
|
||||||
{
|
{
|
||||||
switch (Try(peek_type())) {
|
switch (Try(peek_type())) {
|
||||||
|
case Token::Type::Keyword:
|
||||||
|
if (std::find(Literal_Keywords.begin(), Literal_Keywords.end(), peek()->source) == Literal_Keywords.end()) {
|
||||||
|
return errors::unexpected_token(*peek());
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
case Token::Type::Symbol:
|
case Token::Type::Symbol:
|
||||||
case Token::Type::Numeric:
|
case Token::Type::Numeric:
|
||||||
return Ast::literal(consume());
|
return Ast::literal(consume());
|
||||||
|
@ -51,9 +51,8 @@ suite intepreter_test = [] {
|
|||||||
};
|
};
|
||||||
|
|
||||||
should("call builtin functions") = [] {
|
should("call builtin functions") = [] {
|
||||||
evaluates_to(Value::symbol("nil"), "typeof nil");
|
evaluates_to(Value::symbol("nil"), "typeof nil");
|
||||||
// evaluates_to(Value::symbol("symbol"), "typeof foo");
|
evaluates_to(Value::symbol("number"), "typeof 100");
|
||||||
evaluates_to(Value::symbol("number"), "typeof 100");
|
|
||||||
|
|
||||||
produces_output("say 5", "5\n");
|
produces_output("say 5", "5\n");
|
||||||
produces_output("say 1; say 2; say 3", "1\n2\n3\n");
|
produces_output("say 1; say 2; say 3", "1\n2\n3\n");
|
||||||
|
@ -11,8 +11,52 @@ static std::string str(auto const& printable)
|
|||||||
return std::move(ss).str();
|
return std::move(ss).str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void expect_value(
|
||||||
|
Result<Value> received,
|
||||||
|
Value expected,
|
||||||
|
reflection::source_location sl = reflection::source_location::current())
|
||||||
|
{
|
||||||
|
expect(received.has_value(), sl) << "Received error, instead of value";
|
||||||
|
expect(eq(*received, expected), sl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void either_truthy_or_falsy(
|
||||||
|
bool(Value::*desired)() const,
|
||||||
|
Value v,
|
||||||
|
reflection::source_location sl = reflection::source_location::current())
|
||||||
|
{
|
||||||
|
if (desired == &Value::truthy) {
|
||||||
|
expect(v.truthy(), sl) << "Value " << v << " should be" << "truthy";
|
||||||
|
expect(!v.falsy(), sl) << "Value " << v << " should NOT be" << "falsy";
|
||||||
|
} else {
|
||||||
|
expect(v.falsy(), sl) << "Value " << v << " should be" << "falsy";
|
||||||
|
expect(!v.truthy(), sl) << "Value " << v << " should NOT be" << "truthy";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suite value_test = [] {
|
suite value_test = [] {
|
||||||
"Comparisons"_test = [] {
|
"Value"_test = [] {
|
||||||
|
should("be properly created using Value::from") = [] {
|
||||||
|
expect_value(Value::from({ Token::Type::Numeric, "10", {} }), Value::number(Number(10)));
|
||||||
|
expect_value(Value::from({ Token::Type::Keyword, "nil", {} }), Value{});
|
||||||
|
expect_value(Value::from({ Token::Type::Keyword, "true", {} }), Value::boolean(true));
|
||||||
|
expect_value(Value::from({ Token::Type::Keyword, "false", {} }), Value::boolean(false));
|
||||||
|
expect_value(Value::from({ Token::Type::Symbol, "foobar", {} }), Value::symbol("foobar"));
|
||||||
|
};
|
||||||
|
|
||||||
|
should("have be considered truthy or falsy") = [] {
|
||||||
|
either_truthy_or_falsy(&Value::truthy, Value::boolean(true));
|
||||||
|
either_truthy_or_falsy(&Value::truthy, Value::number(Number(1)));
|
||||||
|
either_truthy_or_falsy(&Value::truthy, Value::symbol("foo"));
|
||||||
|
either_truthy_or_falsy(&Value::truthy, Value::lambda(nullptr));
|
||||||
|
|
||||||
|
either_truthy_or_falsy(&Value::falsy, Value{});
|
||||||
|
either_truthy_or_falsy(&Value::falsy, Value::boolean(false));
|
||||||
|
either_truthy_or_falsy(&Value::falsy, Value::number(Number(0)));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"Value comparisons"_test = [] {
|
||||||
should("be always not equal for lambdas") = [] {
|
should("be always not equal for lambdas") = [] {
|
||||||
expect(neq(Value::lambda(nullptr), Value::lambda(nullptr)));
|
expect(neq(Value::lambda(nullptr), Value::lambda(nullptr)));
|
||||||
};
|
};
|
||||||
@ -24,6 +68,8 @@ suite value_test = [] {
|
|||||||
|
|
||||||
"Value printing"_test = [] {
|
"Value printing"_test = [] {
|
||||||
expect(eq("nil"sv, str(Value{})));
|
expect(eq("nil"sv, str(Value{})));
|
||||||
|
expect(eq("true"sv, str(Value::boolean(true))));
|
||||||
|
expect(eq("false"sv, str(Value::boolean(false))));
|
||||||
|
|
||||||
expect(eq("10"sv, str(Value::number(Number(10)))));
|
expect(eq("10"sv, str(Value::number(Number(10)))));
|
||||||
expect(eq("1/2"sv, str(Value::number(Number(2, 4)))));
|
expect(eq("1/2"sv, str(Value::number(Number(2, 4)))));
|
||||||
|
12
src/value.cc
12
src/value.cc
@ -6,13 +6,13 @@ Result<Value> Value::from(Token t)
|
|||||||
case Token::Type::Numeric:
|
case Token::Type::Numeric:
|
||||||
return Value::number(Try(Number::from(std::move(t))));
|
return Value::number(Try(Number::from(std::move(t))));
|
||||||
|
|
||||||
|
case Token::Type::Keyword:
|
||||||
|
if (t.source == "false") return Value::boolean(false);
|
||||||
|
if (t.source == "nil") return Value{};
|
||||||
|
if (t.source == "true") return Value::boolean(true);
|
||||||
|
unreachable();
|
||||||
|
|
||||||
case Token::Type::Symbol:
|
case Token::Type::Symbol:
|
||||||
if (t.source == "true")
|
|
||||||
return Value::boolean(true);
|
|
||||||
if (t.source == "false")
|
|
||||||
return Value::boolean(false);
|
|
||||||
if (t.source == "nil")
|
|
||||||
return Value{};
|
|
||||||
return Value::symbol(std::string(t.source));
|
return Value::symbol(std::string(t.source));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
Loading…
Reference in New Issue
Block a user