From 5b3cfca2a87ca581d73af608358d98f558297e31 Mon Sep 17 00:00:00 2001 From: Robert Bendun Date: Sun, 22 May 2022 04:31:31 +0200 Subject: [PATCH] More testing & keywords --- Makefile | 2 +- examples/church.mq | 4 ---- src/interpreter.cc | 2 +- src/lexer.cc | 30 +++++++++++++++++-------- src/musique.hh | 3 +++ src/parser.cc | 14 ++++++++++-- src/tests/interpreter.cc | 5 ++--- src/tests/value.cc | 48 +++++++++++++++++++++++++++++++++++++++- src/value.cc | 12 +++++----- 9 files changed, 93 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index e349e9e..64cd86c 100644 --- a/Makefile +++ b/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 diff --git a/examples/church.mq b/examples/church.mq index 69d860d..a45b088 100644 --- a/examples/church.mq +++ b/examples/church.mq @@ -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; diff --git a/src/interpreter.cc b/src/interpreter.cc index de8e499..4b72372 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -83,7 +83,7 @@ Result 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; diff --git a/src/lexer.cc b/src/lexer.cc index e77cf95..75f6d76 100644 --- a/src/lexer.cc +++ b/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 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(); } diff --git a/src/musique.hh b/src/musique.hh index d1c1782..f95d7e6 100644 --- a/src/musique.hh +++ b/src/musique.hh @@ -244,6 +244,9 @@ struct Token // like repeat or choose or chord Symbol, + // like true, false, nil + Keyword, + // like + - ++ < > Operator, diff --git a/src/parser.cc b/src/parser.cc index b5e4161..e3fb8f7 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -3,6 +3,12 @@ static Ast wrap_if_several(std::vector &&ast, Ast(*wrapper)(std::vector)); +constexpr auto Literal_Keywords = std::array { + "false"sv, + "nil"sv, + "true"sv, +}; + enum class At_Least : bool { Zero, @@ -15,7 +21,6 @@ static Result> parse_many( std::optional separator, At_Least at_least); - Result Parser::parse(std::string_view source, std::string_view filename) { Lexer lexer{source}; @@ -55,7 +60,7 @@ Result Parser::parse_expression() Result 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 Parser::parse_infix_expression() Result 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()); diff --git a/src/tests/interpreter.cc b/src/tests/interpreter.cc index eaa238d..5bfc9ee 100644 --- a/src/tests/interpreter.cc +++ b/src/tests/interpreter.cc @@ -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"); diff --git a/src/tests/value.cc b/src/tests/value.cc index dd440c0..ad99066 100644 --- a/src/tests/value.cc +++ b/src/tests/value.cc @@ -11,8 +11,52 @@ static std::string str(auto const& printable) return std::move(ss).str(); } +static void expect_value( + Result 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))))); diff --git a/src/value.cc b/src/value.cc index 410a1db..dfd4e98 100644 --- a/src/value.cc +++ b/src/value.cc @@ -6,13 +6,13 @@ Result 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: