Skip to content

Commit e6663fc

Browse files
committed
add f$getopt
1 parent 3c444c5 commit e6663fc

8 files changed

Lines changed: 170 additions & 9 deletions

File tree

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

Lines changed: 2 additions & 2 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;
@@ -118,8 +120,6 @@ function kphp_extended_instance_cache_metrics_init(callable(string $key):string
118120
function register_kphp_on_warning_callback(callable(string $warning_message, string[] $stacktrace):void $stacktrace) ::: void;
119121
function register_kphp_on_oom_callback(callable():void $callback) ::: bool;
120122

121-
/** @kphp-extern-func-info stub */
122-
function getopt ($options ::: string, $longopt ::: array = array(), ?int &$rest_index = null) ::: mixed[] | false;
123123

124124
/** @kphp-extern-func-info stub generation-required */
125125
function profiler_set_function_label($label ::: string) ::: void;

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: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ inline int64_t f$system(const string & /*command*/, int64_t & /*result_code*/ =
2929
php_critical_error("call to unsupported function");
3030
}
3131

32-
inline Optional<array<mixed>> f$getopt(const string & /*options*/, const array<string> & /*longopts*/ = {},
33-
Optional<int64_t> & /*rest_index*/ = SystemInstanceState::get().rest_index_dummy) {
34-
php_critical_error("call to unsupported function");
35-
}
36-
3732
inline int64_t f$numa_get_bound_node() noexcept {
3833
return -1;
3934
}

0 commit comments

Comments
 (0)