@fugood/llama.node 1.4.6 → 1.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/lib/binding.ts +8 -0
  2. package/package.json +15 -15
  3. package/scripts/llama.cpp.patch +25 -26
  4. package/src/LlamaContext.cpp +2 -2
  5. package/src/llama.cpp/common/CMakeLists.txt +2 -0
  6. package/src/llama.cpp/common/arg.cpp +364 -193
  7. package/src/llama.cpp/common/arg.h +43 -2
  8. package/src/llama.cpp/common/chat-parser-xml-toolcall.cpp +36 -18
  9. package/src/llama.cpp/common/chat-parser-xml-toolcall.h +1 -1
  10. package/src/llama.cpp/common/chat-parser.cpp +3 -2
  11. package/src/llama.cpp/common/chat-peg-parser.cpp +16 -2
  12. package/src/llama.cpp/common/chat.cpp +272 -0
  13. package/src/llama.cpp/common/common.cpp +130 -67
  14. package/src/llama.cpp/common/common.h +40 -16
  15. package/src/llama.cpp/common/console.cpp +680 -47
  16. package/src/llama.cpp/common/console.h +30 -8
  17. package/src/llama.cpp/common/download.cpp +69 -25
  18. package/src/llama.cpp/common/json-schema-to-grammar.cpp +132 -3
  19. package/src/llama.cpp/common/json-schema-to-grammar.h +20 -0
  20. package/src/llama.cpp/common/log.cpp +5 -0
  21. package/src/llama.cpp/common/log.h +1 -0
  22. package/src/llama.cpp/common/peg-parser.cpp +1 -1
  23. package/src/llama.cpp/common/preset.cpp +206 -0
  24. package/src/llama.cpp/common/preset.h +32 -0
  25. package/src/llama.cpp/common/sampling.cpp +91 -92
  26. package/src/llama.cpp/common/sampling.h +11 -6
  27. package/src/llama.cpp/common/speculative.cpp +1 -1
  28. package/src/llama.cpp/ggml/CMakeLists.txt +5 -0
  29. package/src/llama.cpp/ggml/include/ggml-alloc.h +9 -0
  30. package/src/llama.cpp/ggml/include/ggml-backend.h +1 -0
  31. package/src/llama.cpp/ggml/include/ggml-cpu.h +1 -0
  32. package/src/llama.cpp/ggml/include/ggml.h +7 -8
  33. package/src/llama.cpp/ggml/src/CMakeLists.txt +3 -0
  34. package/src/llama.cpp/ggml/src/ggml-cpu/CMakeLists.txt +3 -0
  35. package/src/llama.cpp/ggml/src/ggml-cpu/arch/arm/repack.cpp +2 -0
  36. package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.c +69 -39
  37. package/src/llama.cpp/ggml/src/ggml-cpu/ggml-cpu.cpp +4 -0
  38. package/src/llama.cpp/ggml/src/ggml-cpu/repack.cpp +2 -1
  39. package/src/llama.cpp/include/llama.h +18 -1
  40. package/src/llama.cpp/src/CMakeLists.txt +2 -1
  41. package/src/llama.cpp/src/llama-arch.cpp +1890 -2248
  42. package/src/llama.cpp/src/llama-arch.h +9 -2
  43. package/src/llama.cpp/src/llama-batch.cpp +12 -2
  44. package/src/llama.cpp/src/llama-batch.h +4 -2
  45. package/src/llama.cpp/src/llama-context.cpp +99 -29
  46. package/src/llama.cpp/src/llama-context.h +9 -3
  47. package/src/llama.cpp/src/llama-grammar.cpp +233 -33
  48. package/src/llama.cpp/src/llama-grammar.h +20 -1
  49. package/src/llama.cpp/src/llama-graph.cpp +85 -17
  50. package/src/llama.cpp/src/llama-graph.h +17 -4
  51. package/src/llama.cpp/src/llama-hparams.cpp +6 -0
  52. package/src/llama.cpp/src/llama-hparams.h +5 -1
  53. package/src/llama.cpp/src/llama-impl.cpp +4 -0
  54. package/src/llama.cpp/src/llama-kv-cache.cpp +90 -42
  55. package/src/llama.cpp/src/llama-kv-cache.h +19 -2
  56. package/src/llama.cpp/src/llama-memory-hybrid.cpp +1 -1
  57. package/src/llama.cpp/src/llama-model-loader.cpp +2 -0
  58. package/src/llama.cpp/src/llama-model-loader.h +2 -0
  59. package/src/llama.cpp/src/llama-model.cpp +123 -52
  60. package/src/llama.cpp/src/llama-model.h +1 -0
  61. package/src/llama.cpp/src/llama-quant.cpp +1 -1
  62. package/src/llama.cpp/src/llama-vocab.cpp +2 -1
  63. package/src/llama.cpp/src/llama.cpp +675 -1
  64. package/src/llama.cpp/src/models/deepseek2.cpp +9 -5
  65. package/src/llama.cpp/src/models/{gemma3-iswa.cpp → gemma3.cpp} +30 -5
  66. package/src/llama.cpp/src/models/glm4-moe.cpp +28 -11
  67. package/src/llama.cpp/src/models/glm4.cpp +27 -4
  68. package/src/llama.cpp/src/models/models.h +8 -7
  69. package/src/llama.cpp/src/models/nemotron-h.cpp +35 -6
  70. package/src/llama.cpp/src/models/qwen2.cpp +12 -3
  71. package/src/llama.cpp/src/models/qwen3next.cpp +81 -266
@@ -2,18 +2,40 @@
2
2
 
3
3
  #pragma once
4
4
 
5
+ #include "common.h"
6
+
5
7
  #include <string>
6
8
 
7
- namespace console {
8
- enum display_t {
9
- reset = 0,
10
- prompt,
11
- user_input,
12
- error
13
- };
9
+ enum display_type {
10
+ DISPLAY_TYPE_RESET = 0,
11
+ DISPLAY_TYPE_INFO,
12
+ DISPLAY_TYPE_PROMPT,
13
+ DISPLAY_TYPE_REASONING,
14
+ DISPLAY_TYPE_USER_INPUT,
15
+ DISPLAY_TYPE_ERROR
16
+ };
14
17
 
18
+ namespace console {
15
19
  void init(bool use_simple_io, bool use_advanced_display);
16
20
  void cleanup();
17
- void set_display(display_t display);
21
+ void set_display(display_type display);
18
22
  bool readline(std::string & line, bool multiline_input);
23
+
24
+ namespace spinner {
25
+ void start();
26
+ void stop();
27
+ }
28
+
29
+ // note: the logging API below output directly to stdout
30
+ // it can negatively impact performance if used on inference thread
31
+ // only use in in a dedicated CLI thread
32
+ // for logging in inference thread, use log.h instead
33
+
34
+ LLAMA_COMMON_ATTRIBUTE_FORMAT(1, 2)
35
+ void log(const char * fmt, ...);
36
+
37
+ LLAMA_COMMON_ATTRIBUTE_FORMAT(1, 2)
38
+ void error(const char * fmt, ...);
39
+
40
+ void flush();
19
41
  }
@@ -12,6 +12,8 @@
12
12
  #include <filesystem>
13
13
  #include <fstream>
14
14
  #include <future>
15
+ #include <map>
16
+ #include <mutex>
15
17
  #include <regex>
16
18
  #include <string>
17
19
  #include <thread>
@@ -472,36 +474,79 @@ std::pair<long, std::vector<char>> common_remote_get_content(const std::string &
472
474
 
473
475
  #elif defined(LLAMA_USE_HTTPLIB)
474
476
 
475
- static bool is_output_a_tty() {
477
+ class ProgressBar {
478
+ static inline std::mutex mutex;
479
+ static inline std::map<const ProgressBar *, int> lines;
480
+ static inline int max_line = 0;
481
+
482
+ static void cleanup(const ProgressBar * line) {
483
+ lines.erase(line);
484
+ if (lines.empty()) {
485
+ max_line = 0;
486
+ }
487
+ }
488
+
489
+ static bool is_output_a_tty() {
476
490
  #if defined(_WIN32)
477
- return _isatty(_fileno(stdout));
491
+ return _isatty(_fileno(stdout));
478
492
  #else
479
- return isatty(1);
493
+ return isatty(1);
480
494
  #endif
481
- }
495
+ }
482
496
 
483
- static void print_progress(size_t current, size_t total) {
484
- if (!is_output_a_tty()) {
485
- return;
497
+ public:
498
+ ProgressBar() = default;
499
+
500
+ ~ProgressBar() {
501
+ std::lock_guard<std::mutex> lock(mutex);
502
+ cleanup(this);
486
503
  }
487
504
 
488
- if (!total) {
489
- return;
505
+ void update(size_t current, size_t total) {
506
+ if (!is_output_a_tty()) {
507
+ return;
508
+ }
509
+
510
+ if (!total) {
511
+ return;
512
+ }
513
+
514
+ std::lock_guard<std::mutex> lock(mutex);
515
+
516
+ if (lines.find(this) == lines.end()) {
517
+ lines[this] = max_line++;
518
+ std::cout << "\n";
519
+ }
520
+ int lines_up = max_line - lines[this];
521
+
522
+ size_t width = 50;
523
+ size_t pct = (100 * current) / total;
524
+ size_t pos = (width * current) / total;
525
+
526
+ std::cout << "\033[s";
527
+
528
+ if (lines_up > 0) {
529
+ std::cout << "\033[" << lines_up << "A";
530
+ }
531
+ std::cout << "\033[2K\r["
532
+ << std::string(pos, '=')
533
+ << (pos < width ? ">" : "")
534
+ << std::string(width - pos, ' ')
535
+ << "] " << std::setw(3) << pct << "% ("
536
+ << current / (1024 * 1024) << " MB / "
537
+ << total / (1024 * 1024) << " MB) "
538
+ << "\033[u";
539
+
540
+ std::cout.flush();
541
+
542
+ if (current == total) {
543
+ cleanup(this);
544
+ }
490
545
  }
491
546
 
492
- size_t width = 50;
493
- size_t pct = (100 * current) / total;
494
- size_t pos = (width * current) / total;
495
-
496
- std::cout << "["
497
- << std::string(pos, '=')
498
- << (pos < width ? ">" : "")
499
- << std::string(width - pos, ' ')
500
- << "] " << std::setw(3) << pct << "% ("
501
- << current / (1024 * 1024) << " MB / "
502
- << total / (1024 * 1024) << " MB)\r";
503
- std::cout.flush();
504
- }
547
+ ProgressBar(const ProgressBar &) = delete;
548
+ ProgressBar & operator=(const ProgressBar &) = delete;
549
+ };
505
550
 
506
551
  static bool common_pull_file(httplib::Client & cli,
507
552
  const std::string & resolve_path,
@@ -523,6 +568,7 @@ static bool common_pull_file(httplib::Client & cli,
523
568
  const char * func = __func__; // avoid __func__ inside a lambda
524
569
  size_t downloaded = existing_size;
525
570
  size_t progress_step = 0;
571
+ ProgressBar bar;
526
572
 
527
573
  auto res = cli.Get(resolve_path, headers,
528
574
  [&](const httplib::Response &response) {
@@ -554,7 +600,7 @@ static bool common_pull_file(httplib::Client & cli,
554
600
  progress_step += len;
555
601
 
556
602
  if (progress_step >= total_size / 1000 || downloaded == total_size) {
557
- print_progress(downloaded, total_size);
603
+ bar.update(downloaded, total_size);
558
604
  progress_step = 0;
559
605
  }
560
606
  return true;
@@ -562,8 +608,6 @@ static bool common_pull_file(httplib::Client & cli,
562
608
  nullptr
563
609
  );
564
610
 
565
- std::cout << "\n";
566
-
567
611
  if (!res) {
568
612
  LOG_ERR("%s: error during download. Status: %d\n", __func__, res ? res->status : -1);
569
613
  return false;
@@ -305,8 +305,9 @@ static std::string format_literal(const std::string & literal) {
305
305
 
306
306
  std::string gbnf_format_literal(const std::string & literal) { return format_literal(literal); }
307
307
 
308
- class SchemaConverter {
308
+ class common_schema_converter {
309
309
  private:
310
+ friend class common_schema_info;
310
311
  friend std::string build_grammar(const std::function<void(const common_grammar_builder &)> & cb, const common_grammar_options & options);
311
312
  std::function<json(const std::string &)> _fetch_json;
312
313
  bool _dotall;
@@ -729,7 +730,7 @@ private:
729
730
  }
730
731
 
731
732
  public:
732
- SchemaConverter(
733
+ common_schema_converter(
733
734
  const std::function<json(const std::string &)> & fetch_json,
734
735
  bool dotall)
735
736
  : _fetch_json(fetch_json), _dotall(dotall)
@@ -990,6 +991,134 @@ public:
990
991
  }
991
992
  };
992
993
 
994
+ // common_schema_info implementation (pimpl)
995
+
996
+ common_schema_info::common_schema_info()
997
+ : impl_(std::make_unique<common_schema_converter>(
998
+ [](const std::string &) { return json(); },
999
+ false)) {}
1000
+
1001
+ common_schema_info::~common_schema_info() = default;
1002
+
1003
+ common_schema_info::common_schema_info(common_schema_info &&) noexcept = default;
1004
+ common_schema_info & common_schema_info::operator=(common_schema_info &&) noexcept = default;
1005
+
1006
+ void common_schema_info::resolve_refs(nlohmann::ordered_json & schema) {
1007
+ impl_->resolve_refs(schema, "");
1008
+ }
1009
+
1010
+ // Determines if a JSON schema can resolve to a string type through any path.
1011
+ // Some models emit raw string values rather than JSON-encoded strings for string parameters.
1012
+ // If any branch of the schema (via oneOf, anyOf, $ref, etc.) permits a string, this returns
1013
+ // true, allowing callers to handle the value as a raw string for simplicity.
1014
+ bool common_schema_info::resolves_to_string(const nlohmann::ordered_json & schema) {
1015
+ std::unordered_set<std::string> visited_refs;
1016
+
1017
+ std::function<bool(const json &)> check = [&](const json & s) -> bool {
1018
+ if (!s.is_object()) {
1019
+ return false;
1020
+ }
1021
+
1022
+ // Handle $ref
1023
+ if (s.contains("$ref")) {
1024
+ const std::string & ref = s["$ref"];
1025
+ if (visited_refs.find(ref) != visited_refs.end()) {
1026
+ // Circular reference, assume not a string to be safe
1027
+ return false;
1028
+ }
1029
+ visited_refs.insert(ref);
1030
+ auto it = impl_->_refs.find(ref);
1031
+ if (it != impl_->_refs.end()) {
1032
+ return check(it->second);
1033
+ }
1034
+ return false;
1035
+ }
1036
+
1037
+ // Check type field
1038
+ if (s.contains("type")) {
1039
+ const json & schema_type = s["type"];
1040
+ if (schema_type.is_string()) {
1041
+ if (schema_type == "string") {
1042
+ return true;
1043
+ }
1044
+ } else if (schema_type.is_array()) {
1045
+ // Type can be an array like ["string", "null"]
1046
+ for (const auto & t : schema_type) {
1047
+ if (t == "string") {
1048
+ return true;
1049
+ }
1050
+ }
1051
+ }
1052
+ }
1053
+
1054
+ // Check oneOf/anyOf - if any alternative can be a string
1055
+ if (s.contains("oneOf")) {
1056
+ for (const auto & alt : s["oneOf"]) {
1057
+ if (check(alt)) {
1058
+ return true;
1059
+ }
1060
+ }
1061
+ }
1062
+ if (s.contains("anyOf")) {
1063
+ for (const auto & alt : s["anyOf"]) {
1064
+ if (check(alt)) {
1065
+ return true;
1066
+ }
1067
+ }
1068
+ }
1069
+
1070
+ // Check allOf - all components must be compatible with string type
1071
+ if (s.contains("allOf")) {
1072
+ bool all_string = true;
1073
+ for (const auto & component : s["allOf"]) {
1074
+ if (!check(component)) {
1075
+ all_string = false;
1076
+ break;
1077
+ }
1078
+ }
1079
+ if (all_string) {
1080
+ return true;
1081
+ }
1082
+ }
1083
+
1084
+ // Check const - if the constant value is a string
1085
+ if (s.contains("const")) {
1086
+ if (s["const"].is_string()) {
1087
+ return true;
1088
+ }
1089
+ }
1090
+
1091
+ // Check enum - if any enum value is a string
1092
+ if (s.contains("enum")) {
1093
+ for (const auto & val : s["enum"]) {
1094
+ if (val.is_string()) {
1095
+ return true;
1096
+ }
1097
+ }
1098
+ }
1099
+
1100
+ // String-specific keywords imply string type
1101
+ if (s.contains("pattern") || s.contains("minLength") || s.contains("maxLength")) {
1102
+ return true;
1103
+ }
1104
+
1105
+ // Check format - many formats imply string
1106
+ if (s.contains("format")) {
1107
+ const std::string & fmt = s["format"];
1108
+ if (fmt == "date" || fmt == "time" || fmt == "date-time" ||
1109
+ fmt == "uri" || fmt == "email" || fmt == "hostname" ||
1110
+ fmt == "ipv4" || fmt == "ipv6" || fmt == "uuid" ||
1111
+ fmt.find("uuid") == 0) {
1112
+ return true;
1113
+ }
1114
+ }
1115
+
1116
+ return false;
1117
+ };
1118
+
1119
+ return check(schema);
1120
+ }
1121
+
993
1122
  std::string json_schema_to_grammar(const json & schema, bool force_gbnf) {
994
1123
  #ifdef LLAMA_USE_LLGUIDANCE
995
1124
  if (!force_gbnf) {
@@ -1006,7 +1135,7 @@ std::string json_schema_to_grammar(const json & schema, bool force_gbnf) {
1006
1135
  }
1007
1136
 
1008
1137
  std::string build_grammar(const std::function<void(const common_grammar_builder &)> & cb, const common_grammar_options & options) {
1009
- SchemaConverter converter([&](const std::string &) { return json(); }, options.dotall);
1138
+ common_schema_converter converter([&](const std::string &) { return json(); }, options.dotall);
1010
1139
  common_grammar_builder builder {
1011
1140
  /* .add_rule = */ [&](const std::string & name, const std::string & rule) {
1012
1141
  return converter._add_rule(name, rule);
@@ -3,11 +3,31 @@
3
3
  #include <nlohmann/json_fwd.hpp>
4
4
 
5
5
  #include <functional>
6
+ #include <memory>
6
7
  #include <string>
7
8
 
8
9
  std::string json_schema_to_grammar(const nlohmann::ordered_json & schema,
9
10
  bool force_gbnf = false);
10
11
 
12
+ class common_schema_converter;
13
+
14
+ // Probes a JSON schema to extract information about its structure and type constraints.
15
+ class common_schema_info {
16
+ std::unique_ptr<common_schema_converter> impl_;
17
+
18
+ public:
19
+ common_schema_info();
20
+ ~common_schema_info();
21
+
22
+ common_schema_info(const common_schema_info &) = delete;
23
+ common_schema_info & operator=(const common_schema_info &) = delete;
24
+ common_schema_info(common_schema_info &&) noexcept;
25
+ common_schema_info & operator=(common_schema_info &&) noexcept;
26
+
27
+ void resolve_refs(nlohmann::ordered_json & schema);
28
+ bool resolves_to_string(const nlohmann::ordered_json & schema);
29
+ };
30
+
11
31
  struct common_grammar_builder {
12
32
  std::function<std::string(const std::string &, const std::string &)> add_rule;
13
33
  std::function<std::string(const std::string &, const nlohmann::ordered_json &)> add_schema;
@@ -420,6 +420,11 @@ void common_log_set_timestamps(struct common_log * log, bool timestamps) {
420
420
  log->set_timestamps(timestamps);
421
421
  }
422
422
 
423
+ void common_log_flush(struct common_log * log) {
424
+ log->pause();
425
+ log->resume();
426
+ }
427
+
423
428
  static int common_get_verbosity(enum ggml_log_level level) {
424
429
  switch (level) {
425
430
  case GGML_LOG_LEVEL_DEBUG: return LOG_LEVEL_DEBUG;
@@ -84,6 +84,7 @@ void common_log_set_file (struct common_log * log, const char * file); // n
84
84
  void common_log_set_colors (struct common_log * log, log_colors colors); // not thread-safe
85
85
  void common_log_set_prefix (struct common_log * log, bool prefix); // whether to output prefix to each log
86
86
  void common_log_set_timestamps(struct common_log * log, bool timestamps); // whether to output timestamps in the prefix
87
+ void common_log_flush (struct common_log * log); // flush all pending log messages
87
88
 
88
89
  // helper macros for logging
89
90
  // use these to avoid computing log arguments if the verbosity of the log is higher than the threshold
@@ -425,7 +425,7 @@ struct parser_executor {
425
425
 
426
426
  if (result.need_more_input()) {
427
427
  // Propagate - need to know what child would match before negating
428
- return result;
428
+ return common_peg_parse_result(COMMON_PEG_PARSE_RESULT_NEED_MORE_INPUT, start_pos);
429
429
  }
430
430
 
431
431
  // Child failed, so negation succeeds
@@ -0,0 +1,206 @@
1
+ #include "arg.h"
2
+ #include "preset.h"
3
+ #include "peg-parser.h"
4
+ #include "log.h"
5
+
6
+ #include <fstream>
7
+ #include <sstream>
8
+ #include <filesystem>
9
+
10
+ static std::string rm_leading_dashes(const std::string & str) {
11
+ size_t pos = 0;
12
+ while (pos < str.size() && str[pos] == '-') {
13
+ ++pos;
14
+ }
15
+ return str.substr(pos);
16
+ }
17
+
18
+ std::vector<std::string> common_preset::to_args() const {
19
+ std::vector<std::string> args;
20
+
21
+ for (const auto & [opt, value] : options) {
22
+ args.push_back(opt.args.back()); // use the last arg as the main arg
23
+ if (opt.value_hint == nullptr && opt.value_hint_2 == nullptr) {
24
+ // flag option, no value
25
+ if (common_arg_utils::is_falsey(value)) {
26
+ // use negative arg if available
27
+ if (!opt.args_neg.empty()) {
28
+ args.back() = opt.args_neg.back();
29
+ } else {
30
+ // otherwise, skip the flag
31
+ // TODO: maybe throw an error instead?
32
+ args.pop_back();
33
+ }
34
+ }
35
+ }
36
+ if (opt.value_hint != nullptr) {
37
+ // single value
38
+ args.push_back(value);
39
+ }
40
+ if (opt.value_hint != nullptr && opt.value_hint_2 != nullptr) {
41
+ throw std::runtime_error(string_format(
42
+ "common_preset::to_args(): option '%s' has two values, which is not supported yet",
43
+ opt.args.back()
44
+ ));
45
+ }
46
+ }
47
+
48
+ return args;
49
+ }
50
+
51
+ std::string common_preset::to_ini() const {
52
+ std::ostringstream ss;
53
+
54
+ ss << "[" << name << "]\n";
55
+ for (const auto & [opt, value] : options) {
56
+ auto espaced_value = value;
57
+ string_replace_all(espaced_value, "\n", "\\\n");
58
+ ss << rm_leading_dashes(opt.args.back()) << " = ";
59
+ ss << espaced_value << "\n";
60
+ }
61
+ ss << "\n";
62
+
63
+ return ss.str();
64
+ }
65
+
66
+ static std::map<std::string, std::map<std::string, std::string>> parse_ini_from_file(const std::string & path) {
67
+ std::map<std::string, std::map<std::string, std::string>> parsed;
68
+
69
+ if (!std::filesystem::exists(path)) {
70
+ throw std::runtime_error("preset file does not exist: " + path);
71
+ }
72
+
73
+ std::ifstream file(path);
74
+ if (!file.good()) {
75
+ throw std::runtime_error("failed to open server preset file: " + path);
76
+ }
77
+
78
+ std::string contents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
79
+
80
+ static const auto parser = build_peg_parser([](auto & p) {
81
+ // newline ::= "\r\n" / "\n" / "\r"
82
+ auto newline = p.rule("newline", p.literal("\r\n") | p.literal("\n") | p.literal("\r"));
83
+
84
+ // ws ::= [ \t]*
85
+ auto ws = p.rule("ws", p.chars("[ \t]", 0, -1));
86
+
87
+ // comment ::= [;#] (!newline .)*
88
+ auto comment = p.rule("comment", p.chars("[;#]", 1, 1) + p.zero_or_more(p.negate(newline) + p.any()));
89
+
90
+ // eol ::= ws comment? (newline / EOF)
91
+ auto eol = p.rule("eol", ws + p.optional(comment) + (newline | p.end()));
92
+
93
+ // ident ::= [a-zA-Z_] [a-zA-Z0-9_.-]*
94
+ auto ident = p.rule("ident", p.chars("[a-zA-Z_]", 1, 1) + p.chars("[a-zA-Z0-9_.-]", 0, -1));
95
+
96
+ // value ::= (!eol-start .)*
97
+ auto eol_start = p.rule("eol-start", ws + (p.chars("[;#]", 1, 1) | newline | p.end()));
98
+ auto value = p.rule("value", p.zero_or_more(p.negate(eol_start) + p.any()));
99
+
100
+ // header-line ::= "[" ws ident ws "]" eol
101
+ auto header_line = p.rule("header-line", "[" + ws + p.tag("section-name", p.chars("[^]]")) + ws + "]" + eol);
102
+
103
+ // kv-line ::= ident ws "=" ws value eol
104
+ auto kv_line = p.rule("kv-line", p.tag("key", ident) + ws + "=" + ws + p.tag("value", value) + eol);
105
+
106
+ // comment-line ::= ws comment (newline / EOF)
107
+ auto comment_line = p.rule("comment-line", ws + comment + (newline | p.end()));
108
+
109
+ // blank-line ::= ws (newline / EOF)
110
+ auto blank_line = p.rule("blank-line", ws + (newline | p.end()));
111
+
112
+ // line ::= header-line / kv-line / comment-line / blank-line
113
+ auto line = p.rule("line", header_line | kv_line | comment_line | blank_line);
114
+
115
+ // ini ::= line* EOF
116
+ auto ini = p.rule("ini", p.zero_or_more(line) + p.end());
117
+
118
+ return ini;
119
+ });
120
+
121
+ common_peg_parse_context ctx(contents);
122
+ const auto result = parser.parse(ctx);
123
+ if (!result.success()) {
124
+ throw std::runtime_error("failed to parse server config file: " + path);
125
+ }
126
+
127
+ std::string current_section = COMMON_PRESET_DEFAULT_NAME;
128
+ std::string current_key;
129
+
130
+ ctx.ast.visit(result, [&](const auto & node) {
131
+ if (node.tag == "section-name") {
132
+ const std::string section = std::string(node.text);
133
+ current_section = section;
134
+ parsed[current_section] = {};
135
+ } else if (node.tag == "key") {
136
+ const std::string key = std::string(node.text);
137
+ current_key = key;
138
+ } else if (node.tag == "value" && !current_key.empty() && !current_section.empty()) {
139
+ parsed[current_section][current_key] = std::string(node.text);
140
+ current_key.clear();
141
+ }
142
+ });
143
+
144
+ return parsed;
145
+ }
146
+
147
+ static std::map<std::string, common_arg> get_map_key_opt(common_params_context & ctx_params) {
148
+ std::map<std::string, common_arg> mapping;
149
+ for (const auto & opt : ctx_params.options) {
150
+ for (const auto & env : opt.get_env()) {
151
+ mapping[env] = opt;
152
+ }
153
+ for (const auto & arg : opt.get_args()) {
154
+ mapping[rm_leading_dashes(arg)] = opt;
155
+ }
156
+ }
157
+ return mapping;
158
+ }
159
+
160
+ static bool is_bool_arg(const common_arg & arg) {
161
+ return !arg.args_neg.empty();
162
+ }
163
+
164
+ static std::string parse_bool_arg(const common_arg & arg, const std::string & key, const std::string & value) {
165
+ // if this is a negated arg, we need to reverse the value
166
+ for (const auto & neg_arg : arg.args_neg) {
167
+ if (rm_leading_dashes(neg_arg) == key) {
168
+ return common_arg_utils::is_truthy(value) ? "false" : "true";
169
+ }
170
+ }
171
+ // otherwise, not negated
172
+ return value;
173
+ }
174
+
175
+ common_presets common_presets_load(const std::string & path, common_params_context & ctx_params) {
176
+ common_presets out;
177
+ auto key_to_opt = get_map_key_opt(ctx_params);
178
+ auto ini_data = parse_ini_from_file(path);
179
+
180
+ for (auto section : ini_data) {
181
+ common_preset preset;
182
+ if (section.first.empty()) {
183
+ preset.name = COMMON_PRESET_DEFAULT_NAME;
184
+ } else {
185
+ preset.name = section.first;
186
+ }
187
+ LOG_DBG("loading preset: %s\n", preset.name.c_str());
188
+ for (const auto & [key, value] : section.second) {
189
+ LOG_DBG("option: %s = %s\n", key.c_str(), value.c_str());
190
+ if (key_to_opt.find(key) != key_to_opt.end()) {
191
+ auto & opt = key_to_opt[key];
192
+ if (is_bool_arg(opt)) {
193
+ preset.options[opt] = parse_bool_arg(opt, key, value);
194
+ } else {
195
+ preset.options[opt] = value;
196
+ }
197
+ LOG_DBG("accepted option: %s = %s\n", key.c_str(), preset.options[opt].c_str());
198
+ } else {
199
+ // TODO: maybe warn about unknown key?
200
+ }
201
+ }
202
+ out[preset.name] = preset;
203
+ }
204
+
205
+ return out;
206
+ }
@@ -0,0 +1,32 @@
1
+ #pragma once
2
+
3
+ #include "common.h"
4
+ #include "arg.h"
5
+
6
+ #include <string>
7
+ #include <vector>
8
+ #include <map>
9
+
10
+ //
11
+ // INI preset parser and writer
12
+ //
13
+
14
+ constexpr const char * COMMON_PRESET_DEFAULT_NAME = "default";
15
+
16
+ struct common_preset {
17
+ std::string name;
18
+ // TODO: support repeated args in the future
19
+ std::map<common_arg, std::string> options;
20
+
21
+ // convert preset to CLI argument list
22
+ std::vector<std::string> to_args() const;
23
+
24
+ // convert preset to INI format string
25
+ std::string to_ini() const;
26
+
27
+ // TODO: maybe implement to_env() if needed
28
+ };
29
+
30
+ // interface for multiple presets in one file
31
+ using common_presets = std::map<std::string, common_preset>;
32
+ common_presets common_presets_load(const std::string & path, common_params_context & ctx_params);