More testing & keywords

This commit is contained in:
Robert Bendun 2022-05-22 04:31:31 +02:00
parent c1493a75c2
commit 5b3cfca2a8
9 changed files with 93 additions and 27 deletions

View File

@ -48,7 +48,7 @@ unit-test-coverage:
rm -rf coverage
mkdir coverage
gcovr -e '.*\.hpp' --html --html-details -o coverage/index.html
rm -rf bin
rm -rf bin/debug
xdg-open coverage/index.html
.PHONY: doc

View File

@ -7,10 +7,6 @@ var x = pair 100 200;
say (car x);
say (cdr x);
-- This two values should be builtin, but currently is not
var true = (1 == 1);
var false = (1 != 1);
-- LIST definition
var null = pair true true;
var is_empty = car;

View File

@ -83,7 +83,7 @@ Result<Value> Interpreter::eval(Ast &&ast)
case Ast::Type::Literal:
switch (ast.token.type) {
case Token::Type::Symbol:
if (ast.token.source != "nil") {
{
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));
return *value;

View File

@ -9,6 +9,13 @@ constexpr std::string_view Valid_Operator_Chars =
"<>=!" // comparisons
;
constexpr auto Keywords = std::array {
"false"sv,
"nil"sv,
"true"sv,
"var"sv
};
void Lexer::skip_whitespace_and_comments()
{
for (;;) {
@ -154,7 +161,11 @@ auto Lexer::next_token() -> Result<Token>
}
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;
}
@ -251,16 +262,17 @@ std::ostream& operator<<(std::ostream& os, Token const& token)
std::ostream& operator<<(std::ostream& os, Token::Type 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::Numeric: return os << "NUMERIC";
case Token::Type::Symbol: return os << "SYMBOL";
case Token::Type::Operator: return os << "OPERATOR";
case Token::Type::Close_Block: return os << "CLOSE BLOCK";
case Token::Type::Close_Paren: return os << "CLOSE PAREN";
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();
}

View File

@ -244,6 +244,9 @@ struct Token
// like repeat or choose or chord
Symbol,
// like true, false, nil
Keyword,
// like + - ++ < >
Operator,

View File

@ -3,6 +3,12 @@
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
{
Zero,
@ -15,7 +21,6 @@ static Result<std::vector<Ast>> parse_many(
std::optional<Token::Type> separator,
At_Least at_least);
Result<Ast> Parser::parse(std::string_view source, std::string_view filename)
{
Lexer lexer{source};
@ -55,7 +60,7 @@ Result<Ast> Parser::parse_expression()
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");
}
auto var = consume();
@ -88,6 +93,11 @@ Result<Ast> Parser::parse_infix_expression()
Result<Ast> Parser::parse_atomic_expression()
{
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::Numeric:
return Ast::literal(consume());

View File

@ -51,9 +51,8 @@ suite intepreter_test = [] {
};
should("call builtin functions") = [] {
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("nil"), "typeof nil");
evaluates_to(Value::symbol("number"), "typeof 100");
produces_output("say 5", "5\n");
produces_output("say 1; say 2; say 3", "1\n2\n3\n");

View File

@ -11,8 +11,52 @@ static std::string str(auto const& printable)
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 = [] {
"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") = [] {
expect(neq(Value::lambda(nullptr), Value::lambda(nullptr)));
};
@ -24,6 +68,8 @@ suite value_test = [] {
"Value printing"_test = [] {
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("1/2"sv, str(Value::number(Number(2, 4)))));

View File

@ -6,13 +6,13 @@ Result<Value> Value::from(Token t)
case Token::Type::Numeric:
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:
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));
default: