Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
40 changes: 38 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,48 @@

### Added
- new builtin to extract the bytes of a string: builtin__string:bytes
- math:fromBase to convert a number from a given base to base 10
- string:surrogate? and string:privateUse? to check if an unicode character is in one of those planes
- new builtins `builtin__time:timeToDate` and `builtin__time:parseDate` to convert a timestamp to a date, and parse a string date as a timestamp
- standard library
- math:fromBase to convert a number from a given base to base 10
- string:surrogate? and string:privateUse? to check if a Unicode character is in one of those planes
- list:findAfter to search for an element in a list after a given index
- list:findIf to search for an element in a list using a predicate
- list:search to search for a set of contiguous elements in a list
- list:minMax to get the minimum and maximum values of a list of numbers in a single call
- list:sorted? to check if a list is sorted
- list:lowerBound, list:upperBound
- list:binarySearch
- list:shiftLeft, list:shiftRight
- datetime library
- timezoneOffsets
- timezoneOffset
- makeUTCTimestamp
- timestamps: year0, year1970, year200
- toUTCTimestamp
- asUTCDate, asLocalDate
- plusSeconds, minusSeconds
- plusMinutes, minusMinutes
- plusHours, minusHours
- plusDays, minusDays
- plusWeeks, minusWeeks
- plusMonths, minusMonths
- plusYears, minusYears
- atStartOfDay, atEndOfDay
- today, yesterday, tomorrow
- nextDay, previousDay
- delta, asDelta
- asSeconds
- plusDelta, minusDelta
- year, month, day, hour, minute, second, millisecond, dayOfWeek, dayOfYear
- leapYear?
- monthLength, yearLength
- asISO8601
- parse, parseAs

### Changed
- math:toBase handles 0 correctly
- math:countDigits returns 1 for 0
- builtin__list:find can take an optional third argument, to specify at which index to start looking from

### Removed

Expand Down
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16)

project(ark CXX)
project(ark CXX C)

# ArkScript version (have to manually update it)
set(ARK_VERSION_MAJOR 4)
Expand Down Expand Up @@ -170,6 +170,11 @@ endif ()
add_subdirectory("${ark_SOURCE_DIR}/thirdparties/ankerl_unordered_dense" EXCLUDE_FROM_ALL)
target_link_libraries(ArkReactor PUBLIC unordered_dense::unordered_dense)

add_library(newlib STATIC ${ark_SOURCE_DIR}/thirdparties/newlib/src/gmtime_r.c)
target_include_directories(newlib SYSTEM PUBLIC ${ark_SOURCE_DIR}/thirdparties/newlib/include)
target_include_directories(ArkReactor SYSTEM PUBLIC ${ark_SOURCE_DIR}/thirdparties/newlib/include)
target_link_libraries(ArkReactor PRIVATE newlib)

if (UNIX OR LINUX)
find_package(Threads)
target_link_libraries(ArkReactor PRIVATE ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
Expand Down
2 changes: 2 additions & 0 deletions include/Ark/Builtins/Builtins.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ namespace Ark::internal::Builtins
namespace Time
{
ARK_BUILTIN(timeSinceEpoch);
ARK_BUILTIN(timestampToDate);
ARK_BUILTIN(parseDate);
}

namespace System
Expand Down
2 changes: 1 addition & 1 deletion lib/std
2 changes: 2 additions & 0 deletions src/arkreactor/Builtins/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ namespace Ark::internal::Builtins

// Time
{ "time", Value(Time::timeSinceEpoch) },
{ "builtin__time:timeToDate", Value(Time::timestampToDate) },
{ "builtin__time:parseDate", Value(Time::parseDate) },

// System
{ "builtin__sys:platform", platform },
Expand Down
32 changes: 23 additions & 9 deletions src/arkreactor/Builtins/List.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <fmt/core.h>

#include <Ark/TypeChecker.hpp>
#include <Ark/VM/DefaultValues.hpp>

namespace Ark::internal::Builtins::List
{
Expand All @@ -22,14 +23,25 @@ namespace Ark::internal::Builtins::List

Value findInList(std::vector<Value>& n, VM* vm [[maybe_unused]])
{
if (!types::check(n, ValueType::List, ValueType::Any))
if (!types::check(n, ValueType::List, ValueType::Any) &&
!types::check(n, ValueType::List, ValueType::Any, ValueType::Number))
throw types::TypeCheckingError(
"list:find",
{ { types::Contract { { types::Typedef("list", ValueType::List), types::Typedef("value", ValueType::Any) } } } },
{ { types::Contract {
{ types::Typedef("list", ValueType::List),
types::Typedef("value", ValueType::Any) } },
types::Contract {
{ types::Typedef("list", ValueType::List),
types::Typedef("value", ValueType::Any),
types::Typedef("index", ValueType::Number) } } } },
n);

if (const auto it = std::ranges::find(n[0].list(), n[1]); it != n[0].list().end())
return Value(static_cast<int>(std::distance<decltype(n[0].list().begin())>(n[0].list().begin(), it)));
const long offset = n.size() == 3 ? static_cast<long>(n[2].number()) : 0L;
if (std::cmp_less(offset, n[0].list().size()))
{
if (const auto it = std::ranges::find(n[0].list().begin() + offset, n[0].list().end(), n[1]); it != n[0].list().end())
return Value(static_cast<int>(std::distance<decltype(n[0].list().begin())>(n[0].list().begin() + offset, it) + offset));
}
return Value(-1);
}

Expand All @@ -50,8 +62,9 @@ namespace Ark::internal::Builtins::List
if (!types::check(n, ValueType::Number, ValueType::Any))
throw types::TypeCheckingError(
"list:fill",
{ { types::Contract { { types::Typedef("size", ValueType::Number),
types::Typedef("value", ValueType::Any) } } } },
{ { types::Contract {
{ types::Typedef("size", ValueType::Number),
types::Typedef("value", ValueType::Any) } } } },
n);

const auto c = static_cast<std::size_t>(n[0].number());
Expand All @@ -68,9 +81,10 @@ namespace Ark::internal::Builtins::List
if (!types::check(n, ValueType::List, ValueType::Number, ValueType::Any))
throw types::TypeCheckingError(
"list:setAt",
{ { types::Contract { { types::Typedef("list", ValueType::List),
types::Typedef("index", ValueType::Number),
types::Typedef("value", ValueType::Any) } } } },
{ { types::Contract {
{ types::Typedef("list", ValueType::List),
types::Typedef("index", ValueType::Number),
types::Typedef("value", ValueType::Any) } } } },
n);

auto& list = n[0].list();
Expand Down
93 changes: 92 additions & 1 deletion src/arkreactor/Builtins/Time.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#include <Ark/Builtins/Builtins.hpp>

#include <Ark/VM/DefaultValues.hpp>
#include <Ark/VM/Value/Dict.hpp>
#include <Ark/TypeChecker.hpp>

#undef abs
#include <chrono>
#include <ctime>

#include <Ark/VM/VM.hpp>
#include <newlib/gmtime_r.h>

namespace Ark::internal::Builtins::Time
{
Expand All @@ -15,11 +20,97 @@ namespace Ark::internal::Builtins::Time
* =end
* @author https://github.com/SuperFola
*/
// cppcheck-suppress constParameterReference
Value timeSinceEpoch(std::vector<Value>& n [[maybe_unused]], VM* vm [[maybe_unused]])
{
const auto now = std::chrono::system_clock::now();
const auto epoch = now.time_since_epoch();
const auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(epoch);
return Value(static_cast<double>(microseconds.count()) / 1000000);
}

Value timestampToDate(std::vector<Value>& n, VM* vm [[maybe_unused]])
{
if (!types::check(n, ValueType::Number, ValueType::Any))
throw types::TypeCheckingError(
"timeToDate",
{ { types::Contract {
{ types::Typedef("timestamp", ValueType::Number),
types::Typedef("utc?", ValueType::Any) } } } },
n);

// FIXME: offset time by X seconds?
// const bool is_utc = n[1] == True;

nl_tm calendar_time {};
// FIXME: is_utc ? std::gmtime(&time) : std::localtime(&time);
nl_gmtime_r(static_cast<long>(n[0].number()), &calendar_time);

int week = calendar_time.tm_wday;
if (week == 0) // Sunday
week = 6;
else
--week; // 0-5: Monday-Saturday

internal::Dict dict;
const double ms = n[0].number() - static_cast<double>(static_cast<long>(n[0].number()));

dict.set(Value("millisecond"), Value(static_cast<int>(1000.0 * ms)));
dict.set(Value("second"), Value(calendar_time.tm_sec));
dict.set(Value("minute"), Value(calendar_time.tm_min));
dict.set(Value("hour"), Value(calendar_time.tm_hour));
dict.set(Value("day"), Value(calendar_time.tm_mday));
dict.set(Value("month"), Value(calendar_time.tm_mon + 1));
dict.set(Value("year"), Value(calendar_time.tm_year + 1900));
dict.set(Value("week_day"), Value(week));
dict.set(Value("year_day"), Value(calendar_time.tm_yday));
dict.set(Value("is_dst"), calendar_time.tm_isdst ? True : False);

return Value(std::move(dict));
}

int64_t makeTimestamp(const int tm_sec, const int tm_min, const int tm_hour, const int tm_mday, const int tm_mon, const int tm_year)
{
constexpr int MonthsPerYear = 12;
static const std::array<int, MonthsPerYear> cumulative_days = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

const long year = 1900 + tm_year + tm_mon / MonthsPerYear;
int64_t result = (year - 1970) * 365 + cumulative_days[static_cast<std::size_t>(tm_mon % MonthsPerYear)];
result += (year - 1968) / 4;
result -= (year - 1900) / 100;
result += (year - 1600) / 400;
if ((year % 4) == 0 &&
((year % 100) != 0 || (year % 400) == 0) &&
(tm_mon % MonthsPerYear) < 2)
result--;
result += tm_mday - 1;
result *= 24;
result += tm_hour;
result *= 60;
result += tm_min;
result *= 60;
result += tm_sec;
return result;
}

Value parseDate(std::vector<Value>& n, VM* vm [[maybe_unused]])
{
if (!types::check(n, ValueType::String) && !types::check(n, ValueType::String, ValueType::String))
throw types::TypeCheckingError(
"parseDate",
{ { types::Contract {
{ types::Typedef("date", ValueType::String) } },
types::Contract {
{ types::Typedef("date", ValueType::String),
types::Typedef("format", ValueType::String) } } } },
n);

std::tm t = {};
std::istringstream ss(n[0].string());
ss >> std::get_time(&t, n.size() == 1 ? "%Y-%m-%dT%H:%M:%S" : n[1].string().c_str());

if (ss.fail())
return Nil;
return Value(makeTimestamp(t.tm_sec, t.tm_min, t.tm_hour, t.tm_mday, t.tm_mon, t.tm_year));
}
}
4 changes: 2 additions & 2 deletions tests/unittests/resources/CompilerSuite/ir/99bottles.expected
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ page_0
LOAD_CONST 3
LOAD_FAST 4
LOAD_FAST 4
CALL_BUILTIN 27, 3
CALL_BUILTIN 29, 3
.L7:
CALL_BUILTIN 9, 1
.L6:
Expand All @@ -50,7 +50,7 @@ page_0
PUSH_RETURN_ADDRESS L9
LOAD_CONST 4
LOAD_FAST 4
CALL_BUILTIN 27, 2
CALL_BUILTIN 29, 2
.L9:
CALL_BUILTIN 9, 1
.L8:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ page_1
LOAD_FAST_BY_INDEX 0
LOAD_FAST_BY_INDEX 1
LOAD_FAST_BY_INDEX 2
CALL_BUILTIN 27, 4
CALL_BUILTIN 29, 4
.L1:
CALL_BUILTIN 9, 1
.L0:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
page_0
PUSH_RETURN_ADDRESS L0
BUILTIN 87
BUILTIN 88
BUILTIN 89
BUILTIN 90
BUILTIN 91
Expand All @@ -25,6 +23,8 @@ page_0
BUILTIN 109
BUILTIN 110
BUILTIN 111
BUILTIN 112
BUILTIN 113
CALL_BUILTIN 9, 25
.L0:
POP 0
Expand Down
2 changes: 1 addition & 1 deletion tests/unittests/resources/CompilerSuite/ir/plugin.expected
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ page_0
.L1:
EQ 0
LOAD_CONST 3
CALL_BUILTIN 26, 2
CALL_BUILTIN 28, 2
.L0:
POP 0
HALT 0
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ page_0
LOAD_CONST_STORE 0, 0
LOAD_CONST_STORE 1, 2
LOAD_CONST_STORE 2, 4
BUILTIN 22
BUILTIN 24
STORE 6
STORE_FROM 8, 7
STORE_FROM 10, 9
Expand Down Expand Up @@ -37,7 +37,7 @@ page_0
LOAD_CONST 6
LOAD_FAST 13
LOAD_FAST 13
CALL_BUILTIN 27, 3
CALL_BUILTIN 29, 3
.L10:
CALL_BUILTIN 9, 1
.L9:
Expand All @@ -47,7 +47,7 @@ page_0
PUSH_RETURN_ADDRESS L12
LOAD_CONST 7
LOAD_FAST 13
CALL_BUILTIN 27, 2
CALL_BUILTIN 29, 2
.L12:
CALL_BUILTIN 9, 1
.L11:
Expand All @@ -58,19 +58,19 @@ page_0
HALT 0

page_1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 23, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 25, 1
.L0:
RET 0
HALT 0

page_2
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 24, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 26, 1
.L1:
RET 0
HALT 0

page_3
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 25, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 27, 1
.L2:
RET 0
HALT 0
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ page_0
HALT 0

page_1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 58, 1
CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 60, 1
.L0:
RET 0
HALT 0
Expand Down
Loading
Loading