Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler+runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ add_library(
src/cpp/jtl/panic.cpp
src/cpp/jtl/assert.cpp
src/cpp/jtl/string_builder.cpp
src/cpp/jtl/utf8.cpp
src/cpp/jank/c_api.cpp
src/cpp/jank/hash.cpp
src/cpp/jank/util/cli.cpp
Expand Down
30 changes: 30 additions & 0 deletions compiler+runtime/include/cpp/jtl/utf8.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <jtl/immutable_string.hpp>

namespace jtl
Comment thread
djblue marked this conversation as resolved.
{
struct utf8_iterator
{
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = immutable_string;
using reference = value_type const &;

utf8_iterator() = default;
utf8_iterator(immutable_string const &s);
utf8_iterator(immutable_string const &s, value_type::size_type const position);

value_type operator*() const;
utf8_iterator &operator++();
utf8_iterator operator++(int);
utf8_iterator &operator--();
utf8_iterator operator--(int);
bool operator==(utf8_iterator const &it) const;

utf8_iterator begin() const;
utf8_iterator end() const;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iterators do not have begin/end. Ranges do. These two things should not be conflated.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below, when you use this with a reverse view, what we actually want is utf8_range{ s } | .... So the fix here is to add a new struct utf8_range which holds a string and provides begin/end (and optionally rbegin/rend).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for all the code churn. I'm still learning about iterators + range based for loops. I think I finally landed in a good place. Initially we talked about having two separate iterators forwards/reverse, but it felt better if we could have one iterator that could go forwards and backwards.


value_type data;
value_type::size_type i{};
value_type::size_type n{};
};
}
11 changes: 10 additions & 1 deletion compiler+runtime/src/cpp/clojure/string_native.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#include <ranges>

#include <jtl/utf8.hpp>

#include <clojure/string_native.hpp>
#include <jank/runtime/core.hpp>
#include <jank/runtime/core/call.hpp>
Expand Down Expand Up @@ -26,7 +30,12 @@ namespace clojure::string_native

jtl::immutable_string reverse(jtl::immutable_string const &s)
{
return { s.rbegin(), s.rend() };
Comment thread
djblue marked this conversation as resolved.
jtl::string_builder buff{ s.size() };
for(auto const &c : jtl::utf8_iterator(s) | std::views::reverse)
{
buff(c);
}
return buff.release();
}

jtl::immutable_string lower_case(jtl::immutable_string const &s)
Expand Down
159 changes: 159 additions & 0 deletions compiler+runtime/src/cpp/jtl/utf8.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#include <jtl/utf8.hpp>

namespace jtl
{
using value_type = utf8_iterator::value_type;
using size_type = value_type::size_type;

static size_type next_char_size(immutable_string const &s, size_type const i)
{
auto const c(s[i]);
if(c <= 0x7f)
{
return 1;
}
else if((c & 0xE0) == 0xC0)
{
return 2;
}
else if((c & 0xF0) == 0xE0)
{
return 3;
}
else if((c & 0xF8) == 0xF0)
{
return 4;
}
else
{
throw std::runtime_error{ "Invalid UTF-8 string." };
}
}

static bool is_continuation_byte(immutable_string const &s, size_type const i)
{
if(i == immutable_string::npos)
{
throw std::runtime_error{ "Invalid UTF-8 string." };
}

return (s[i] & 0xC0) == 0x80;
}

static size_type prev_char_size(immutable_string const &data, size_type const i)
{
if(!is_continuation_byte(data, i - 1))
{
return 1;
}
else if(!is_continuation_byte(data, i - 2))
{
return 2;
}
else if(!is_continuation_byte(data, i - 3))
{
return 3;
}
else if(!is_continuation_byte(data, i - 4))
{
return 4;
}
else
{
throw std::runtime_error{ "Invalid UTF-8 string." };
}
}

utf8_iterator::utf8_iterator(immutable_string const &s)
: data{ s }
, i{ s.empty() ? value_type::npos : 0 }
, n{ i == value_type::npos ? 0 : next_char_size(s, i) }
{
}

utf8_iterator::utf8_iterator(immutable_string const &s, value_type::size_type const position)
: data{ s }
, i{ s.empty() ? value_type::npos : position }
, n{ i == value_type::npos ? 0 : next_char_size(s, i) }
{
}

value_type utf8_iterator::operator*() const
{
return data.substr(i, n);
}

utf8_iterator &utf8_iterator::operator++()
{
if(i == value_type::npos)
{
i = 0;
n = next_char_size(data, i);
}
else
{
i += n;
if(i >= data.size())
{
i = value_type::npos;
n = 0;
}
else
{
n = next_char_size(data, i);
}
}

return *this;
}

utf8_iterator utf8_iterator::operator++(int)
{
auto tmp{ *this };
++*this;
return tmp;
}

utf8_iterator &utf8_iterator::operator--()
{
if(i == 0)
{
i = value_type::npos;
n = 0;
}
else if(i == value_type::npos)
{
auto const size(data.size());
n = prev_char_size(data, size);
i = size - n;
}
else
{
n = prev_char_size(data, i);
i -= n;
}
return *this;
}

utf8_iterator utf8_iterator::operator--(int)
{
auto tmp{ *this };
--*this;
return tmp;
}

bool utf8_iterator::operator==(utf8_iterator const &it) const
{
return i == it.i && (data.data() == it.data.data() || data == it.data);
}

utf8_iterator utf8_iterator::begin() const
{
return { data, 0 };
}

utf8_iterator utf8_iterator::end() const
{
return { data, value_type::npos };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@
clojure.string-test.ends-with-qmark
; clojure.string-test.escape ; Uncaught exception: Can't convert character to integer.
clojure.string-test.lower-case
; clojure.string-test.reverse ; issues with unicode
clojure.string-test.reverse
clojure.string-test.starts-with-qmark
clojure.string-test.upper-case
])
Expand Down
Loading