diff --git a/CMakeLists.txt b/CMakeLists.txt index ccef595..5e2c9a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ add_library( source/ast/builtin_function_expression.cpp source/ast/callable_expression.cpp source/ast/call_expression.cpp + source/ast/character_literal.cpp source/ast/function_expression.cpp source/ast/hash_literal_expression.cpp source/ast/identifier.cpp diff --git a/source/ast/character_literal.cpp b/source/ast/character_literal.cpp new file mode 100644 index 0000000..a04cde2 --- /dev/null +++ b/source/ast/character_literal.cpp @@ -0,0 +1,11 @@ +#include "character_literal.hpp" + +character_literal::character_literal(char val) + : value(val) +{ +} + +auto character_literal::string() const -> std::string +{ + return {1, value}; +} diff --git a/source/ast/character_literal.hpp b/source/ast/character_literal.hpp new file mode 100644 index 0000000..a4113f3 --- /dev/null +++ b/source/ast/character_literal.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "expression.hpp" + +struct character_literal : expression +{ + explicit character_literal(char val); + [[nodiscard]] auto string() const -> std::string override; + [[nodiscard]] auto eval(environment_ptr env) const -> object override; + auto compile(compiler& comp) const -> void override; + + char value; +}; diff --git a/source/compiler/ast_compile.cpp b/source/compiler/ast_compile.cpp index bf58bf3..ece79e1 100644 --- a/source/compiler/ast_compile.cpp +++ b/source/compiler/ast_compile.cpp @@ -14,6 +14,7 @@ #include #include +#include "ast/character_literal.hpp" #include "compiler.hpp" #include "symbol_table.hpp" @@ -219,3 +220,8 @@ auto call_expression::compile(compiler& comp) const -> void } auto builtin_function_expression::compile(compiler& /*comp*/) const -> void {} + +auto character_literal::compile(compiler& comp) const -> void +{ + comp.emit(opcodes::constant, comp.add_constant({value})); +} diff --git a/source/compiler/compiler.cpp b/source/compiler/compiler.cpp index 215863b..7279a75 100644 --- a/source/compiler/compiler.cpp +++ b/source/compiler/compiler.cpp @@ -215,7 +215,7 @@ auto check_program(std::string_view input) -> parsed_program return {std::move(prgrm), std::move(prsr)}; } -using expected_value = std::variant>; +using expected_value = std::variant>; struct ctc { @@ -240,6 +240,7 @@ auto check_constants(const std::vector& expecteds, const constan std::visit( overloaded { [&](const int64_t val) { CHECK_EQ(val, actual.as()); }, + [&](const char val) { CHECK_EQ(val, actual.as()); }, [&](const std::string& val) { CHECK_EQ(val, actual.as()); }, [&](const std::vector& instrs) { check_instructions(instrs, actual.as().instrs); }, @@ -485,6 +486,23 @@ TEST_CASE("stringExpression") }, }, }; + run(std::move(tests)); +} + +TEST_CASE("characterExpression") +{ + using enum opcodes; + std::array tests { + ctc { + R"('m')", + {{'m'}}, + { + make(constant, 0), + make(pop), + }, + }, + }; + run(std::move(tests)); } TEST_CASE("arrayLiterals") diff --git a/source/eval/ast_eval.cpp b/source/eval/ast_eval.cpp index 3b73a42..926bf7a 100644 --- a/source/eval/ast_eval.cpp +++ b/source/eval/ast_eval.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -32,33 +33,48 @@ auto array_expression::eval(environment_ptr env) const -> object return {result}; } -auto eval_integer_binary_expression(token_type oper, const object& left, const object& right) -> object +auto eval_integer_binary_expression(token_type oper, const int64_t left, const int64_t right) -> object { using enum token_type; - auto left_int = left.as(); - auto right_int = right.as(); switch (oper) { case plus: - return {left_int + right_int}; + return {left + right}; case minus: - return {left_int - right_int}; + return {left - right}; case asterisk: - return {left_int * right_int}; + return {left * right}; case slash: - return {left_int / right_int}; + return {left / right}; case less_than: - return {left_int < right_int}; + return {left < right}; case greater_than: - return {left_int > right_int}; + return {left > right}; case equals: - return {left_int == right_int}; + return {left == right}; case not_equals: - return {left_int != right_int}; + return {left != right}; default: return {}; } } +auto eval_character_binary_expression(token_type oper, const char left, const char right) -> object +{ + using enum token_type; + switch (oper) { + case less_than: + return {left < right}; + case greater_than: + return {left > right}; + case equals: + return {left == right}; + case not_equals: + return {left != right}; + default: + return make_error("unknown operator: character {} character", oper); + } +} + auto eval_string_binary_expression(token_type oper, const object& left, const object& right) -> object { using enum token_type; @@ -85,7 +101,11 @@ auto binary_expression::eval(environment_ptr env) const -> object return make_error("type mismatch: {} {} {}", evaluated_left.type_name(), op, evaluated_right.type_name()); } if (evaluated_left.is() && evaluated_right.is()) { - return eval_integer_binary_expression(op, evaluated_left, evaluated_right); + return eval_integer_binary_expression( + op, evaluated_left.as(), evaluated_right.as()); + } + if (evaluated_left.is() && evaluated_right.is()) { + return eval_character_binary_expression(op, evaluated_left.as(), evaluated_right.as()); } if (evaluated_left.is() && evaluated_right.is()) { return eval_string_binary_expression(op, evaluated_left, evaluated_right); @@ -188,6 +208,17 @@ auto index_expression::eval(environment_ptr env) const -> object } return arr.at(static_cast(index)); } + + if (evaluated_left.is() && evaluated_index.is()) { + auto str = evaluated_left.as(); + auto index = evaluated_index.as(); + auto max = static_cast(str.size() - 1); + if (index < 0 || index > max) { + return {}; + } + return {str.at(static_cast(index))}; + } + if (evaluated_left.is()) { auto hsh = evaluated_left.as(); if (!evaluated_index.is_hashable()) { @@ -351,7 +382,7 @@ const std::vector builtin_function_expression::buil [](const string_type& str) -> object { if (str.length() > 0) { - return {str.substr(0, 1)}; + return {str.at(0)}; } return {}; }, @@ -378,8 +409,8 @@ const std::vector builtin_function_expression::buil overloaded { [](const string_type& str) -> object { - if (str.length() > 1) { - return {str.substr(str.length() - 1, 1)}; + if (str.length() > 0) { + return {str.at(str.length() - 1)}; } return {}; }, @@ -440,6 +471,12 @@ const std::vector builtin_function_expression::buil copy.push_back({obj}); return {copy}; }, + [](const string_type& str, const char obj) -> object + { + auto copy = str; + copy.push_back(obj); + return {copy}; + }, [](const auto& other1, const auto& other2) -> object { return make_error("argument of type {} and {} to push() are not supported", @@ -457,13 +494,18 @@ auto callable_expression::eval(environment_ptr env) const -> object return {std::make_pair(this, env)}; } +auto character_literal::eval(environment_ptr /*env*/) const -> object +{ + return {value}; +} + namespace { // NOLINTBEGIN(*) template auto require_eq(const object& obj, const Expected& expected) -> void { - INFO("expected: ", object {expected}.type_name(), " got: ", obj.type_name()); + INFO("expected: ", object {expected}.type_name(), " got: ", obj.type_name(), " with: ", std::to_string(obj.value)); REQUIRE(obj.is()); const auto& actual = obj.as(); REQUIRE_EQ(actual, expected); @@ -550,6 +592,24 @@ TEST_CASE("integerExpresssion") } } +TEST_CASE("characterExpresssion") +{ + struct ct + { + std::string_view input; + char expected; + }; + + std::array tests { + ct {"'5'", '5'}, + ct {"'a'", 'a'}, + }; + for (const auto& [input, expected] : tests) { + const auto evaluated = run(input); + require_eq(evaluated, expected); + } +} + TEST_CASE("booleanExpresssion") { struct et @@ -569,6 +629,14 @@ TEST_CASE("booleanExpresssion") et {"1 != 1", false}, et {"1 == 2", false}, et {"1 != 2", true}, + et {"'a' < 'b'", true}, + et {"'a' > 'b'", false}, + et {"'a' < 'a'", false}, + et {"'a' > 'a'", false}, + et {"'a' == 'a'", true}, + et {"'a' != 'a'", false}, + et {"'a' == 'b'", false}, + et {"'a' != 'b'", true}, }; for (const auto& [input, expected] : tests) { @@ -590,6 +658,12 @@ TEST_CASE("stringConcatenation") require_eq(evaluated, "Hello World!"); } +TEST_CASE("characterExpression") +{ + auto evaluated = run(R"('H')"); + require_eq(evaluated, 'H'); +} + TEST_CASE("bangOperator") { struct et @@ -603,9 +677,11 @@ TEST_CASE("bangOperator") et {"!false", true}, et {"!false", true}, et {"!5", false}, + et {"!'a'", false}, et {"!!true", true}, et {"!!false", false}, et {"!!5", true}, + et {"!!'a'", true}, }; for (const auto& [input, expected] : tests) { const auto evaluated = run(input); @@ -797,7 +873,7 @@ TEST_CASE("builtinFunctions") struct bt { std::string_view input; - std::variant expected; + std::variant expected; }; std::array tests { @@ -807,33 +883,39 @@ TEST_CASE("builtinFunctions") bt {R"(len(1))", error {"argument of type integer to len() is not supported"}}, bt {R"(len("one", "two"))", error {"wrong number of arguments to len(): expected=1, got=2"}}, bt {R"(len([1,2]))", 2}, - bt {R"(first("abc"))", "a"}, + bt {R"(first("abc"))", 'a'}, bt {R"(first())", error {"wrong number of arguments to first(): expected=1, got=0"}}, bt {R"(first(1))", error {"argument of type integer to first() is not supported"}}, bt {R"(first([1,2]))", 1}, - bt {R"(first([]))", nil_type {}}, - bt {R"(last("abc"))", "c"}, + bt {R"(first([]))", nilv}, + bt {R"(last("abc"))", 'c'}, bt {R"(last())", error {"wrong number of arguments to last(): expected=1, got=0"}}, bt {R"(last(1))", error {"argument of type integer to last() is not supported"}}, bt {R"(last([1,2]))", 2}, - bt {R"(last([]))", nil_type {}}, + bt {R"(last([]))", nilv}, bt {R"(rest("abc"))", "bc"}, + bt {R"(rest("bc"))", "c"}, + bt {R"(rest("c"))", nilv}, bt {R"(rest())", error {"wrong number of arguments to rest(): expected=1, got=0"}}, bt {R"(rest(1))", error {"argument of type integer to rest() is not supported"}}, bt {R"(rest([1,2]))", array {{2}}}, - bt {R"(rest([1]))", nil_type {}}, - bt {R"(rest([]))", nil_type {}}, + bt {R"(rest([1]))", nilv}, + bt {R"(rest([]))", nilv}, bt {R"(push())", error {"wrong number of arguments to push(): expected=2, got=0"}}, bt {R"(push(1))", error {"wrong number of arguments to push(): expected=2, got=1"}}, bt {R"(push(1, 2))", error {"argument of type integer and integer to push() are not supported"}}, bt {R"(push([1,2], 3))", array {{1}, {2}, {3}}}, bt {R"(push([], "abc"))", array {{"abc"}}}, + bt {R"(push("", 'a'))", "a"}, + bt {R"(push("c", 'a'))", "ca"}, }; for (auto test : tests) { auto evaluated = run(test.input); + INFO("with input: `", test.input, "`"); std::visit( overloaded { + [&evaluated](const char val) { require_eq(evaluated, val); }, [&evaluated](const integer_type val) { require_eq(evaluated, val); }, [&evaluated](const error& val) { require_eq(evaluated, val); }, [&evaluated](const std::string& val) { require_eq(evaluated, val); }, @@ -898,17 +980,25 @@ TEST_CASE("indexOperatorExpressions") }, it { "[1, 2, 3][3]", - nil_type {}, + nilv, }, it { "[1, 2, 3][-1]", - nil_type {}, + nilv, + }, + it { + R"("2"[0])", + '2', + }, + it { + R"(""[0])", + nilv, }, }; for (const auto& [input, expected] : tests) { auto evaluated = run(input); - INFO("got: ", evaluated.type_name()); + INFO("got: ", evaluated.type_name(), " with: ", std::to_string(evaluated.value)); CHECK_EQ(object {evaluated.value}, object {expected}); } } @@ -974,6 +1064,10 @@ TEST_CASE("hashIndexExpression") R"({}["foo"])", nilv, }, + ht { + R"({}['5'])", + nilv, + }, ht { R"({5: 5}[5])", 5, @@ -986,6 +1080,10 @@ TEST_CASE("hashIndexExpression") R"({false: 5}[false])", 5, }, + ht { + R"({'a': 5}['a'])", + 5, + }, }; for (const auto& [input, expected] : tests) { auto evaluated = run(input); diff --git a/source/eval/object.cpp b/source/eval/object.cpp index 5e909de..a67a7f1 100644 --- a/source/eval/object.cpp +++ b/source/eval/object.cpp @@ -9,6 +9,7 @@ auto to_string(const hash_key_type& hash_key) { return std::visit(overloaded {[](const integer_type val) -> std::string { return std::to_string(val); }, + [](const char val) -> std::string { return "'" + std::string(1, val) + "'"; }, [](const string_type& val) -> std::string { return "\"" + val + "\""; }, [](const bool val) -> std::string { return val ? "true" : "false"; }, [](const auto&) -> std::string { return "unknown"; }}, @@ -27,6 +28,7 @@ auto to_string(const value_type& value) -> std::string return std::visit( overloaded {[](const nil_type&) -> std::string { return "nil"; }, [](const integer_type val) -> std::string { return to_string(val); }, + [](const char val) -> std::string { return "'" + std::string(1, val) + "'"; }, [](const string_type& val) -> std::string { return "\"" + val + "\""; }, [](const bool val) -> std::string { return val ? "true" : "false"; }, [](const error& val) -> std::string { return "ERROR: " + val.message; }, @@ -72,6 +74,7 @@ auto object::type_name() const -> std::string [](const nil_type&) { return "nil"; }, [](const bool) { return "bool"; }, [](const integer_type) { return "integer"; }, + [](const char) { return "character"; }, [](const string_type&) { return "string"; }, [](const error&) { return "error"; }, [](const bound_function&) { return "function"; }, diff --git a/source/eval/object.hpp b/source/eval/object.hpp index b2661cd..84ca27d 100644 --- a/source/eval/object.hpp +++ b/source/eval/object.hpp @@ -38,7 +38,7 @@ using integer_type = std::int64_t; using string_type = std::string; struct object; using array = std::vector; -using hash_key_type = std::variant; +using hash_key_type = std::variant; using hash = std::unordered_map; struct callable_expression; @@ -61,6 +61,7 @@ struct closure using value_type = std::variant bool { - return is() || is() || is(); + return is() || is() || is() || is(); } [[nodiscard]] inline auto is_truthy() const -> bool { return !is_nil() && (!is() || as()); } @@ -105,6 +106,7 @@ struct object [[nodiscard]] inline auto hash_key() const -> hash_key_type { return std::visit(overloaded {[](const integer_type integer) -> hash_key_type { return {integer}; }, + [](const char chr) -> hash_key_type { return {chr}; }, [](const string_type& str) -> hash_key_type { return {str}; }, [](const bool val) -> hash_key_type { return {val}; }, [](const auto& other) -> hash_key_type { @@ -134,6 +136,7 @@ inline constexpr auto operator==(const object& lhs, const object& rhs) -> bool return std::visit( overloaded {[](const nil_type, const nil_type) { return true; }, [](const bool val1, const bool val2) { return val1 == val2; }, + [](const char val1, const char val2) { return val1 == val2; }, [](const integer_type val1, const integer_type val2) { return val1 == val2; }, [](const string_type& val1, const string_type& val2) { return val1 == val2; }, [](const bound_function& /*val1*/, const bound_function& /*val2*/) { return false; }, diff --git a/source/lexer/lexer.cpp b/source/lexer/lexer.cpp index 00cbf75..9d0c9e7 100644 --- a/source/lexer/lexer.cpp +++ b/source/lexer/lexer.cpp @@ -111,6 +111,9 @@ auto lexer::next_token() -> token if (m_byte == '"') { return read_string(); } + if (m_byte == '\'') { + return read_character(); + } if (is_letter(m_byte)) { return read_identifier_or_keyword(); } @@ -191,12 +194,32 @@ auto lexer::read_string() -> token return read_char(), token {token_type::string, m_input.substr(position, count)}; } +auto lexer::read_character() -> token +{ + const auto position = m_position + 1; + while (true) { + read_char(); + if (m_byte == '\'' || m_byte == '\0') { + break; + } + } + + auto end = m_position; + auto count = end - position; + if (count > 1) { + return read_char(), token {token_type::illegal, m_input.substr(position, count)}; + } + return read_char(), token {token_type::character, m_input.substr(position, count)}; +} + namespace { TEST_CASE("lexing") { using enum token_type; - auto lxr = lexer {R"r(let five = 5; + auto lxr = lexer { + R"( +let five = 5; let ten = 10; let add = fn(x, y) { x + y; @@ -216,7 +239,9 @@ return false; "" [1,2]; {"foo": "bar"} -)r"}; +'c' +'cd' + )"}; std::array expected_tokens { token {let, "let"}, token {ident, "five"}, token {assign, "="}, token {integer, "5"}, token {semicolon, ";"}, token {let, "let"}, token {ident, "ten"}, token {assign, "="}, @@ -239,7 +264,8 @@ return false; token {semicolon, ";"}, token {string, "foobar"}, token {string, "foo bar"}, token {string, ""}, token {lbracket, "["}, token {integer, "1"}, token {comma, ","}, token {integer, "2"}, token {rbracket, "]"}, token {semicolon, ";"}, token {lsquirly, "{"}, token {string, "foo"}, - token {colon, ":"}, token {string, "bar"}, token {rsquirly, "}"}, token {eof, ""}, + token {colon, ":"}, token {string, "bar"}, token {rsquirly, "}"}, token {character, "c"}, + token {illegal, "cd"}, token {eof, ""}, }; for (const auto& expected_token : expected_tokens) { diff --git a/source/lexer/lexer.hpp b/source/lexer/lexer.hpp index c116755..958137c 100644 --- a/source/lexer/lexer.hpp +++ b/source/lexer/lexer.hpp @@ -17,8 +17,9 @@ class lexer final auto read_identifier_or_keyword() -> token; auto read_integer() -> token; auto read_string() -> token; - + auto read_character() -> token; std::string_view m_input; + std::string_view::size_type m_position {0}; std::string_view::size_type m_read_position {0}; std::string_view::value_type m_byte {0}; diff --git a/source/lexer/token_type.cpp b/source/lexer/token_type.cpp index 0afa0cc..440c88c 100644 --- a/source/lexer/token_type.cpp +++ b/source/lexer/token_type.cpp @@ -61,6 +61,8 @@ auto operator<<(std::ostream& ostream, token_type type) -> std::ostream& return ostream << "/"; case token_type::tilde: return ostream << "~"; + case token_type::character: + return ostream << "character"; case token_type::illegal: return ostream << "illegal"; case token_type::eof: diff --git a/source/lexer/token_type.hpp b/source/lexer/token_type.hpp index df9676d..8497871 100644 --- a/source/lexer/token_type.hpp +++ b/source/lexer/token_type.hpp @@ -35,6 +35,7 @@ enum class token_type semicolon, slash, tilde, + character, // two character tokens equals, diff --git a/source/parser/parser.cpp b/source/parser/parser.cpp index 00d6667..68b309c 100644 --- a/source/parser/parser.cpp +++ b/source/parser/parser.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,7 @@ parser::parser(lexer lxr) register_unary(eef, [this] { return parse_if_expression(); }); register_unary(function, [this] { return parse_function_expression(); }); register_unary(string, [this] { return parse_string_literal(); }); + register_unary(character, [this] { return parse_character_literal(); }); register_unary(lbracket, [this] { return parse_array_expression(); }); register_unary(lsquirly, [this] { return parse_hash_literal(); }); register_binary(plus, [this](expression_ptr left) { return parse_binary_expression(std::move(left)); }); @@ -349,6 +351,11 @@ auto parser::parse_string_literal() const -> expression_ptr return std::make_unique(std::string {m_current_token.literal}); } +auto parser::parse_character_literal() const -> expression_ptr +{ + return std::make_unique(m_current_token.literal[0]); +} + auto parser::parse_expression_list(token_type end) -> std::vector { using enum token_type; @@ -978,6 +985,14 @@ TEST_CASE("stringLiteralExpression") REQUIRE_EQ(str->value, "hello world"); } +TEST_CASE("characterLiteralExpression") +{ + const auto* input = R"('a')"; + auto [prgrm, _] = check_program(input); + auto* str = require_expression(prgrm); + REQUIRE_EQ(str->value, 'a'); +} + TEST_CASE("arrayExpression") { auto [prgrm, _] = check_program("[1, 2 * 2, 3 + 3]"); diff --git a/source/parser/parser.hpp b/source/parser/parser.hpp index 76e715c..9fc98fe 100644 --- a/source/parser/parser.hpp +++ b/source/parser/parser.hpp @@ -41,6 +41,7 @@ class parser final auto parse_block_statement() -> block_statement_ptr; auto parse_call_expression(expression_ptr function) -> expression_ptr; auto parse_string_literal() const -> expression_ptr; + auto parse_character_literal() const -> expression_ptr; auto parse_array_expression() -> expression_ptr; auto parse_index_expression(expression_ptr left) -> expression_ptr; auto parse_hash_literal() -> expression_ptr; diff --git a/source/vm/vm.cpp b/source/vm/vm.cpp index 2c5f496..5c8d254 100644 --- a/source/vm/vm.cpp +++ b/source/vm/vm.cpp @@ -251,6 +251,11 @@ auto vm::exec_cmp(opcodes opcode) -> void return; } + if (left.is() && right.is()) { + push({exec_int_cmp(opcode, left.as(), right.as())}); + return; + } + using enum opcodes; switch (opcode) { case equal: @@ -314,25 +319,32 @@ auto exec_hash(const hash& hsh, const hash_key_type& key) -> object auto vm::exec_index(object&& left, object&& index) -> void { - std::visit( - overloaded {[&](const array& arr, int64_t index) - { - auto max = static_cast(arr.size() - 1); - if (index < 0 || index > max) { - return push(nil); - } - return push(arr.at(static_cast(index))); - }, - [&](const hash& hsh, bool index) { push(exec_hash(hsh, {index})); }, - [&](const hash& hsh, int64_t index) { push(exec_hash(hsh, {index})); }, - [&](const hash& hsh, const std::string& index) { push(exec_hash(hsh, {index})); }, - [&](const auto& lft, const auto& idx) - { - throw std::runtime_error(fmt::format( - "invalid index operation: {}:[{}]", object {lft}.type_name(), object {idx}.type_name())); - }}, - left.value, - index.value); + std::visit(overloaded {[&](const array& arr, int64_t index) + { + auto max = static_cast(arr.size() - 1); + if (index < 0 || index > max) { + return push(nil); + } + return push(arr.at(static_cast(index))); + }, + [&](const std::string& str, int64_t index) + { + auto max = static_cast(str.size() - 1); + if (index < 0 || index > max) { + return push(nil); + } + return push({str.at(static_cast(index))}); + }, + [&](const hash& hsh, bool index) { push(exec_hash(hsh, {index})); }, + [&](const hash& hsh, int64_t index) { push(exec_hash(hsh, {index})); }, + [&](const hash& hsh, char index) { push(exec_hash(hsh, {index})); }, + [&](const hash& hsh, const std::string& index) { push(exec_hash(hsh, {index})); }, + [&](const auto& /*lft*/, const auto& /*idx*/) { + throw std::runtime_error(fmt::format( + "invalid index operation: {}:[{}]", left.type_name(), index.type_name())); + }}, + left.value, + index.value); } auto vm::exec_call(size_t num_args) -> void @@ -471,6 +483,7 @@ auto require_eq(const std::variant& expected, const object& actual, std::s std::visit( overloaded { [&](const int64_t exp) { require_is(exp, actual, input); }, + [&](const char exp) { require_is(exp, actual, input); }, [&](const bool exp) { require_is(exp, actual, input); }, [&](const nil_type) { REQUIRE(actual.is_nil()); }, [&](const std::string& exp) { require_is(exp, actual, input); }, @@ -528,11 +541,15 @@ TEST_CASE("booleanExpressions") std::array tests { vt {"true", true}, vt {"false", false}, + vt {"'a' < 'b'", true}, + vt {"'b' < 'a'", false}, vt {"1 < 2", true}, vt {"1 > 2", false}, vt {"1 < 1", false}, vt {"1 > 1", false}, + vt {"'a' == 'a'", true}, vt {"1 == 1", true}, + vt {"'a' != 'a'", false}, vt {"1 != 1", false}, vt {"1 == 2", false}, vt {"1 != 2", true}, @@ -545,8 +562,11 @@ TEST_CASE("booleanExpressions") vt {"(1 < 2) == false", false}, vt {"(1 > 2) == true", false}, vt {"(1 > 2) == false", true}, + vt {"('a' > 'b') == false", true}, vt {"!true", false}, vt {"!false", true}, + vt {"!'a'", false}, + vt {"!!'a'", true}, vt {"!5", false}, vt {"!!true", true}, vt {"!!false", false}, @@ -636,16 +656,20 @@ TEST_CASE("hashLiterals") TEST_CASE("indexExpressions") { std::array tests { - vt {"[1, 2, 3][1]", 2}, - vt {"[1, 2, 3][0 + 2]", 3}, - vt {"[[1, 1, 1]][0][0]", 1}, - vt {"[][0]", nil_type {}}, - vt {"[1, 2, 3][99]", nil_type {}}, - vt {"[1][-1]", nil_type {}}, - vt {"{1: 1, 2: 2}[1]", 1}, - vt {"{1: 1, 2: 2}[2]", 2}, - vt {"{1: 1}[0]", nil_type {}}, - vt {"{}[0]", nil_type {}}, + vt {"[1, 2, 3][1]", 2}, + vt {"[1, 2, 3][0 + 2]", 3}, + vt {"[[1, 1, 1]][0][0]", 1}, + vt {"[][0]", nilv}, + vt {"[1, 2, 3][99]", nilv}, + vt {"[1][-1]", nilv}, + vt {"{1: 1, 2: 2}[1]", 1}, + vt {"{1: 1, 2: 2}[2]", 2}, + vt {"{1: 1}[0]", nilv}, + vt {"{}[0]", nilv}, + vt {"{'a': 5}['a']", 5}, + vt {R"("a"[0])", 'a'}, + vt {R"("ab"[1])", 'b'}, + vt {R"("ab"[2])", nilv}, }; run(std::move(tests)); } @@ -890,19 +914,26 @@ TEST_CASE("callFunctionsWithWrongArgument") TEST_CASE("callBuiltins") { std::array tests { - vt> {R"(len(""))", 0}, - vt> {R"(len("four"))", 4}, - vt> {R"(len("hello world"))", 11}, - vt> {R"(len([1, 2, 3]))", 3}, - vt> {R"(len([]))", 0}, - vt> {R"(puts("hello", "world!"))", nilv}, - vt> {R"(first([1, 2, 3]))", 1}, - vt> {R"(first([]))", nilv}, - vt> {R"(last([]))", nilv}, - vt> {R"(rest([1, 2, 3]))", maker({2, 3})}, - vt> {R"(rest([]))", nilv}, - vt> {R"(push([], 1))", maker({1})}, - vt> {R"(last([1, 2, 3]))", 3}, + vt> {R"(len(""))", 0}, + vt> {R"(len("four"))", 4}, + vt> {R"(len("hello world"))", 11}, + vt> {R"(len([1, 2, 3]))", 3}, + vt> {R"(len([]))", 0}, + vt> {R"(puts("hello", "world!"))", nilv}, + vt> {R"(first([1, 2, 3]))", 1}, + vt> {R"(first("hello"))", 'h'}, + vt> {R"(first([]))", nilv}, + vt> {R"(last([]))", nilv}, + vt> {R"(last(""))", nilv}, + vt> {R"(last("o"))", 'o'}, + vt> {R"(rest([1, 2, 3]))", maker({2, 3})}, + vt> {R"(rest("hello"))", "ello"}, + vt> {R"(rest("lo"))", "o"}, + vt> {R"(rest("o"))", nilv}, + vt> {R"(rest(""))", nilv}, + vt> {R"(rest([]))", nilv}, + vt> {R"(push([], 1))", maker({1})}, + vt> {R"(last([1, 2, 3]))", 3}, }; std::array errortests { vt { @@ -1033,6 +1064,5 @@ TEST_CASE("recuriveFibonnacci") } TEST_SUITE_END(); -} // namespace - // NOLINTEND(*) +} // namespace