Skip to content

Commit 1ef8b49

Browse files
committed
add f$getopt
1 parent 71a0fe7 commit 1ef8b49

File tree

8 files changed

+193
-19
lines changed

8 files changed

+193
-19
lines changed

builtin-functions/kphp-light/stdlib/server-functions.txt

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
function ini_get ($s ::: string): string | false;
66

7+
function getopt ($options ::: string, $longopt ::: array = array(), ?int &$rest_index = null) ::: mixed[] | false;
8+
79
// === Handlers ===================================================================================
810

911
function register_shutdown_function (callable():void $callback) ::: void;
@@ -49,6 +51,17 @@ function ip2ulong ($ip ::: string) ::: string | false;
4951

5052
function long2ip ($ip ::: int) ::: string;
5153

54+
// Dummy realization without caching
55+
/** @kphp-extern-func-info cpp_template_call */
56+
function instance_cache_fetch(string $type, string $key, bool $even_if_expired = false) ::: instance<^1>;
57+
58+
function instance_cache_store(string $key, object $value, int $ttl = 0) ::: bool;
59+
60+
function instance_cache_update_ttl(string $key, int $ttl = 0) ::: bool;
61+
62+
function instance_cache_delete(string $key) ::: bool;
63+
64+
5265
// === Memory =====================================================================================
5366

5467
function estimate_memory_usage($value ::: any) ::: int;
@@ -75,16 +88,6 @@ function ini_set ($s ::: string, $v ::: string) ::: bool;
7588

7689

7790

78-
/** @kphp-extern-func-info cpp_template_call */
79-
function instance_cache_fetch(string $type, string $key, bool $even_if_expired = false) ::: instance<^1>;
80-
81-
function instance_cache_store(string $key, object $value, int $ttl = 0) ::: bool;
82-
/** @kphp-extern-func-info stub generation-required */
83-
function instance_cache_update_ttl(string $key, int $ttl = 0) ::: bool;
84-
/** @kphp-extern-func-info stub generation-required */
85-
function instance_cache_delete(string $key) ::: bool;
86-
87-
8891
/** @kphp-extern-func-info stub generation-required */
8992
function thread_pool_test_load($size ::: int, $n ::: int, $a ::: float, $b ::: float) ::: float;
9093
/** @kphp-extern-func-info stub generation-required */
@@ -118,8 +121,6 @@ function register_kphp_on_warning_callback(callable(string $warning_message, str
118121
function register_kphp_on_oom_callback(callable():void $callback) ::: bool;
119122

120123

121-
function getopt ($options ::: string, $longopt ::: array = array(), ?int &$rest_index = null) ::: mixed[] | false;
122-
123124
/** @kphp-extern-func-info stub generation-required */
124125
function profiler_set_function_label($label ::: string) ::: void;
125126

runtime-light/state/component-state.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ void ComponentState::parse_ini_arg(std::string_view key_view, std::string_view v
4444
ini_opts.set_value(key_str, value_str);
4545
}
4646

47+
void ComponentState::parse_cli_arg(std::string_view key_view, std::string_view value_view) noexcept {
48+
if (!key_view.starts_with(CLI_ARG_PREFIX)) [[unlikely]] {
49+
php_warning("wrong cli argument format %s", key_view.data());
50+
return;
51+
}
52+
53+
string key_str{std::next(key_view.data(), CLI_ARG_PREFIX.size()), static_cast<string::size_type>(key_view.size() - CLI_ARG_PREFIX.size())};
54+
key_str.set_reference_counter_to(ExtraRefCnt::for_global_const);
55+
56+
string value_str{value_view.data(), static_cast<string::size_type>(value_view.size())};
57+
value_str.set_reference_counter_to(ExtraRefCnt::for_global_const);
58+
59+
cli_opts.set_value(key_str, value_str);
60+
}
61+
4762
void ComponentState::parse_runtime_config_arg(std::string_view value_view) noexcept {
4863
// FIXME: actually no need to allocate string here
4964
auto [config, ok]{json_decode(string{value_view.data(), static_cast<string::size_type>(value_view.size())})};
@@ -62,6 +77,8 @@ void ComponentState::parse_args() noexcept {
6277

6378
if (key_view.starts_with(INI_ARG_PREFIX)) {
6479
parse_ini_arg(key_view, value_view);
80+
} else if (key_view.starts_with(CLI_ARG_PREFIX)) {
81+
parse_cli_arg(key_view, value_view);
6582
} else if (key_view == RUNTIME_CONFIG_ARG) {
6683
parse_runtime_config_arg(value_view);
6784
} else {
@@ -70,4 +87,5 @@ void ComponentState::parse_args() noexcept {
7087
}
7188
runtime_config.set_reference_counter_to(ExtraRefCnt::for_global_const);
7289
ini_opts.set_reference_counter_to(ExtraRefCnt::for_global_const);
90+
cli_opts.set_reference_counter_to(ExtraRefCnt::for_global_const);
7391
}

runtime-light/state/component-state.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ struct ComponentState final : private vk::not_copyable {
1919
const uint32_t argc;
2020
mixed runtime_config;
2121
array<string> ini_opts;
22+
array<mixed> cli_opts;
2223
const uint32_t envc;
2324
array<string> env;
2425

2526
ComponentState() noexcept
2627
: allocator(INIT_COMPONENT_ALLOCATOR_SIZE, 0)
2728
, argc(k2::args_count())
2829
, ini_opts(array_size{argc, false}) /* overapproximation */
30+
, cli_opts(array_size{argc, false})
2931
, envc(k2::env_count())
3032
, env(array_size{envc, false}) {
3133
parse_env();
@@ -42,6 +44,7 @@ struct ComponentState final : private vk::not_copyable {
4244

4345
private:
4446
static constexpr std::string_view INI_ARG_PREFIX = "ini ";
47+
static constexpr std::string_view CLI_ARG_PREFIX = "cli ";
4548
static constexpr std::string_view RUNTIME_CONFIG_ARG = "runtime-config";
4649
static constexpr auto INIT_COMPONENT_ALLOCATOR_SIZE = static_cast<size_t>(512U * 1024U); // 512KB
4750

@@ -51,5 +54,7 @@ struct ComponentState final : private vk::not_copyable {
5154

5255
void parse_ini_arg(std::string_view, std::string_view) noexcept;
5356

57+
void parse_cli_arg(std::string_view, std::string_view) noexcept;
58+
5459
void parse_runtime_config_arg(std::string_view) noexcept;
5560
};

runtime-light/state/init-functions.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "runtime-light/state/init-functions.h"
66

77
#include <cinttypes>
8+
#include <string_view>
89
#include <cstdint>
910

1011
#include "runtime-common/core/utils/kphp-assert-core.h"
@@ -14,13 +15,56 @@
1415
#include "runtime-light/server/http/init-functions.h"
1516
#include "runtime-light/server/init-functions.h"
1617
#include "runtime-light/server/job-worker/job-worker-server-state.h"
18+
#include "runtime-light/state/component-state.h"
1719
#include "runtime-light/state/instance-state.h"
1820
#include "runtime-light/streams/streams.h"
1921
#include "runtime-light/tl/tl-core.h"
2022
#include "runtime-light/tl/tl-functions.h"
2123

2224
namespace {
2325

26+
array<mixed> format_cli_argv() {
27+
constexpr std::string_view INI_ARG_PHP_PREFIX = "-D";
28+
constexpr std::string_view SHORT_ARG_PHP_PREFIX = "-";
29+
constexpr std::string_view LONG_ARG_PHP_PREFIX = "--";
30+
constexpr std::string_view RUNTIME_CONFIG_PHP_PREFIX = "--runtime_config";
31+
32+
array<mixed> argv;
33+
34+
const auto &component_state{ComponentState::get()};
35+
argv.reserve(component_state.argc * 2, false);
36+
37+
for (const auto &ini_opt : component_state.ini_opts) {
38+
argv.push_back(string{INI_ARG_PHP_PREFIX.data()});
39+
argv.push_back(ini_opt.get_key());
40+
41+
const auto &value{ini_opt.get_value()};
42+
if (!value.empty()) {
43+
argv.push_back(string{"="});
44+
argv.push_back(value);
45+
}
46+
}
47+
48+
for (const auto &cli_opt : component_state.cli_opts) {
49+
const bool is_short_option = cli_opt.get_key().as_string().size() == 1;
50+
argv.push_back((is_short_option ? string{SHORT_ARG_PHP_PREFIX.data()} : string{LONG_ARG_PHP_PREFIX.data()}).append(cli_opt.get_key()));
51+
52+
const auto &value{cli_opt.get_value()};
53+
if (!value.empty()) {
54+
argv.push_back(string{"="});
55+
argv.push_back(value);
56+
}
57+
}
58+
59+
if (!component_state.runtime_config.empty()) {
60+
argv.push_back(string{RUNTIME_CONFIG_PHP_PREFIX.data()});
61+
argv.push_back(string{"="});
62+
argv.push_back(component_state.runtime_config);
63+
}
64+
65+
return argv;
66+
}
67+
2468
void process_k2_invoke_http(tl::TLBuffer &tlb) noexcept {
2569
tl::K2InvokeHttp invoke_http{};
2670
if (!invoke_http.fetch(tlb)) {
@@ -44,8 +88,8 @@ task_t<uint64_t> init_kphp_cli_component() noexcept {
4488
{ // TODO superglobals init
4589
auto &superglobals{InstanceState::get().php_script_mutable_globals_singleton.get_superglobals()};
4690
using namespace PhpServerSuperGlobalIndices;
47-
superglobals.v$argc = static_cast<int64_t>(0);
48-
superglobals.v$argv = array<mixed>{};
91+
superglobals.v$argv = format_cli_argv();
92+
superglobals.v$argc = superglobals.v$argv.as_array().size().size;
4993
superglobals.v$_SERVER.set_value(string{ARGC.data(), ARGC.size()}, superglobals.v$argc);
5094
superglobals.v$_SERVER.set_value(string{ARGV.data(), ARGV.size()}, superglobals.v$argv);
5195
superglobals.v$_SERVER.set_value(string{PHP_SELF.data(), PHP_SELF.size()}, string{});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Compiler for PHP (aka KPHP)
2+
// Copyright (c) 2025 LLC «V Kontakte»
3+
// Distributed under the GPL v3 License, see LICENSE.notice.txt
4+
5+
#include <cctype>
6+
#include <cstdint>
7+
#include <functional>
8+
#include <string_view>
9+
10+
#include "runtime-common/core/runtime-core.h"
11+
#include "runtime-light/state/instance-state.h"
12+
#include "runtime-light/stdlib/server/args-functions.h"
13+
14+
Optional<array<mixed>> f$getopt(const string &short_options, const array<string> &long_options,
15+
[[maybe_unused]] Optional<std::optional<std::reference_wrapper<string>>> rest_index) noexcept {
16+
if (const auto &instance_st{InstanceState::get()}; instance_st.image_kind() != ImageKind::CLI) [[unlikely]] {
17+
return false;
18+
}
19+
20+
enum class option_kind : uint8_t { flag, required, optional };
21+
22+
const auto &cli_opts{ComponentState::get().cli_opts};
23+
array<mixed> options;
24+
25+
std::string_view short_options_view{short_options.c_str(), short_options.size()};
26+
// parse short options
27+
for (size_t pos = 0; pos < short_options_view.size(); ++pos) {
28+
if (!std::isalnum(short_options_view[pos])) {
29+
continue;
30+
}
31+
const string option{1, short_options_view[pos]};
32+
33+
option_kind kind{option_kind::flag};
34+
// check that char followed by a colon
35+
if (pos + 1 < short_options_view.size() && short_options_view[pos + 1] == ':') {
36+
kind = option_kind::required;
37+
pos++;
38+
39+
// check that char followed by a two colon
40+
if (pos + 1 < short_options_view.size() && short_options_view[pos + 1] == ':') {
41+
kind = option_kind::optional;
42+
pos++;
43+
}
44+
}
45+
46+
if (!cli_opts.has_key(option)) [[unlikely]] {
47+
// option has not been set
48+
continue;
49+
}
50+
51+
const mixed &value{cli_opts.get_value(option)};
52+
if (kind == option_kind::optional) {
53+
options.set_value(option, value.empty() ? false : value);
54+
} else if (kind == option_kind::required && !value.empty()) {
55+
options.set_value(option, value);
56+
} else if (kind == option_kind::flag) {
57+
options.set_value(option, false);
58+
}
59+
}
60+
61+
// parse long options
62+
for (const auto &long_option : long_options) {
63+
const std::string_view option_view{long_option.get_value().c_str(), long_option.get_value().size()};
64+
uint8_t offset{};
65+
66+
option_kind kind{option_kind::flag};
67+
if (option_view.ends_with("::")) {
68+
kind = option_kind::optional;
69+
offset = 2;
70+
} else if (option_view.ends_with(":")) {
71+
kind = option_kind::required;
72+
offset = 1;
73+
}
74+
const string option{option_view.data(), static_cast<string::size_type>(option_view.size() - offset)};
75+
76+
if (!cli_opts.has_key(option)) [[unlikely]] {
77+
// option has not been set
78+
continue;
79+
}
80+
81+
const mixed &value{cli_opts.get_value(option)};
82+
if (kind == option_kind::optional) {
83+
options.set_value(option, value.empty() ? false : value);
84+
} else if (kind == option_kind::required && !value.empty()) {
85+
options.set_value(option, value);
86+
} else if (kind == option_kind::flag) {
87+
options.set_value(option, false);
88+
}
89+
}
90+
91+
return options;
92+
}

runtime-light/stdlib/server/args-functions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44

55
#pragma once
66

7+
#include <functional>
8+
#include <optional>
9+
710
#include "runtime-common/core/runtime-core.h"
811
#include "runtime-light/state/component-state.h"
912

1013
inline Optional<string> f$ini_get(const string &key) noexcept {
1114
const auto &component_st{ComponentState::get()};
1215
return component_st.ini_opts.has_key(key) ? Optional<string>{component_st.ini_opts.get_value(key)} : Optional<string>{false};
1316
}
17+
18+
Optional<array<mixed>> f$getopt(const string &short_options, const array<string> &long_options = {},
19+
Optional<std::optional<std::reference_wrapper<string>>> rest_index = {}) noexcept;

runtime-light/stdlib/stdlib.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ prepend(
2222
rpc/rpc-tl-query.cpp
2323
rpc/rpc-tl-request.cpp
2424
serialization/serialization-state.cpp
25+
server/args-functions.cpp
2526
server/http-functions.cpp
2627
string/regex-functions.cpp
2728
string/regex-state.cpp

runtime-light/stdlib/system/system-functions.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
#pragma once
66

7+
#include <chrono>
78
#include <cstdint>
89

910
#include "runtime-common/core/runtime-core.h"
1011
#include "runtime-common/core/utils/kphp-assert-core.h"
12+
#include "runtime-light/coroutine/awaitable.h"
13+
#include "runtime-light/coroutine/task.h"
1114
#include "runtime-light/state/image-state.h"
1215
#include "runtime-light/stdlib/system/system-state.h"
1316

@@ -25,11 +28,6 @@ inline int64_t f$system(const string & /*command*/, int64_t & /*result_code*/ =
2528
php_critical_error("call to unsupported function");
2629
}
2730

28-
inline Optional<array<mixed>> f$getopt(const string & /*options*/, const array<string> & /*longopts*/ = {},
29-
Optional<int64_t> & /*rest_index*/ = SystemInstanceState::get().rest_index_dummy) {
30-
php_critical_error("call to unsupported function");
31-
}
32-
3331
inline int64_t f$numa_get_bound_node() noexcept {
3432
return -1;
3533
}
@@ -62,6 +60,15 @@ inline string f$php_uname(const string &mode = string{1, 'a'}) noexcept {
6260

6361
Optional<string> f$iconv(const string &input_encoding, const string &output_encoding, const string &input_str) noexcept;
6462

63+
inline task_t<void> f$usleep(int64_t microseconds) noexcept {
64+
if (microseconds <= 0) [[unlikely]] {
65+
php_warning("Value of microseconds (%" PRIi64 ") must be positive", microseconds);
66+
co_return;
67+
}
68+
const std::chrono::milliseconds sleep_time{microseconds * 1000};
69+
co_await wait_for_timer_t{sleep_time};
70+
}
71+
6572
inline array<array<string>> f$debug_backtrace() noexcept {
6673
php_warning("called stub debug_backtrace");
6774
return {};

0 commit comments

Comments
 (0)