44inline double locale_independent_stod(
const std::string& s) {
45 std::istringstream iss(s);
46 iss.imbue(std::locale::classic());
47 double val = std::numeric_limits<double>::quiet_NaN();
48 if (!(iss >> val) || !iss.eof()) {
49 throw std::runtime_error(std::format(
"Failed to parse number: {}", s));
54inline int svtoi(std::string_view sv) {
56 const char* start = sv.data();
57 const char* end = std::next(start,
static_cast<std::ptrdiff_t
>(sv.size()));
59 auto [ptr, ec] = std::from_chars(start, end, val);
60 if (ec == std::errc::invalid_argument) {
61 throw std::invalid_argument(
62 std::format(
"Failed to parse integer from '''{}'''", sv));
64 if (ec == std::errc::result_out_of_range) {
65 throw std::out_of_range(
66 std::format(
"Integer out of range for '''{}'''", sv));
69 throw std::invalid_argument(
70 std::format(
"Invalid integer format '''{}'''", sv));
77constexpr Availability operator|(Availability lhs, Availability rhs) {
78 return static_cast<Availability
>(
static_cast<std::uint8_t
>(lhs) |
79 static_cast<std::uint8_t
>(rhs));
82constexpr Availability operator&(Availability lhs, Availability rhs) {
83 return static_cast<Availability
>(
static_cast<std::uint8_t
>(lhs) &
84 static_cast<std::uint8_t
>(rhs));
87constexpr Availability AVAILABILITY_ALL =
88 Availability::Expr | Availability::SingleExpr | Availability::VkExpr;
90constexpr bool supports_mode(Availability availability,
ExprMode mode) {
92 return static_cast<std::uint8_t
>(availability & Availability::Expr) !=
96 return static_cast<std::uint8_t
>(availability &
97 Availability::SingleExpr) != 0;
100 return static_cast<std::uint8_t
>(availability & Availability::VkExpr) !=
106template <FixedString Str, TokenType Type>
107std::optional<Token> parse_literal(std::string_view input) {
108 if (input == Str.view()) {
110 .text = std::string(input),
111 .payload = std::monostate{}};
116template <FixedString Str, TokenType Type>
119 Availability availability = AVAILABILITY_ALL) {
120 return {.type =
Type,
122 .behavior = behavior,
123 .parser = parse_literal<Str, Type>,
124 .availability = availability};
127constexpr TokenBehavior BEHAVIOR_BINARY{.arity = 2, .stack_effect = -1};
128constexpr TokenBehavior BEHAVIOR_UNARY{.arity = 1, .stack_effect = 0};
129constexpr TokenBehavior BEHAVIOR_ZERO_PUSH{.arity = 0, .stack_effect = 1};
130constexpr TokenBehavior BEHAVIOR_TERNARY{.arity = 3, .stack_effect = -2};
131constexpr TokenBehavior BEHAVIOR_NO_EFFECT{.arity = 0, .stack_effect = 0};
134inline std::optional<Token> parse_plane_width(std::string_view input) {
135 if (
auto m = ctre::match<R
"(^width\^(\d+)$)">(input)) {
136 int plane_idx = svtoi(m.template get<1>().to_view());
138 .text = std::string(input),
144inline std::optional<Token> parse_plane_height(std::string_view input) {
145 if (
auto m = ctre::match<R
"(^height\^(\d+)$)">(input)) {
146 int plane_idx = svtoi(m.template get<1>().to_view());
148 .text = std::string(input),
154inline std::optional<Token> parse_clip_width(std::string_view input) {
155 if (
auto m = ctre::match<R
"(^(?:src(\d+)|([x-za-w])):width$)">(input)) {
157 if (m.template get<1>()) {
158 data.
clip_idx = svtoi(m.template get<1>().to_view());
159 }
else if (m.template get<2>()) {
164 .text = std::string(input),
170inline std::optional<Token> parse_clip_height(std::string_view input) {
171 if (
auto m = ctre::match<R
"(^(?:src(\d+)|([x-za-w])):height$)">(input)) {
173 if (m.template get<1>()) {
174 data.
clip_idx = svtoi(m.template get<1>().to_view());
175 }
else if (m.template get<2>()) {
180 .text = std::string(input),
186inline std::optional<Token> parse_clip_plane_width(std::string_view input) {
188 ctre::match<R
"(^(?:src(\d+)|([x-za-w])):width\^(\d+)$)">(input)) {
190 if (m.template get<1>()) {
191 data.
clip_idx = svtoi(m.template get<1>().to_view());
192 }
else if (m.template get<2>()) {
196 data.
plane_idx = svtoi(m.template get<3>().to_view());
198 .text = std::string(input),
204inline std::optional<Token> parse_clip_plane_height(std::string_view input) {
206 ctre::match<R
"(^(?:src(\d+)|([x-za-w])):height\^(\d+)$)">(input)) {
208 if (m.template get<1>()) {
209 data.
clip_idx = svtoi(m.template get<1>().to_view());
210 }
else if (m.template get<2>()) {
214 data.
plane_idx = svtoi(m.template get<3>().to_view());
216 .text = std::string(input),
222inline std::optional<Token> parse_dup(std::string_view input) {
223 if (
auto m = ctre::match<R
"(^dup(\d*)$)">(input)) {
225 if (m.template get<1>()) {
226 auto digit_sv = m.template get<1>().to_view();
227 if (!digit_sv.empty()) {
232 throw std::runtime_error(
"Invalid dupN value");
235 .text = std::string(input),
241inline std::optional<Token> parse_drop(std::string_view input) {
242 if (
auto m = ctre::match<R
"(^drop(\d*)$)">(input)) {
244 if (m.template get<1>()) {
245 auto digit_sv = m.template get<1>().to_view();
246 if (!digit_sv.empty()) {
251 throw std::runtime_error(
"Invalid dropN value");
254 .text = std::string(input),
260inline std::optional<Token> parse_swap(std::string_view input) {
261 if (
auto m = ctre::match<R
"(^swap(\d*)$)">(input)) {
263 if (m.template get<1>()) {
264 auto digit_sv = m.template get<1>().to_view();
265 if (!digit_sv.empty()) {
270 throw std::runtime_error(
"Invalid swapN value");
273 .text = std::string(input),
279template <FixedString Prefix, TokenType Type>
280inline std::optional<Token> parse_stack_n(std::string_view input) {
281 if (input.starts_with(Prefix.view())) {
282 auto suffix = input.substr(Prefix.view().size());
283 if (
auto m = ctre::match<R
"(^(\d+)$)">(suffix)) {
284 int n = svtoi(m.template get<1>().to_view());
286 throw std::runtime_error(
287 std::format(
"Invalid {}{} value", Prefix.view(), n));
290 .text = std::string(input),
297inline std::optional<Token> parse_label_def(std::string_view input) {
298 if (
auto m = ctre::match<R
"(^#(.+)$)">(input)) {
300 .text = std::string(input),
302 .name = std::string(m.template get<1>().to_view())}};
307inline std::optional<Token> parse_jump(std::string_view input) {
308 if (
auto m = ctre::match<R
"(^(.+)#$)">(input)) {
310 .text = std::string(input),
312 .name = std::string(m.template get<1>().to_view())}};
317inline std::optional<Token> parse_var_store(std::string_view input) {
318 if (
auto m = ctre::match<R
"(^([a-zA-Z_][a-zA-Z0-9_]*)!$)">(input)) {
320 .text = std::string(input),
322 .name = std::string(m.template get<1>().to_view())}};
327inline std::optional<Token> parse_var_load(std::string_view input) {
328 if (
auto m = ctre::match<R
"(^([a-zA-Z_][a-zA-Z0-9_]*)@$)">(input)) {
330 .text = std::string(input),
332 .name = std::string(m.template get<1>().to_view())}};
337inline std::optional<Token> parse_array_alloc_static(std::string_view input) {
339 ctre::match<R
"(^([a-zA-Z_][a-zA-Z0-9_]*)\{\}\^(\d+)$)">(input)) {
340 int static_size = svtoi(m.template get<2>().to_view());
342 .text = std::string(input),
344 .name = std::string(m.template get<1>().to_view()),
345 .static_size = static_size}};
350inline std::optional<Token> parse_array_alloc_dyn(std::string_view input) {
351 if (
auto m = ctre::match<R
"(^([a-zA-Z_][a-zA-Z0-9_]*)\{\}\^$)">(input)) {
353 .text = std::string(input),
355 .name = std::string(m.template get<1>().to_view())}};
360inline std::optional<Token> parse_array_store(std::string_view input) {
361 if (
auto m = ctre::match<R
"(^([a-zA-Z_][a-zA-Z0-9_]*)\{\}!$)">(input)) {
363 .text = std::string(input),
365 .name = std::string(m.template get<1>().to_view())}};
370inline std::optional<Token> parse_array_load(std::string_view input) {
371 if (
auto m = ctre::match<R
"(^([a-zA-Z_][a-zA-Z0-9_]*)\{\}@$)">(input)) {
373 .text = std::string(input),
375 .name = std::string(m.template get<1>().to_view())}};
380inline std::optional<Token> parse_clip_rel(std::string_view input) {
381 if (
auto m = ctre::match<
382 R
"(^(?:src(\d+)|([x-za-w]))\[\s*(-?\d+)\s*,\s*(-?\d+)\s*\](?::([cm]))?$)">(
385 if (m.template get<1>()) {
386 data.
clip_idx = svtoi(m.template get<1>().to_view());
387 }
else if (m.template get<2>()) {
391 data.
rel_x = svtoi(m.template get<3>().to_view());
392 data.
rel_y = svtoi(m.template get<4>().to_view());
395 if (m.template get<5>()) {
397 data.
use_mirror = (m.template get<5>().to_view() ==
"m");
401 .text = std::string(input),
407inline std::optional<Token> parse_clip_abs(std::string_view input) {
408 if (
auto m = ctre::match<R
"(^(?:src(\d+)|([x-za-w]))\[\](?::([mcb]))?$)">(
411 if (m.template get<1>()) {
412 data.
clip_idx = svtoi(m.template get<1>().to_view());
413 }
else if (m.template get<2>()) {
417 if (m.template get<3>()) {
418 char mode_char = m.template get<3>().to_view()[0];
419 if (mode_char ==
'm') {
422 }
else if (mode_char ==
'c') {
425 }
else if (mode_char ==
'b') {
433 .text = std::string(input),
439inline std::optional<Token> parse_clip_cur(std::string_view input) {
440 if (
auto m = ctre::match<R
"(^(?:src(\d+)|([x-za-w]))$)">(input)) {
442 if (m.template get<1>()) {
443 data.
clip_idx = svtoi(m.template get<1>().to_view());
444 }
else if (m.template get<2>()) {
449 .text = std::string(input),
455inline std::optional<Token> parse_prop_access(std::string_view input) {
456 if (
auto m = ctre::match<
457 R
"(^(?:src(\d+)|([x-za-w]))\.([a-zA-Z_][a-zA-Z0-9_]*)$)">(input)) {
459 if (m.template get<1>()) {
460 data.
clip_idx = svtoi(m.template get<1>().to_view());
461 }
else if (m.template get<2>()) {
465 data.
prop_name = std::string(m.template get<3>().to_view());
467 .text = std::string(input),
473inline std::optional<Token> parse_prop_exists(std::string_view input) {
474 if (
auto m = ctre::match<
475 R
"(^(?:src(\d+)|([x-za-w]))\.([a-zA-Z_][a-zA-Z0-9_]*)\?$)">(
478 if (m.template get<1>()) {
479 data.
clip_idx = svtoi(m.template get<1>().to_view());
480 }
else if (m.template get<2>()) {
484 data.
prop_name = std::string(m.template get<3>().to_view());
486 .text = std::string(input),
492inline std::optional<Token> parse_clip_abs_plane(std::string_view input) {
494 ctre::match<R
"(^(?:src(\d+)|([x-za-w]))\^(\d+)\[\]$)">(input)) {
496 if (m.template get<1>()) {
497 data.
clip_idx = svtoi(m.template get<1>().to_view());
498 }
else if (m.template get<2>()) {
502 data.
plane_idx = svtoi(m.template get<3>().to_view());
504 .text = std::string(input),
510inline std::optional<Token> parse_store_abs_plane(std::string_view input) {
511 if (
auto m = ctre::match<R
"(^@\[\]\^(\d+)$)">(input)) {
512 int plane_idx = svtoi(m.template get<1>().to_view());
514 .text = std::string(input),
521inline std::optional<Token> parse_prop_store(std::string_view input) {
522 if (
auto m = ctre::match<R
"(^([a-zA-Z_][a-zA-Z0-9_]*)\$(af|ai|f|i|d)?$)">(
525 if (m.template get<2>()) {
526 auto suffix = m.template get<2>().to_view();
529 }
else if (suffix ==
"f") {
531 }
else if (suffix ==
"ai") {
533 }
else if (suffix ==
"af") {
535 }
else if (suffix ==
"d") {
542 .text = std::string(input),
544 .prop_name = std::string(m.template get<1>().to_view()),
550inline std::optional<Token> parse_buffer_access(std::string_view input) {
551 if (
auto m = ctre::match<
552 R
"(^buf(\d+)(?:(?:(\[\]))|(?:\[\s*(-?\d+)\s*,\s*(-?\d+)\s*\]))?(?::([cmb]))?$)">(
555 data.buffer_idx = svtoi(m.template get<1>().to_view());
559 if (m.template get<2>()) {
561 }
else if (m.template get<3>()) {
563 data.
rel_x = svtoi(m.template get<3>().to_view());
564 data.
rel_y = svtoi(m.template get<4>().to_view());
568 if (m.template get<5>()) {
570 char mode_char = m.template get<5>().to_view()[0];
571 if (mode_char ==
'm') {
574 }
else if (mode_char ==
'c') {
577 }
else if (mode_char ==
'b') {
582 return Token{.type = type, .text = std::string(input), .payload = data};
587inline std::optional<Token> parse_number(std::string_view input) {
588 if (
auto m = ctre::match<
589 R
"(^(?:(0x[0-9a-fA-F]+(?:\.[0-9a-fA-F]+(?:p[+\-]?\d+)?)?)|(0[0-7]+)|([+\-]?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?))$)">(
592 if (m.template get<2>()) {
594 auto sv = m.template get<2>().to_view();
595 auto octal_sv = sv.substr(1);
596 const char* octal_begin = octal_sv.data();
597 const char* octal_end = std::next(
598 octal_begin,
static_cast<std::ptrdiff_t
>(octal_sv.size()));
599 auto res = std::from_chars(
600 octal_begin, octal_end,
603 if (res.ec != std::errc{} || res.ptr != octal_end) {
604 throw std::runtime_error(
605 std::format(
"Failed to parse octal number: {}", sv));
607 val =
static_cast<double>(llval);
609 val = locale_independent_stod(std::string(input));
612 .text = std::string(input),
620 const auto& payload = std::get<TokenPayloadStackOp>(t.
payload);
621 return {.arity = payload.n + 1, .stack_effect = 1};
625 const auto& payload = std::get<TokenPayloadStackOp>(t.
payload);
626 return {.arity = payload.n, .stack_effect = -payload.n};
630 const auto& payload = std::get<TokenPayloadStackOp>(t.
payload);
631 return {.arity = payload.n + 1, .stack_effect = 0};
635 const auto& payload = std::get<TokenPayloadStackOp>(t.
payload);
636 return {.arity = payload.n, .stack_effect = 0};
640 const auto& payload = std::get<TokenPayloadStackOp>(t.
payload);
641 return {.arity = payload.n, .stack_effect = 1 - payload.n};
645 const auto& payload = std::get<TokenPayloadStackOp>(t.
payload);
646 return {.arity = payload.n, .stack_effect = 1 - payload.n};
650 const auto& payload = std::get<TokenPayloadStackOp>(t.
payload);
651 return {.arity = payload.n, .stack_effect = 0};
655 const auto& payload = std::get<TokenPayloadPropStore>(t.
payload);
657 return {.arity = 0, .stack_effect = 0};
659 return {.arity = 1, .stack_effect = -1};
663constexpr auto get_token_definitions() {
684 BEHAVIOR_ZERO_PUSH, Availability::Expr | Availability::VkExpr),
686 BEHAVIOR_ZERO_PUSH, Availability::Expr | Availability::VkExpr),
731 Availability::Expr | Availability::VkExpr),
780 .behavior = BEHAVIOR_ZERO_PUSH,
781 .parser = parse_buffer_access,
782 .availability = Availability::VkExpr},
785 .behavior = BEHAVIOR_ZERO_PUSH,
786 .parser = parse_buffer_access,
787 .availability = Availability::VkExpr},
792 .parser = parse_buffer_access,
793 .availability = Availability::VkExpr},
795 BEHAVIOR_ZERO_PUSH, Availability::Expr | Availability::VkExpr),
799 .name =
"plane_width",
800 .behavior = BEHAVIOR_ZERO_PUSH,
801 .parser = parse_plane_width,
802 .availability = Availability::SingleExpr},
804 .name =
"plane_height",
805 .behavior = BEHAVIOR_ZERO_PUSH,
806 .parser = parse_plane_height,
807 .availability = Availability::SingleExpr},
809 .name =
"clip_width",
810 .behavior = BEHAVIOR_ZERO_PUSH,
811 .parser = parse_clip_width,
812 .availability = Availability::SingleExpr},
814 .name =
"clip_height",
815 .behavior = BEHAVIOR_ZERO_PUSH,
816 .parser = parse_clip_height,
817 .availability = Availability::SingleExpr},
819 .name =
"clip_plane_width",
820 .behavior = BEHAVIOR_ZERO_PUSH,
821 .parser = parse_clip_plane_width,
822 .availability = Availability::SingleExpr},
824 .name =
"clip_plane_height",
825 .behavior = BEHAVIOR_ZERO_PUSH,
826 .parser = parse_clip_plane_height,
827 .availability = Availability::SingleExpr},
832 .availability = AVAILABILITY_ALL},
836 .parser = parse_drop,
837 .availability = AVAILABILITY_ALL},
841 .parser = parse_swap,
842 .availability = AVAILABILITY_ALL},
848 .availability = AVAILABILITY_ALL},
854 .availability = AVAILABILITY_ALL},
860 .availability = AVAILABILITY_ALL},
867 .availability = AVAILABILITY_ALL},
870 .behavior = BEHAVIOR_NO_EFFECT,
871 .parser = parse_label_def,
872 .availability = AVAILABILITY_ALL},
877 .parser = parse_jump,
878 .availability = AVAILABILITY_ALL},
883 .parser = parse_var_store,
884 .availability = AVAILABILITY_ALL},
887 .behavior = BEHAVIOR_ZERO_PUSH,
888 .parser = parse_var_load,
889 .availability = AVAILABILITY_ALL},
891 .name =
"array_alloc_static",
894 .parser = parse_array_alloc_static,
895 .availability = AVAILABILITY_ALL},
897 .name =
"array_alloc_dyn",
900 .parser = parse_array_alloc_dyn,
901 .availability = Availability::SingleExpr},
903 .name =
"array_store",
906 .parser = parse_array_store,
907 .availability = AVAILABILITY_ALL},
909 .name =
"array_load",
912 .parser = parse_array_load,
913 .availability = AVAILABILITY_ALL},
916 .behavior = BEHAVIOR_ZERO_PUSH,
917 .parser = parse_clip_rel,
919 Availability::Expr | Availability::VkExpr},
924 .parser = parse_clip_abs,
925 .availability = AVAILABILITY_ALL},
928 .behavior = BEHAVIOR_ZERO_PUSH,
929 .parser = parse_clip_cur,
931 Availability::Expr | Availability::VkExpr},
933 .name =
"prop_access",
934 .behavior = BEHAVIOR_ZERO_PUSH,
935 .parser = parse_prop_access,
936 .availability = AVAILABILITY_ALL},
938 .name =
"prop_exists",
939 .behavior = BEHAVIOR_ZERO_PUSH,
940 .parser = parse_prop_exists,
941 .availability = AVAILABILITY_ALL},
943 .name =
"clip_abs_plane",
946 .parser = parse_clip_abs_plane,
947 .availability = Availability::SingleExpr},
949 .name =
"store_abs_plane",
952 .parser = parse_store_abs_plane,
953 .availability = Availability::SingleExpr},
955 .name =
"prop_store",
957 .parser = parse_prop_store,
958 .availability = Availability::SingleExpr},
961 .behavior = BEHAVIOR_ZERO_PUSH,
962 .parser = parse_number,
963 .availability = AVAILABILITY_ALL},
969std::vector<Token>
tokenize(
const std::string& expr,
int num_inputs,
970 ExprMode mode,
int num_intermediate_inputs) {
971 std::vector<Token> tokens;
974 auto is_space = [](
char c) {
return std::isspace(c); };
975 auto to_string_view = [](
auto r) {
976 return std::string_view(r.begin(), r.end());
979 constexpr auto TOKEN_DEFS = get_token_definitions();
981 for (
const auto str_token_view :
982 expr | std::views::chunk_by([=](
char a,
char b) {
983 return is_space(a) == is_space(b);
984 }) | std::views::filter([=](
auto r) {
return !is_space(r.front()); }) |
985 std::views::transform(to_string_view)) {
986 std::optional<Token> parsed_token;
988 for (
const auto& definition : TOKEN_DEFS) {
990 if (!supports_mode(definition.availability, mode)) {
994 if ((parsed_token = definition.parser(str_token_view))) {
1000 throw std::runtime_error(std::format(
"Invalid token: {} (idx {})",
1001 std::string(str_token_view),
1009 if (std::get<TokenPayloadClipAccess>(parsed_token->payload)
1011 std::get<TokenPayloadClipAccess>(parsed_token->payload)
1012 .clip_idx >= num_inputs) {
1013 throw std::runtime_error(
1014 std::format(
"Invalid clip index in token: {} (idx {})",
1015 std::string(str_token_view), idx));
1018 if (std::get<TokenPayloadPropAccess>(parsed_token->payload)
1020 std::get<TokenPayloadPropAccess>(parsed_token->payload)
1021 .clip_idx >= num_inputs) {
1022 throw std::runtime_error(
1023 std::format(
"Invalid clip index in token: {} (idx {})",
1024 std::string(str_token_view), idx));
1027 if (std::get<TokenPayloadClipAccessPlane>(parsed_token->payload)
1029 std::get<TokenPayloadClipAccessPlane>(parsed_token->payload)
1030 .clip_idx >= num_inputs) {
1031 throw std::runtime_error(
1032 std::format(
"Invalid clip index in token: {} (idx {})",
1033 std::string(str_token_view), idx));
1038 if (std::get<TokenPayloadBufferAccess>(parsed_token->payload)
1040 std::get<TokenPayloadBufferAccess>(parsed_token->payload)
1041 .buffer_idx >= num_intermediate_inputs) {
1042 throw std::runtime_error(
1043 std::format(
"Invalid buffer index in token: {} (idx {})",
1044 std::string(str_token_view), idx));
1048 tokens.push_back(*parsed_token);
1055 constexpr auto TOKEN_DEFS = get_token_definitions();
1057 const auto* it = std::ranges::find_if(
1058 TOKEN_DEFS, [&](
const auto& def) {
return def.type == token.
type; });
1062 using T = std::decay_t<
decltype(arg)>;
1063 if constexpr (std::is_same_v<T, TokenBehavior>) {
1065 }
else if constexpr (std::is_same_v<T, DynamicBehaviorFn>) {
std::vector< Token > tokenize(const std::string &expr, int num_inputs, ExprMode mode, int num_intermediate_inputs)
TokenBehavior get_token_behavior(const Token &token)
@ ConstantClipPlaneHeight
constexpr int parse_std_clip_idx(char c)
TokenBehavior(*)(const Token &) DynamicBehaviorFn