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
|
||||
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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
30
src/lexer.cc
30
src/lexer.cc
@ -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();
|
||||
}
|
||||
|
@ -244,6 +244,9 @@ struct Token
|
||||
// like repeat or choose or chord
|
||||
Symbol,
|
||||
|
||||
// like true, false, nil
|
||||
Keyword,
|
||||
|
||||
// like + - ++ < >
|
||||
Operator,
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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");
|
||||
|
@ -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)))));
|
||||
|
12
src/value.cc
12
src/value.cc
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user