Skip to content

Commit

Permalink
feat: allow for "string" * int multiplication like in python (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
hrzlgnm authored Dec 16, 2024
1 parent 9565a23 commit 530f96b
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 64 deletions.
2 changes: 1 addition & 1 deletion source/ast/builtin_function_expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
builtin_function_expression::builtin_function_expression(
std::string name,
std::vector<std::string> params,
std::function<const object*(array_object::array&& arguments)> bod)
std::function<const object*(array_object::value_type&& arguments)> bod)
: callable_expression {std::move(params)}
, name {std::move(name)}
, body {std::move(bod)}
Expand Down
2 changes: 1 addition & 1 deletion source/ast/builtin_function_expression.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ struct builtin_function_expression : callable_expression
static const std::vector<const builtin_function_expression*> builtins;

std::string name;
std::function<const object*(array_object::array&& arguments)> body;
std::function<const object*(array_object::value_type&& arguments)> body;
};
77 changes: 52 additions & 25 deletions source/eval/ast_eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

auto array_expression::eval(environment* env) const -> const object*
{
array_object::array arr;
array_object::value_type arr;
for (const auto& element : elements) {
const auto* evaluated = element->eval(env);
if (evaluated->is_error()) {
Expand Down Expand Up @@ -95,6 +95,16 @@ auto apply_string_binary_operator(token_type oper, const std::string& left, cons
return {};
}
}

template<typename O>
auto multiply_sequence(const typename O::value_type& values, int64_t count) -> object*
{
typename O::value_type target;
for (int64_t i = 0; i < count; i++) {
std::copy(values.cbegin(), values.cend(), std::back_inserter(target));
}
return make<O>(std::move(target));
}
} // namespace

auto binary_expression::eval(environment* env) const -> const object*
Expand All @@ -121,11 +131,21 @@ auto binary_expression::eval(environment* env) const -> const object*
arr = evaluated_right->as<array_object>();
integer = evaluated_left->as<integer_object>();
}
array_object::array target;
for (int64_t count = 0; count < integer->value; count++) {
std::copy(arr->elements.cbegin(), arr->elements.cend(), std::back_inserter(target));
return multiply_sequence<array_object>(arr->value, integer->value);
}
if ((evaluated_left->is(integer) && evaluated_right->is(string))
|| (evaluated_left->is(string) && evaluated_right->is(integer)) && op == token_type::asterisk)
{
const string_object* str {};
const integer_object* integer {};
if (evaluated_left->is(string)) {
str = evaluated_left->as<string_object>();
integer = evaluated_right->as<integer_object>();
} else {
str = evaluated_right->as<string_object>();
integer = evaluated_left->as<integer_object>();
}
return make<array_object>(std::move(target));
return multiply_sequence<string_object>(str->value, integer->value);
}
if (evaluated_left->type() != evaluated_right->type()) {
return make_error("type mismatch: {} {} {}", evaluated_left->type(), op, evaluated_right->type());
Expand Down Expand Up @@ -181,7 +201,7 @@ auto call_expression::eval(environment* env) const -> const object*

auto hash_literal_expression::eval(environment* env) const -> const object*
{
hash_object::hash result;
hash_object::value_type result;

for (const auto& [key, value] : pairs) {
const auto* eval_key = key->eval(env);
Expand Down Expand Up @@ -236,7 +256,7 @@ auto index_expression::eval(environment* env) const -> const object*
}
using enum object::object_type;
if (evaluated_left->is(array) && evaluated_index->is(integer)) {
const auto& arr = evaluated_left->as<array_object>()->elements;
const auto& arr = evaluated_left->as<array_object>()->value;
auto index = evaluated_index->as<integer_object>()->value;
auto max = static_cast<int64_t>(arr.size() - 1);
if (index < 0 || index > max) {
Expand All @@ -256,7 +276,7 @@ auto index_expression::eval(environment* env) const -> const object*
}

if (evaluated_left->is(hash)) {
const auto& hsh = evaluated_left->as<hash_object>()->pairs;
const auto& hsh = evaluated_left->as<hash_object>()->value;
if (!evaluated_index->is_hashable()) {
return make_error("unusable as hash key: {}", evaluated_index->type());
}
Expand Down Expand Up @@ -361,7 +381,7 @@ auto builtin_function_expression::call(environment* /*closure_env*/,
environment* caller_env,
const std::vector<const expression*>& arguments) const -> const object*
{
array_object::array args;
array_object::value_type args;
std::transform(arguments.cbegin(),
arguments.cend(),
std::back_inserter(args),
Expand All @@ -374,7 +394,7 @@ namespace
const builtin_function_expression builtin_len {
"len",
{"val"},
[](const array_object::array& arguments) -> const object*
[](const array_object::value_type& arguments) -> const object*
{
if (arguments.size() != 1) {
return make_error("wrong number of arguments to len(): expected=1, got={}", arguments.size());
Expand All @@ -386,7 +406,7 @@ const builtin_function_expression builtin_len {
return make<integer_object>(static_cast<int64_t>(str.size()));
}
if (maybe_string_or_array->is(array)) {
const auto& arr = maybe_string_or_array->as<array_object>()->elements;
const auto& arr = maybe_string_or_array->as<array_object>()->value;

return make<integer_object>(static_cast<int64_t>(arr.size()));
}
Expand All @@ -395,7 +415,7 @@ const builtin_function_expression builtin_len {

const builtin_function_expression builtin_puts {"puts",
{"str"},
[](const array_object::array& arguments) -> const object*
[](const array_object::value_type& arguments) -> const object*
{
using enum object::object_type;
for (bool first = true; const auto& arg : arguments) {
Expand All @@ -416,7 +436,7 @@ const builtin_function_expression builtin_puts {"puts",
const builtin_function_expression builtin_first {
"first",
{"arr"},
[](const array_object::array& arguments) -> const object*
[](const array_object::value_type& arguments) -> const object*
{
if (arguments.size() != 1) {
return make_error("wrong number of arguments to first(): expected=1, got={}", arguments.size());
Expand All @@ -431,7 +451,7 @@ const builtin_function_expression builtin_first {
return native_null();
}
if (maybe_string_or_array->is(array)) {
const auto& arr = maybe_string_or_array->as<array_object>()->elements;
const auto& arr = maybe_string_or_array->as<array_object>()->value;
if (!arr.empty()) {
return arr.front();
}
Expand All @@ -443,7 +463,7 @@ const builtin_function_expression builtin_first {
const builtin_function_expression builtin_last {
"last",
{"arr"},
[](const array_object::array& arguments) -> const object*
[](const array_object::value_type& arguments) -> const object*
{
if (arguments.size() != 1) {
return make_error("wrong number of arguments to last(): expected=1, got={}", arguments.size());
Expand All @@ -458,7 +478,7 @@ const builtin_function_expression builtin_last {
return native_null();
}
if (maybe_string_or_array->is(array)) {
const auto& arr = maybe_string_or_array->as<array_object>()->elements;
const auto& arr = maybe_string_or_array->as<array_object>()->value;
if (!arr.empty()) {
return arr.back();
}
Expand All @@ -470,7 +490,7 @@ const builtin_function_expression builtin_last {
const builtin_function_expression builtin_rest {
"rest",
{"arr"},
[](const array_object::array& arguments) -> const object*
[](const array_object::value_type& arguments) -> const object*
{
if (arguments.size() != 1) {
return make_error("wrong number of arguments to rest(): expected=1, got={}", arguments.size());
Expand All @@ -485,9 +505,9 @@ const builtin_function_expression builtin_rest {
return native_null();
}
if (maybe_string_or_array->is(array)) {
const auto& arr = maybe_string_or_array->as<array_object>()->elements;
const auto& arr = maybe_string_or_array->as<array_object>()->value;
if (arr.size() > 1) {
array_object::array rest;
array_object::value_type rest;
std::copy(arr.cbegin() + 1, arr.cend(), std::back_inserter(rest));
return make<array_object>(std::move(rest));
}
Expand All @@ -499,7 +519,7 @@ const builtin_function_expression builtin_rest {
const builtin_function_expression builtin_push {
"push",
{"arr", "val"},
[](const array_object::array& arguments) -> const object*
[](const array_object::value_type& arguments) -> const object*
{
if (arguments.size() != 2) {
return make_error("wrong number of arguments to push(): expected=2, got={}", arguments.size());
Expand All @@ -508,7 +528,7 @@ const builtin_function_expression builtin_push {
const auto& rhs = arguments[1];
using enum object::object_type;
if (lhs->is(array)) {
auto copy = lhs->as<array_object>()->elements;
auto copy = lhs->as<array_object>()->value;
copy.push_back(rhs);
return make<array_object>(std::move(copy));
}
Expand All @@ -522,7 +542,7 @@ const builtin_function_expression builtin_push {
const builtin_function_expression builtin_type {
"type",
{"val"},
[](const array_object::array& arguments) -> const object*
[](const array_object::value_type& arguments) -> const object*
{
if (arguments.size() != 1) {
return make_error("wrong number of arguments to type(): expected=1, got={}", arguments.size());
Expand Down Expand Up @@ -582,7 +602,7 @@ auto require_array_eq(const object* obj, const array& expected, std::string_view
{
INFO(input, " expected: array with: ", expected.size(), "elements got: ", obj->type(), " with: ", obj->inspect());
REQUIRE(obj->is(object::object_type::array));
const auto& actual = obj->as<array_object>()->elements;
const auto& actual = obj->as<array_object>()->value;
REQUIRE(actual.size() == expected.size());
for (auto idx = 0UL; const auto& expected_elem : expected) {
std::visit(
Expand Down Expand Up @@ -725,6 +745,13 @@ TEST_CASE("stringConcatenation")
require_eq(evaluated, std::string("Hello World!"), input);
}

TEST_CASE("stringIntegerMultiplication")
{
auto input = R"("Hello" * 2 + " " + 3 * "World!")";
auto evaluated = run(input);
require_eq(evaluated, std::string("HelloHello World!World!World!"), input);
}

TEST_CASE("bangOperator")
{
struct et
Expand Down Expand Up @@ -1026,7 +1053,7 @@ TEST_CASE("arrayExpression")
const auto evaluated = run(test.input);
INFO("got: " << evaluated->type());
REQUIRE(evaluated->is(object::object_type::array));
const auto& as_arr = evaluated->as<array_object>()->elements;
const auto& as_arr = evaluated->as<array_object>()->value;
require_array_eq(evaluated->as<array_object>(), test.expected, test.input);
}
}
Expand Down Expand Up @@ -1137,7 +1164,7 @@ TEST_CASE("hashLiterals")
expect {true, 5},
expect {false, 6},
};
const auto& as_hash = evaluated->as<hash_object>()->pairs;
const auto& as_hash = evaluated->as<hash_object>()->value;
for (const auto& [key, value] : expected) {
REQUIRE(as_hash.contains(key));
REQUIRE_EQ(value, as_hash.at(key)->as<integer_object>()->value);
Expand Down
20 changes: 10 additions & 10 deletions source/eval/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ auto array_object::inspect() const -> std::string
{
std::ostringstream strm;
strm << "[";
for (bool first = true; const auto* const element : elements) {
for (bool first = true; const auto* const element : value) {
if (!first) {
strm << ", ";
}
Expand All @@ -99,12 +99,12 @@ auto array_object::inspect() const -> std::string

auto array_object::equals_to(const object* other) const -> bool
{
if (!other->is(type()) || other->as<array_object>()->elements.size() != elements.size()) {
if (!other->is(type()) || other->as<array_object>()->value.size() != value.size()) {
return false;
}
const auto& other_elements = other->as<array_object>()->elements;
return std::equal(elements.cbegin(),
elements.cend(),
const auto& other_elements = other->as<array_object>()->value;
return std::equal(value.cbegin(),
value.cend(),
other_elements.cbegin(),
other_elements.cend(),
[](const object* a, const object* b) { return a->equals_to(b); });
Expand All @@ -126,7 +126,7 @@ auto hash_object::inspect() const -> std::string
{
std::ostringstream strm;
strm << "{";
for (bool first = true; const auto& [key, value] : pairs) {
for (bool first = true; const auto& [key, value] : value) {
if (!first) {
strm << ", ";
}
Expand All @@ -140,12 +140,12 @@ auto hash_object::inspect() const -> std::string

auto hash_object::equals_to(const object* other) const -> bool
{
if (!other->is(type()) || other->as<hash_object>()->pairs.size() != pairs.size()) {
if (!other->is(type()) || other->as<hash_object>()->value.size() != value.size()) {
return false;
}
const auto& other_pairs = other->as<hash_object>()->pairs;
return std::all_of(pairs.cbegin(),
pairs.cend(),
const auto& other_pairs = other->as<hash_object>()->value;
return std::all_of(value.cbegin(),
value.cend(),
[other_pairs](const auto& pair)
{
const auto& [key, value] = pair;
Expand Down
Loading

0 comments on commit 530f96b

Please sign in to comment.