Safetensors
GGUF
Turkish
llama
Llama-3
instruct
finetune
chatml
gpt4
synthetic data
distillation
function calling
json mode
axolotl
roleplaying
chat
Instructions to use tda45/TdAI with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- llama-cpp-python
How to use tda45/TdAI with llama-cpp-python:
# !pip install llama-cpp-python from llama_cpp import Llama llm = Llama.from_pretrained( repo_id="tda45/TdAI", filename="llama.cpp/models/ggml-vocab-aquila.gguf", )
output = llm( "Once upon a time,", max_tokens=512, echo=True ) print(output)
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use tda45/TdAI with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf tda45/TdAI # Run inference directly in the terminal: llama cli -hf tda45/TdAI
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf tda45/TdAI # Run inference directly in the terminal: llama cli -hf tda45/TdAI
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf tda45/TdAI # Run inference directly in the terminal: ./llama-cli -hf tda45/TdAI
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf tda45/TdAI # Run inference directly in the terminal: ./build/bin/llama-cli -hf tda45/TdAI
Use Docker
docker model run hf.co/tda45/TdAI
- LM Studio
- Jan
- Ollama
How to use tda45/TdAI with Ollama:
ollama run hf.co/tda45/TdAI
- Unsloth Studio
How to use tda45/TdAI with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for tda45/TdAI to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for tda45/TdAI to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for tda45/TdAI to start chatting
- Atomic Chat new
- Docker Model Runner
How to use tda45/TdAI with Docker Model Runner:
docker model run hf.co/tda45/TdAI
- Lemonade
How to use tda45/TdAI with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull tda45/TdAI
Run and chat with the model
lemonade run user.TdAI-{{QUANT_TAG}}List all available models
lemonade list
| // for converting from JSON to jinja values | |
| namespace jinja { | |
| // func_args method implementations | |
| value func_args::get_kwarg(const std::string & key, value default_val) const { | |
| for (const auto & arg : args) { | |
| if (is_val<value_kwarg>(arg)) { | |
| auto * kwarg = cast_val<value_kwarg>(arg); | |
| if (kwarg->key == key) { | |
| return kwarg->val; | |
| } | |
| } | |
| } | |
| return default_val; | |
| } | |
| value func_args::get_kwarg_or_pos(const std::string & key, size_t pos) const { | |
| value val = get_kwarg(key, mk_val<value_undefined>()); | |
| if (val->is_undefined() && pos < count() && !is_val<value_kwarg>(args[pos])) { | |
| return args[pos]; | |
| } | |
| return val; | |
| } | |
| value func_args::get_pos(size_t pos) const { | |
| if (count() > pos) { | |
| return args[pos]; | |
| } | |
| throw raised_exception("Function '" + func_name + "' expected at least " + std::to_string(pos + 1) + " arguments, got " + std::to_string(count())); | |
| } | |
| value func_args::get_pos(size_t pos, value default_val) const { | |
| if (count() > pos) { | |
| return args[pos]; | |
| } | |
| return default_val; | |
| } | |
| void func_args::push_back(const value & val) { | |
| args.push_back(val); | |
| } | |
| void func_args::push_front(const value & val) { | |
| args.insert(args.begin(), val); | |
| } | |
| const std::vector<value> & func_args::get_args() const { | |
| return args; | |
| } | |
| /** | |
| * Function that mimics Python's array slicing. | |
| */ | |
| template<typename T> | |
| static T slice(const T & array, int64_t start, int64_t stop, int64_t step = 1) { | |
| int64_t len = static_cast<int64_t>(array.size()); | |
| int64_t direction = (step > 0) ? 1 : ((step < 0) ? -1 : 0); | |
| int64_t start_val = 0; | |
| int64_t stop_val = 0; | |
| if (direction >= 0) { | |
| start_val = start; | |
| if (start_val < 0) { | |
| start_val = std::max(len + start_val, (int64_t)0); | |
| } else { | |
| start_val = std::min(start_val, len); | |
| } | |
| stop_val = stop; | |
| if (stop_val < 0) { | |
| stop_val = std::max(len + stop_val, (int64_t)0); | |
| } else { | |
| stop_val = std::min(stop_val, len); | |
| } | |
| } else { | |
| start_val = start; | |
| if (start_val < 0) { | |
| start_val = std::max(len + start_val, (int64_t)0); | |
| } else { | |
| start_val = std::min(start_val, len - 1); | |
| } | |
| stop_val = stop; | |
| if (stop_val < -1) { | |
| stop_val = std::max(len + stop_val, (int64_t)-1); | |
| } else { | |
| stop_val = std::min(stop_val, len - 1); | |
| } | |
| } | |
| T result; | |
| if (direction == 0) { | |
| return result; | |
| } | |
| for (int64_t i = start_val; direction * i < direction * stop_val; i += step) { | |
| if (i >= 0 && i < len) { | |
| result.push_back(array[static_cast<size_t>(i)]); | |
| } | |
| } | |
| return result; | |
| } | |
| template<typename T> | |
| static value empty_value_fn(const func_args &) { | |
| if constexpr (std::is_same_v<T, value_int>) { | |
| return mk_val<T>(0); | |
| } else if constexpr (std::is_same_v<T, value_float>) { | |
| return mk_val<T>(0.0); | |
| } else if constexpr (std::is_same_v<T, value_bool>) { | |
| return mk_val<T>(false); | |
| } else { | |
| return mk_val<T>(); | |
| } | |
| } | |
| template<typename T> | |
| static value test_type_fn(const func_args & args) { | |
| args.ensure_count(1); | |
| bool is_type = is_val<T>(args.get_pos(0)); | |
| JJ_DEBUG("test_type_fn: type=%s result=%d", typeid(T).name(), is_type ? 1 : 0); | |
| return mk_val<value_bool>(is_type); | |
| } | |
| template<typename T, typename U> | |
| static value test_type_fn(const func_args & args) { | |
| args.ensure_count(1); | |
| bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0)); | |
| JJ_DEBUG("test_type_fn: type=%s or %s result=%d", typeid(T).name(), typeid(U).name(), is_type ? 1 : 0); | |
| return mk_val<value_bool>(is_type); | |
| } | |
| template<typename T, typename U, typename V> | |
| static value test_type_fn(const func_args & args) { | |
| args.ensure_count(1); | |
| bool is_type = is_val<T>(args.get_pos(0)) || is_val<U>(args.get_pos(0)) || is_val<V>(args.get_pos(0)); | |
| JJ_DEBUG("test_type_fn: type=%s, %s or %s result=%d", typeid(T).name(), typeid(U).name(), typeid(V).name(), is_type ? 1 : 0); | |
| return mk_val<value_bool>(is_type); | |
| } | |
| template<value_compare_op op> | |
| static value test_compare_fn(const func_args & args) { | |
| args.ensure_count(2, 2); | |
| return mk_val<value_bool>(value_compare(args.get_pos(0), args.get_pos(1), op)); | |
| } | |
| static void append_codepoint_as_ascii_json_escape(std::string & out, uint32_t codepoint) { | |
| auto append_u16 = [&out](uint32_t value) { | |
| char buf[8]; | |
| snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned int>(value)); | |
| out += buf; | |
| }; | |
| if (codepoint <= 0xFFFF) { | |
| append_u16(codepoint); | |
| return; | |
| } | |
| codepoint -= 0x10000; | |
| append_u16(0xD800 + ((codepoint >> 10) & 0x3FF)); | |
| append_u16(0xDC00 + (codepoint & 0x3FF)); | |
| } | |
| static std::string json_ensure_ascii_preserving_format(const std::string & json_str) { | |
| std::string output; | |
| output.reserve(json_str.size()); | |
| bool in_string = false; | |
| bool escaped = false; | |
| for (size_t pos = 0; pos < json_str.size();) { | |
| const char ch = json_str[pos]; | |
| if (!in_string) { | |
| output.push_back(ch); | |
| if (ch == '"') { | |
| in_string = true; | |
| } | |
| ++pos; | |
| continue; | |
| } | |
| if (escaped) { | |
| output.push_back(ch); | |
| escaped = false; | |
| ++pos; | |
| continue; | |
| } | |
| if (ch == '\\') { | |
| output.push_back(ch); | |
| escaped = true; | |
| ++pos; | |
| continue; | |
| } | |
| if (ch == '"') { | |
| output.push_back(ch); | |
| in_string = false; | |
| ++pos; | |
| continue; | |
| } | |
| const unsigned char uch = static_cast<unsigned char>(ch); | |
| if (uch < 0x80) { | |
| output.push_back(ch); | |
| ++pos; | |
| continue; | |
| } | |
| auto parsed = common_parse_utf8_codepoint(json_str, pos); | |
| if (parsed.status != utf8_parse_result::SUCCESS) { | |
| output += "\\ufffd"; | |
| ++pos; | |
| continue; | |
| } | |
| append_codepoint_as_ascii_json_escape(output, parsed.codepoint); | |
| pos += parsed.bytes_consumed; | |
| } | |
| return output; | |
| } | |
| static value tojson(const func_args & args) { | |
| args.ensure_count(1, 5); | |
| value val_ascii = args.get_kwarg_or_pos("ensure_ascii", 1); | |
| value val_indent = args.get_kwarg_or_pos("indent", 2); | |
| value val_separators = args.get_kwarg_or_pos("separators", 3); | |
| value val_sort = args.get_kwarg_or_pos("sort_keys", 4); | |
| int indent = -1; | |
| if (args.ctx.is_get_stats) { | |
| // mark as used (recursively) for stats | |
| auto val_input = args.get_pos(0); | |
| value_t::stats_t::mark_used(const_cast<value&>(val_input), true); | |
| } | |
| if (is_val<value_int>(val_indent)) { | |
| indent = static_cast<int>(val_indent->as_int()); | |
| } | |
| if (val_sort->as_bool()) { // undefined == false | |
| throw not_implemented_exception("tojson sort_keys=true not implemented"); | |
| } | |
| const bool ensure_ascii = val_ascii->as_bool(); // undefined == false | |
| auto separators = (is_val<value_array>(val_separators) ? val_separators : mk_val<value_array>())->as_array(); | |
| std::string item_sep = separators.size() > 0 ? separators[0]->as_string().str() : (indent < 0 ? ", " : ","); | |
| std::string key_sep = separators.size() > 1 ? separators[1]->as_string().str() : ": "; | |
| std::string json_str = value_to_json(args.get_pos(0), indent, item_sep, key_sep); | |
| if (ensure_ascii) { | |
| json_str = json_ensure_ascii_preserving_format(json_str); | |
| } | |
| return mk_val<value_string>(json_str); | |
| } | |
| template<bool is_reject> | |
| static value selectattr(const func_args & args) { | |
| args.ensure_count(2, 4); | |
| args.ensure_vals<value_array, value_string, value_string, value_string>(true, true, false, false); | |
| auto arr = args.get_pos(0)->as_array(); | |
| auto attribute = args.get_pos(1); | |
| auto out = mk_val<value_array>(); | |
| value val_default = mk_val<value_undefined>(); | |
| if (args.count() == 2) { | |
| // example: array | selectattr("active") | |
| for (const auto & item : arr) { | |
| if (!is_val<value_object>(item)) { | |
| throw raised_exception("selectattr: item is not an object"); | |
| } | |
| value attr_val = item->at(attribute, val_default); | |
| bool is_selected = attr_val->as_bool(); | |
| if constexpr (is_reject) is_selected = !is_selected; | |
| if (is_selected) out->push_back(item); | |
| } | |
| return out; | |
| } else if (args.count() == 3) { | |
| // example: array | selectattr("equalto", "text") | |
| // translated to: test_is_equalto(item, "text") | |
| std::string test_name = args.get_pos(1)->as_string().str(); | |
| value test_val = args.get_pos(2); | |
| auto & builtins = global_builtins(); | |
| auto it = builtins.find("test_is_" + test_name); | |
| if (it == builtins.end()) { | |
| throw raised_exception("selectattr: unknown test '" + test_name + "'"); | |
| } | |
| auto test_fn = it->second; | |
| for (const auto & item : arr) { | |
| func_args test_args(args.ctx); | |
| test_args.push_back(item); // current object | |
| test_args.push_back(test_val); // extra argument | |
| value test_result = test_fn(test_args); | |
| bool is_selected = test_result->as_bool(); | |
| if constexpr (is_reject) is_selected = !is_selected; | |
| if (is_selected) out->push_back(item); | |
| } | |
| return out; | |
| } else if (args.count() == 4) { | |
| // example: array | selectattr("status", "equalto", "active") | |
| // translated to: test_is_equalto(item.status, "active") | |
| std::string test_name = args.get_pos(2)->as_string().str(); | |
| auto extra_arg = args.get_pos(3); | |
| auto & builtins = global_builtins(); | |
| auto it = builtins.find("test_is_" + test_name); | |
| if (it == builtins.end()) { | |
| throw raised_exception("selectattr: unknown test '" + test_name + "'"); | |
| } | |
| auto test_fn = it->second; | |
| for (const auto & item : arr) { | |
| if (!is_val<value_object>(item)) { | |
| throw raised_exception("selectattr: item is not an object"); | |
| } | |
| value attr_val = item->at(attribute, val_default); | |
| func_args test_args(args.ctx); | |
| test_args.push_back(attr_val); // attribute value | |
| test_args.push_back(extra_arg); // extra argument | |
| value test_result = test_fn(test_args); | |
| bool is_selected = test_result->as_bool(); | |
| if constexpr (is_reject) is_selected = !is_selected; | |
| if (is_selected) out->push_back(item); | |
| } | |
| return out; | |
| } else { | |
| throw raised_exception("selectattr: invalid number of arguments"); | |
| } | |
| return out; | |
| } | |
| static value default_value(const func_args & args) { | |
| args.ensure_count(2, 3); | |
| value val_check = args.get_kwarg_or_pos("boolean", 2); | |
| bool check_bool = val_check->as_bool(); // undefined == false | |
| bool no_value = check_bool | |
| ? (!args.get_pos(0)->as_bool()) | |
| : (args.get_pos(0)->is_undefined() || args.get_pos(0)->is_none()); | |
| return no_value ? args.get_pos(1) : args.get_pos(0); | |
| } | |
| const func_builtins & global_builtins() { | |
| static const func_builtins builtins = { | |
| {"raise_exception", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| std::string msg = args.get_pos(0)->as_string().str(); | |
| throw raised_exception("Jinja Exception: " + msg); | |
| }}, | |
| {"namespace", [](const func_args & args) -> value { | |
| auto out = mk_val<value_object>(); | |
| for (const auto & arg : args.get_args()) { | |
| if (!is_val<value_kwarg>(arg)) { | |
| throw raised_exception("namespace() arguments must be kwargs"); | |
| } | |
| auto kwarg = cast_val<value_kwarg>(arg); | |
| JJ_DEBUG("namespace: adding key '%s'", kwarg->key.c_str()); | |
| out->insert(kwarg->key, kwarg->val); | |
| } | |
| return out; | |
| }}, | |
| {"strftime_now", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| std::string format = args.get_pos(0)->as_string().str(); | |
| // get current time | |
| // TODO: make sure this is the same behavior as Python's strftime | |
| char buf[100]; | |
| if (std::strftime(buf, sizeof(buf), format.c_str(), std::localtime(&args.ctx.current_time))) { | |
| return mk_val<value_string>(std::string(buf)); | |
| } else { | |
| throw raised_exception("strftime_now: failed to format time"); | |
| } | |
| }}, | |
| {"range", [](const func_args & args) -> value { | |
| args.ensure_count(1, 3); | |
| args.ensure_vals<value_int, value_int, value_int>(true, false, false); | |
| auto arg0 = args.get_pos(0); | |
| auto arg1 = args.get_pos(1, mk_val<value_undefined>()); | |
| auto arg2 = args.get_pos(2, mk_val<value_undefined>()); | |
| int64_t start, stop, step; | |
| if (args.count() == 1) { | |
| start = 0; | |
| stop = arg0->as_int(); | |
| step = 1; | |
| } else if (args.count() == 2) { | |
| start = arg0->as_int(); | |
| stop = arg1->as_int(); | |
| step = 1; | |
| } else { | |
| start = arg0->as_int(); | |
| stop = arg1->as_int(); | |
| step = arg2->as_int(); | |
| } | |
| auto out = mk_val<value_array>(); | |
| if (step == 0) { | |
| throw raised_exception("range() step argument must not be zero"); | |
| } | |
| if (step > 0) { | |
| for (int64_t i = start; i < stop; i += step) { | |
| out->push_back(mk_val<value_int>(i)); | |
| } | |
| } else { | |
| for (int64_t i = start; i > stop; i += step) { | |
| out->push_back(mk_val<value_int>(i)); | |
| } | |
| } | |
| return out; | |
| }}, | |
| {"tojson", tojson}, | |
| // tests | |
| {"test_is_boolean", test_type_fn<value_bool>}, | |
| {"test_is_callable", test_type_fn<value_func>}, | |
| {"test_is_odd", [](const func_args & args) -> value { | |
| args.ensure_vals<value_int>(); | |
| int64_t val = args.get_pos(0)->as_int(); | |
| return mk_val<value_bool>(val % 2 != 0); | |
| }}, | |
| {"test_is_even", [](const func_args & args) -> value { | |
| args.ensure_vals<value_int>(); | |
| int64_t val = args.get_pos(0)->as_int(); | |
| return mk_val<value_bool>(val % 2 == 0); | |
| }}, | |
| {"test_is_false", [](const func_args & args) -> value { | |
| args.ensure_count(1); | |
| bool val = is_val<value_bool>(args.get_pos(0)) && !args.get_pos(0)->as_bool(); | |
| return mk_val<value_bool>(val); | |
| }}, | |
| {"test_is_true", [](const func_args & args) -> value { | |
| args.ensure_count(1); | |
| bool val = is_val<value_bool>(args.get_pos(0)) && args.get_pos(0)->as_bool(); | |
| return mk_val<value_bool>(val); | |
| }}, | |
| {"test_is_divisibleby", [](const func_args & args) -> value { | |
| args.ensure_vals<value_int, value_int>(); | |
| bool res = args.get_pos(0)->val_int % args.get_pos(1)->val_int == 0; | |
| return mk_val<value_bool>(res); | |
| }}, | |
| {"test_is_string", test_type_fn<value_string>}, | |
| {"test_is_integer", test_type_fn<value_int>}, | |
| {"test_is_float", test_type_fn<value_float>}, | |
| {"test_is_number", test_type_fn<value_int, value_float>}, | |
| {"test_is_iterable", test_type_fn<value_array, value_string, value_undefined>}, | |
| {"test_is_sequence", test_type_fn<value_array, value_string, value_undefined>}, | |
| {"test_is_mapping", test_type_fn<value_object>}, | |
| {"test_is_lower", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| return mk_val<value_bool>(args.get_pos(0)->val_str.is_lowercase()); | |
| }}, | |
| {"test_is_upper", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| return mk_val<value_bool>(args.get_pos(0)->val_str.is_uppercase()); | |
| }}, | |
| {"test_is_none", test_type_fn<value_none>}, | |
| {"test_is_defined", [](const func_args & args) -> value { | |
| args.ensure_count(1); | |
| bool res = !args.get_pos(0)->is_undefined(); | |
| JJ_DEBUG("test_is_defined: result=%d", res ? 1 : 0); | |
| return mk_val<value_bool>(res); | |
| }}, | |
| {"test_is_undefined", test_type_fn<value_undefined>}, | |
| {"test_is_eq", test_compare_fn<value_compare_op::eq>}, | |
| {"test_is_equalto", test_compare_fn<value_compare_op::eq>}, | |
| {"test_is_ge", test_compare_fn<value_compare_op::ge>}, | |
| {"test_is_gt", test_compare_fn<value_compare_op::gt>}, | |
| {"test_is_greaterthan", test_compare_fn<value_compare_op::gt>}, | |
| {"test_is_lt", test_compare_fn<value_compare_op::lt>}, | |
| {"test_is_lessthan", test_compare_fn<value_compare_op::lt>}, | |
| {"test_is_ne", test_compare_fn<value_compare_op::ne>}, | |
| {"test_is_in", [](const func_args & args) -> value { | |
| args.ensure_count(2); | |
| auto needle = args.get_pos(0); | |
| auto haystack = args.get_pos(1); | |
| if (is_val<value_undefined>(haystack)) { | |
| return mk_val<value_bool>(false); | |
| } | |
| if (is_val<value_array>(haystack)) { | |
| for (const auto & item : haystack->as_array()) { | |
| if (*needle == *item) { | |
| return mk_val<value_bool>(true); | |
| } | |
| } | |
| return mk_val<value_bool>(false); | |
| } | |
| if (is_val<value_string>(haystack)) { | |
| if (!is_val<value_string>(needle)) { | |
| throw raised_exception("'in' test expects args[1] as string when args[0] is string, got args[1] as " + needle->type()); | |
| } | |
| return mk_val<value_bool>( | |
| haystack->as_string().str().find(needle->as_string().str()) != std::string::npos); | |
| } | |
| if (is_val<value_object>(haystack)) { | |
| return mk_val<value_bool>(haystack->has_key(needle)); | |
| } | |
| throw raised_exception("'in' test expects iterable as first argument, got " + haystack->type()); | |
| }}, | |
| {"test_is_test", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| auto & builtins = global_builtins(); | |
| std::string test_name = args.get_pos(0)->val_str.str(); | |
| auto it = builtins.find("test_is_" + test_name); | |
| bool res = it != builtins.end(); | |
| return mk_val<value_bool>(res); | |
| }}, | |
| {"test_is_sameas", [](const func_args & args) -> value { | |
| // Check if an object points to the same memory address as another object | |
| (void)args; | |
| throw not_implemented_exception("sameas test not implemented"); | |
| }}, | |
| {"test_is_escaped", [](const func_args & args) -> value { | |
| (void)args; | |
| throw not_implemented_exception("escaped test not implemented"); | |
| }}, | |
| {"test_is_filter", [](const func_args & args) -> value { | |
| (void)args; | |
| throw not_implemented_exception("filter test not implemented"); | |
| }}, | |
| }; | |
| return builtins; | |
| } | |
| const func_builtins & value_int_t::get_builtins() const { | |
| static const func_builtins builtins = { | |
| {"default", default_value}, | |
| {"abs", [](const func_args & args) -> value { | |
| args.ensure_vals<value_int>(); | |
| int64_t val = args.get_pos(0)->as_int(); | |
| return mk_val<value_int>(val < 0 ? -val : val); | |
| }}, | |
| {"int", [](const func_args & args) -> value { | |
| args.ensure_vals<value_int>(); | |
| return mk_val<value_int>(args.get_pos(0)->as_int()); | |
| }}, | |
| {"float", [](const func_args & args) -> value { | |
| args.ensure_vals<value_int>(); | |
| double val = static_cast<double>(args.get_pos(0)->as_int()); | |
| return mk_val<value_float>(val); | |
| }}, | |
| {"safe", tojson}, | |
| {"string", tojson}, | |
| {"tojson", tojson}, | |
| }; | |
| return builtins; | |
| } | |
| const func_builtins & value_float_t::get_builtins() const { | |
| static const func_builtins builtins = { | |
| {"default", default_value}, | |
| {"abs", [](const func_args & args) -> value { | |
| args.ensure_vals<value_float>(); | |
| double val = args.get_pos(0)->as_float(); | |
| return mk_val<value_float>(val < 0.0 ? -val : val); | |
| }}, | |
| {"int", [](const func_args & args) -> value { | |
| args.ensure_vals<value_float>(); | |
| int64_t val = static_cast<int64_t>(args.get_pos(0)->as_float()); | |
| return mk_val<value_int>(val); | |
| }}, | |
| {"float", [](const func_args & args) -> value { | |
| args.ensure_vals<value_float>(); | |
| return mk_val<value_float>(args.get_pos(0)->as_float()); | |
| }}, | |
| {"safe", tojson}, | |
| {"string", tojson}, | |
| {"tojson", tojson}, | |
| }; | |
| return builtins; | |
| } | |
| static bool string_startswith(const std::string & str, const std::string & prefix) { | |
| if (str.length() < prefix.length()) return false; | |
| return str.compare(0, prefix.length(), prefix) == 0; | |
| } | |
| static bool string_endswith(const std::string & str, const std::string & suffix) { | |
| if (str.length() < suffix.length()) return false; | |
| return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; | |
| } | |
| [[noreturn]] static value string_join_not_implemented(const func_args &) { | |
| throw not_implemented_exception("String join builtin not implemented"); | |
| } | |
| const func_builtins & value_string_t::get_builtins() const { | |
| static const func_builtins builtins = { | |
| {"default", default_value}, | |
| {"upper", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| jinja::string str = args.get_pos(0)->as_string().uppercase(); | |
| return mk_val<value_string>(str); | |
| }}, | |
| {"lower", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| jinja::string str = args.get_pos(0)->as_string().lowercase(); | |
| return mk_val<value_string>(str); | |
| }}, | |
| {"strip", [](const func_args & args) -> value { | |
| value val_input = args.get_pos(0); | |
| if (!is_val<value_string>(val_input)) { | |
| throw raised_exception("strip() first argument must be a string"); | |
| } | |
| value val_chars = args.get_kwarg_or_pos("chars", 1); | |
| if (val_chars->is_undefined()) { | |
| return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, true)); | |
| } else { | |
| return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, true, val_chars->as_string().str())); | |
| } | |
| }}, | |
| {"rstrip", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| value val_chars = args.get_kwarg_or_pos("chars", 1); | |
| if (val_chars->is_undefined()) { | |
| return mk_val<value_string>(args.get_pos(0)->as_string().strip(false, true)); | |
| } else { | |
| return mk_val<value_string>(args.get_pos(0)->as_string().strip(false, true, val_chars->as_string().str())); | |
| } | |
| }}, | |
| {"lstrip", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| value val_chars = args.get_kwarg_or_pos("chars", 1); | |
| if (val_chars->is_undefined()) { | |
| return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, false)); | |
| } else { | |
| return mk_val<value_string>(args.get_pos(0)->as_string().strip(true, false, val_chars->as_string().str())); | |
| } | |
| }}, | |
| {"title", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| jinja::string str = args.get_pos(0)->as_string().titlecase(); | |
| return mk_val<value_string>(str); | |
| }}, | |
| {"capitalize", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| jinja::string str = args.get_pos(0)->as_string().capitalize(); | |
| return mk_val<value_string>(str); | |
| }}, | |
| {"length", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| jinja::string str = args.get_pos(0)->as_string(); | |
| return mk_val<value_int>(str.length()); | |
| }}, | |
| {"startswith", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string, value_string>(); | |
| std::string str = args.get_pos(0)->as_string().str(); | |
| std::string prefix = args.get_pos(1)->as_string().str(); | |
| return mk_val<value_bool>(string_startswith(str, prefix)); | |
| }}, | |
| {"endswith", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string, value_string>(); | |
| std::string str = args.get_pos(0)->as_string().str(); | |
| std::string suffix = args.get_pos(1)->as_string().str(); | |
| return mk_val<value_bool>(string_endswith(str, suffix)); | |
| }}, | |
| {"split", [](const func_args & args) -> value { | |
| args.ensure_count(1, 3); | |
| value val_input = args.get_pos(0); | |
| if (!is_val<value_string>(val_input)) { | |
| throw raised_exception("split() first argument must be a string"); | |
| } | |
| std::string str = val_input->as_string().str(); | |
| // FIXME: Support non-specified delimiter (split on consecutive (no leading or trailing) whitespace) | |
| std::string delim = (args.count() > 1) ? args.get_pos(1)->as_string().str() : " "; | |
| if (delim.empty()) { | |
| throw raised_exception("empty separator"); | |
| } | |
| int64_t maxsplit = (args.count() > 2) ? args.get_pos(2)->as_int() : -1; | |
| auto result = mk_val<value_array>(); | |
| size_t pos = 0; | |
| std::string token; | |
| while ((pos = str.find(delim)) != std::string::npos && maxsplit != 0) { | |
| token = str.substr(0, pos); | |
| result->push_back(mk_val<value_string>(token)); | |
| str.erase(0, pos + delim.length()); | |
| --maxsplit; | |
| } | |
| auto res = mk_val<value_string>(str); | |
| res->val_str.mark_input_based_on(args.get_pos(0)->val_str); | |
| result->push_back(std::move(res)); | |
| return result; | |
| }}, | |
| {"rsplit", [](const func_args & args) -> value { | |
| args.ensure_count(1, 3); | |
| value val_input = args.get_pos(0); | |
| if (!is_val<value_string>(val_input)) { | |
| throw raised_exception("rsplit() first argument must be a string"); | |
| } | |
| std::string str = val_input->as_string().str(); | |
| // FIXME: Support non-specified delimiter (split on consecutive (no leading or trailing) whitespace) | |
| std::string delim = (args.count() > 1) ? args.get_pos(1)->as_string().str() : " "; | |
| if (delim.empty()) { | |
| throw raised_exception("empty separator"); | |
| } | |
| int64_t maxsplit = (args.count() > 2) ? args.get_pos(2)->as_int() : -1; | |
| auto result = mk_val<value_array>(); | |
| size_t pos = 0; | |
| std::string token; | |
| while ((pos = str.rfind(delim)) != std::string::npos && maxsplit != 0) { | |
| token = str.substr(pos + delim.length()); | |
| result->push_back(mk_val<value_string>(token)); | |
| str.erase(pos); | |
| --maxsplit; | |
| } | |
| auto res = mk_val<value_string>(str); | |
| res->val_str.mark_input_based_on(args.get_pos(0)->val_str); | |
| result->push_back(std::move(res)); | |
| result->reverse(); | |
| return result; | |
| }}, | |
| {"replace", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string, value_string, value_string, value_int>(true, true, true, false); | |
| std::string str = args.get_pos(0)->as_string().str(); | |
| std::string old_str = args.get_pos(1)->as_string().str(); | |
| std::string new_str = args.get_pos(2)->as_string().str(); | |
| int64_t count = args.count() > 3 ? args.get_pos(3)->as_int() : -1; | |
| if (count > 0) { | |
| throw not_implemented_exception("String replace with count argument not implemented"); | |
| } | |
| if (old_str != new_str) { | |
| size_t pos = 0; | |
| if (old_str.empty()) { | |
| std::string new_res; | |
| new_res.reserve(str.length() + new_str.length() * (str.length() + 1)); | |
| new_res += new_str; | |
| for (const char c : str) { | |
| new_res.push_back(c); | |
| new_res += new_str; | |
| } | |
| str = new_res; | |
| } else { | |
| while ((pos = str.find(old_str, pos)) != std::string::npos) { | |
| str.replace(pos, old_str.length(), new_str); | |
| pos += new_str.length(); | |
| } | |
| } | |
| } | |
| auto res = mk_val<value_string>(str); | |
| res->val_str.mark_input_based_on(args.get_pos(0)->val_str); | |
| return res; | |
| }}, | |
| {"int", [](const func_args & args) -> value { | |
| value val_input = args.get_pos(0); | |
| value val_default = args.get_kwarg_or_pos("default", 1); | |
| value val_base = args.get_kwarg_or_pos("base", 2); | |
| const int base = val_base->is_undefined() ? 10 : val_base->as_int(); | |
| if (is_val<value_string>(val_input) == false) { | |
| throw raised_exception("int() first argument must be a string"); | |
| } | |
| std::string str = val_input->as_string().str(); | |
| try { | |
| return mk_val<value_int>(std::stoi(str, nullptr, base)); | |
| } catch (...) { | |
| return mk_val<value_int>(val_default->is_undefined() ? 0 : val_default->as_int()); | |
| } | |
| }}, | |
| {"float", [](const func_args & args) -> value { | |
| args.ensure_vals<value_string>(); | |
| value val_default = args.get_kwarg_or_pos("default", 1); | |
| std::string str = args.get_pos(0)->as_string().str(); | |
| try { | |
| return mk_val<value_float>(std::stod(str)); | |
| } catch (...) { | |
| return mk_val<value_float>(val_default->is_undefined() ? 0.0 : val_default->as_float()); | |
| } | |
| }}, | |
| {"string", [](const func_args & args) -> value { | |
| // no-op | |
| args.ensure_vals<value_string>(); | |
| return mk_val<value_string>(args.get_pos(0)->as_string()); | |
| }}, | |
| {"default", [](const func_args & args) -> value { | |
| value input = args.get_pos(0); | |
| if (!is_val<value_string>(input)) { | |
| throw raised_exception("default() first argument must be a string"); | |
| } | |
| value default_val = mk_val<value_string>(""); | |
| if (args.count() > 1 && !args.get_pos(1)->is_undefined()) { | |
| default_val = args.get_pos(1); | |
| } | |
| value boolean_val = args.get_kwarg_or_pos("boolean", 2); // undefined == false | |
| if (input->is_undefined() || (boolean_val->as_bool() && !input->as_bool())) { | |
| return default_val; | |
| } else { | |
| return input; | |
| } | |
| }}, | |
| {"slice", [](const func_args & args) -> value { | |
| args.ensure_count(1, 4); | |
| args.ensure_vals<value_string, value_int, value_int, value_int>(true, true, false, false); | |
| auto arg0 = args.get_pos(1); | |
| auto arg1 = args.get_pos(2, mk_val<value_undefined>()); | |
| auto arg2 = args.get_pos(3, mk_val<value_undefined>()); | |
| int64_t start, stop, step; | |
| if (args.count() == 1) { | |
| start = 0; | |
| stop = arg0->as_int(); | |
| step = 1; | |
| } else if (args.count() == 2) { | |
| start = arg0->as_int(); | |
| stop = arg1->as_int(); | |
| step = 1; | |
| } else { | |
| start = arg0->as_int(); | |
| stop = arg1->as_int(); | |
| step = arg2->as_int(); | |
| } | |
| if (step == 0) { | |
| throw raised_exception("slice step cannot be zero"); | |
| } | |
| auto input = args.get_pos(0); | |
| auto sliced = slice(input->as_string().str(), start, stop, step); | |
| auto res = mk_val<value_string>(sliced); | |
| res->val_str.mark_input_based_on(input->as_string()); | |
| return res; | |
| }}, | |
| {"safe", [](const func_args & args) -> value { | |
| // no-op for now | |
| args.ensure_vals<value_string>(); | |
| return args.get_pos(0); | |
| }}, | |
| {"tojson", tojson}, | |
| {"indent", [](const func_args &args) -> value { | |
| args.ensure_count(1, 4); | |
| value val_input = args.get_pos(0); | |
| value val_width = args.get_kwarg_or_pos("width", 1); | |
| const bool first = args.get_kwarg_or_pos("first", 2)->as_bool(); // undefined == false | |
| const bool blank = args.get_kwarg_or_pos("blank", 3)->as_bool(); // undefined == false | |
| if (!is_val<value_string>(val_input)) { | |
| throw raised_exception("indent() first argument must be a string"); | |
| } | |
| std::string indent; | |
| if (is_val<value_int>(val_width)) { | |
| indent.assign(val_width->as_int(), ' '); | |
| } else if (is_val<value_string>(val_width)) { | |
| indent = val_width->as_string().str(); | |
| } else { | |
| indent = " "; | |
| } | |
| std::string indented; | |
| std::string input = val_input->as_string().str(); | |
| std::istringstream iss = std::istringstream(input); | |
| std::string line; | |
| while (std::getline(iss, line)) { | |
| if (!indented.empty()) { | |
| indented.push_back('\n'); | |
| } | |
| if ((indented.empty() ? first : (!line.empty() || blank))) { | |
| indented += indent; | |
| } | |
| indented += line; | |
| } | |
| if (!input.empty() && input.back() == '\n') { | |
| indented.push_back('\n'); | |
| if (blank) { | |
| indented += indent; | |
| } | |
| } | |
| auto res = mk_val<value_string>(indented); | |
| res->val_str.mark_input_based_on(val_input->as_string()); | |
| return res; | |
| }}, | |
| {"join", string_join_not_implemented}, | |
| }; | |
| return builtins; | |
| } | |
| const func_builtins & value_bool_t::get_builtins() const { | |
| static const func_handler tostring = [](const func_args & args) -> value { | |
| args.ensure_vals<value_bool>(); | |
| bool val = args.get_pos(0)->as_bool(); | |
| return mk_val<value_string>(val ? "True" : "False"); | |
| }; | |
| static const func_builtins builtins = { | |
| {"default", default_value}, | |
| {"int", [](const func_args & args) -> value { | |
| args.ensure_vals<value_bool>(); | |
| bool val = args.get_pos(0)->as_bool(); | |
| return mk_val<value_int>(val ? 1 : 0); | |
| }}, | |
| {"float", [](const func_args & args) -> value { | |
| args.ensure_vals<value_bool>(); | |
| bool val = args.get_pos(0)->as_bool(); | |
| return mk_val<value_float>(val ? 1.0 : 0.0); | |
| }}, | |
| {"safe", tostring}, | |
| {"string", tostring}, | |
| {"tojson", tojson}, | |
| }; | |
| return builtins; | |
| } | |
| [[noreturn]] static value array_unique_not_implemented(const func_args &) { | |
| throw not_implemented_exception("Array unique builtin not implemented"); | |
| } | |
| const func_builtins & value_array_t::get_builtins() const { | |
| static const func_builtins builtins = { | |
| {"default", default_value}, | |
| {"list", [](const func_args & args) -> value { | |
| args.ensure_vals<value_array>(); | |
| const auto & arr = args.get_pos(0)->as_array(); | |
| auto result = mk_val<value_array>(); | |
| for (const auto& v : arr) { | |
| result->push_back(v); | |
| } | |
| return result; | |
| }}, | |
| {"first", [](const func_args & args) -> value { | |
| args.ensure_vals<value_array>(); | |
| const auto & arr = args.get_pos(0)->as_array(); | |
| if (arr.empty()) { | |
| return mk_val<value_undefined>(); | |
| } | |
| return arr[0]; | |
| }}, | |
| {"last", [](const func_args & args) -> value { | |
| args.ensure_vals<value_array>(); | |
| const auto & arr = args.get_pos(0)->as_array(); | |
| if (arr.empty()) { | |
| return mk_val<value_undefined>(); | |
| } | |
| return arr[arr.size() - 1]; | |
| }}, | |
| {"length", [](const func_args & args) -> value { | |
| args.ensure_vals<value_array>(); | |
| const auto & arr = args.get_pos(0)->as_array(); | |
| return mk_val<value_int>(static_cast<int64_t>(arr.size())); | |
| }}, | |
| {"slice", [](const func_args & args) -> value { | |
| args.ensure_count(1, 4); | |
| args.ensure_vals<value_array, value_int, value_int, value_int>(true, true, false, false); | |
| auto val = args.get_pos(0); | |
| auto arg0 = args.get_pos(1); | |
| auto arg1 = args.get_pos(2, mk_val<value_undefined>()); | |
| auto arg2 = args.get_pos(3, mk_val<value_undefined>()); | |
| int64_t start, stop, step; | |
| if (args.count() == 1) { | |
| start = 0; | |
| stop = arg0->as_int(); | |
| step = 1; | |
| } else if (args.count() == 2) { | |
| start = arg0->as_int(); | |
| stop = arg1->as_int(); | |
| step = 1; | |
| } else { | |
| start = arg0->as_int(); | |
| stop = arg1->as_int(); | |
| step = arg2->as_int(); | |
| } | |
| if (step == 0) { | |
| throw raised_exception("slice step cannot be zero"); | |
| } | |
| auto arr = slice(val->as_array(), start, stop, step); | |
| return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr)); | |
| }}, | |
| {"selectattr", selectattr<false>}, | |
| {"select", selectattr<false>}, | |
| {"rejectattr", selectattr<true>}, | |
| {"reject", selectattr<true>}, | |
| {"join", [](const func_args & args) -> value { | |
| args.ensure_count(1, 3); | |
| if (!is_val<value_array>(args.get_pos(0))) { | |
| throw raised_exception("join() first argument must be an array"); | |
| } | |
| value val_delim = args.get_kwarg_or_pos("d", 1); | |
| value attribute = args.get_kwarg_or_pos("attribute", 2); | |
| const auto & arr = args.get_pos(0)->as_array(); | |
| const bool attr_is_int = is_val<value_int>(attribute); | |
| if (!attribute->is_undefined() && !is_val<value_string>(attribute) && !attr_is_int) { | |
| throw raised_exception("join() attribute must be string or integer"); | |
| } | |
| const int64_t attr_int = attr_is_int ? attribute->as_int() : 0; | |
| const std::string delim = val_delim->is_undefined() ? "" : val_delim->as_string().str(); | |
| std::string result; | |
| for (size_t i = 0; i < arr.size(); ++i) { | |
| value val_arr = arr[i]; | |
| if (!attribute->is_undefined()) { | |
| if (attr_is_int && is_val<value_array>(val_arr)) { | |
| val_arr = val_arr->at(attr_int); | |
| } else if (!attr_is_int && is_val<value_object>(val_arr)) { | |
| val_arr = val_arr->at(attribute); | |
| } | |
| } | |
| if (!is_val<value_string>(val_arr) && !is_val<value_int>(val_arr) && !is_val<value_float>(val_arr)) { | |
| throw raised_exception("join() can only join arrays of strings or numerics"); | |
| } | |
| result += val_arr->as_string().str(); | |
| if (i < arr.size() - 1) { | |
| result += delim; | |
| } | |
| } | |
| return mk_val<value_string>(result); | |
| }}, | |
| {"string", [](const func_args & args) -> value { | |
| args.ensure_vals<value_array>(); | |
| if (args.ctx.is_get_stats) { | |
| // mark as used (recursively) for stats | |
| auto val_input = args.get_pos(0); | |
| value_t::stats_t::mark_used(const_cast<value&>(val_input), true); | |
| } | |
| return mk_val<value_string>(args.get_pos(0)->as_string()); | |
| }}, | |
| {"tojson", tojson}, | |
| {"map", [](const func_args & args) -> value { | |
| args.ensure_count(2); | |
| if (!is_val<value_array>(args.get_pos(0))) { | |
| throw raised_exception("map: first argument must be an array"); | |
| } | |
| if (!is_val<value_kwarg>(args.get_args().at(1))) { | |
| throw not_implemented_exception("map: filter-mapping not implemented"); | |
| } | |
| value val = args.get_pos(0); | |
| value attribute = args.get_kwarg_or_pos("attribute", 1); | |
| const bool attr_is_int = is_val<value_int>(attribute); | |
| if (!is_val<value_string>(attribute) && !attr_is_int) { | |
| throw raised_exception("map: attribute must be string or integer"); | |
| } | |
| const int64_t attr_int = attr_is_int ? attribute->as_int() : 0; | |
| value default_val = args.get_kwarg("default", mk_val<value_undefined>()); | |
| auto out = mk_val<value_array>(); | |
| auto arr = val->as_array(); | |
| for (const auto & item : arr) { | |
| value attr_val; | |
| if (attr_is_int) { | |
| attr_val = is_val<value_array>(item) ? item->at(attr_int, default_val) : default_val; | |
| } else { | |
| attr_val = is_val<value_object>(item) ? item->at(attribute, default_val) : default_val; | |
| } | |
| out->push_back(attr_val); | |
| } | |
| return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(out->as_array())) : out; | |
| }}, | |
| {"append", [](const func_args & args) -> value { | |
| args.ensure_count(2); | |
| if (!is_val<value_array>(args.get_pos(0))) { | |
| throw raised_exception("append: first argument must be an array"); | |
| } | |
| const value_array_t * arr = cast_val<value_array>(args.get_pos(0)); | |
| // need to use const_cast here to modify the array | |
| value_array_t * arr_editable = const_cast<value_array_t *>(arr); | |
| arr_editable->push_back(args.get_pos(1)); | |
| return args.get_pos(0); | |
| }}, | |
| {"pop", [](const func_args & args) -> value { | |
| args.ensure_count(1, 2); | |
| args.ensure_vals<value_array, value_int>(true, false); | |
| int64_t index = args.count() == 2 ? args.get_pos(1)->as_int() : -1; | |
| const value_array_t * arr = cast_val<value_array>(args.get_pos(0)); | |
| // need to use const_cast here to modify the array | |
| value_array_t * arr_editable = const_cast<value_array_t *>(arr); | |
| return arr_editable->pop_at(index); | |
| }}, | |
| {"sort", [](const func_args & args) -> value { | |
| args.ensure_count(1, 4); | |
| if (!is_val<value_array>(args.get_pos(0))) { | |
| throw raised_exception("sort: first argument must be an array"); | |
| } | |
| value val = args.get_pos(0); | |
| value val_reverse = args.get_kwarg_or_pos("reverse", 1); | |
| value val_case = args.get_kwarg_or_pos("case_sensitive", 2); | |
| value attribute = args.get_kwarg_or_pos("attribute", 3); | |
| // FIXME: sorting is currently always case sensitive | |
| //const bool case_sensitive = val_case->as_bool(); // undefined == false | |
| const bool reverse = val_reverse->as_bool(); // undefined == false | |
| const bool attr_is_int = is_val<value_int>(attribute); | |
| const int64_t attr_int = attr_is_int ? attribute->as_int() : 0; | |
| std::vector<value> arr = val->as_array(); // copy | |
| std::sort(arr.begin(), arr.end(),[&](const value & a, const value & b) { | |
| value val_a = a; | |
| value val_b = b; | |
| if (!attribute->is_undefined()) { | |
| if (attr_is_int && is_val<value_array>(a) && is_val<value_array>(b)) { | |
| val_a = a->at(attr_int); | |
| val_b = b->at(attr_int); | |
| } else if (!attr_is_int && is_val<value_object>(a) && is_val<value_object>(b)) { | |
| val_a = a->at(attribute); | |
| val_b = b->at(attribute); | |
| } else { | |
| throw raised_exception("sort: unsupported object attribute comparison between " + a->type() + " and " + b->type()); | |
| } | |
| } | |
| return value_compare(val_a, val_b, reverse ? value_compare_op::gt : value_compare_op::lt); | |
| }); | |
| return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr)); | |
| }}, | |
| {"reverse", [](const func_args & args) -> value { | |
| args.ensure_vals<value_array>(); | |
| value val = args.get_pos(0); | |
| std::vector<value> arr = val->as_array(); // copy | |
| std::reverse(arr.begin(), arr.end()); | |
| return is_val<value_tuple>(val) ? mk_val<value_tuple>(std::move(arr)) : mk_val<value_array>(std::move(arr)); | |
| }}, | |
| {"min", [](const func_args & args) -> value { | |
| args.ensure_count(1, 4); | |
| args.ensure_vals<value_array>(); | |
| value val_case = args.get_kwarg_or_pos("case_sensitive", 1); | |
| value attribute = args.get_kwarg_or_pos("attribute", 2); | |
| if (!attribute->is_undefined()) { | |
| throw not_implemented_exception("min: attribute not implemented"); | |
| } | |
| // FIXME: min is currently always case sensitive | |
| (void) val_case; | |
| const auto & arr = args.get_pos(0)->as_array(); | |
| if (arr.empty()) { | |
| return mk_val<value_undefined>(); | |
| } | |
| value result = arr[0]; | |
| for (size_t i = 1; i < arr.size(); ++i) { | |
| if (value_compare(arr[i], result, value_compare_op::lt)) { | |
| result = arr[i]; | |
| } | |
| } | |
| return result; | |
| }}, | |
| {"max", [](const func_args & args) -> value { | |
| args.ensure_count(1, 4); | |
| args.ensure_vals<value_array>(); | |
| value val_case = args.get_kwarg_or_pos("case_sensitive", 1); | |
| value attribute = args.get_kwarg_or_pos("attribute", 2); | |
| if (!attribute->is_undefined()) { | |
| throw not_implemented_exception("max: attribute not implemented"); | |
| } | |
| // FIXME: max is currently always case sensitive | |
| (void) val_case; | |
| const auto & arr = args.get_pos(0)->as_array(); | |
| if (arr.empty()) { | |
| return mk_val<value_undefined>(); | |
| } | |
| value result = arr[0]; | |
| for (size_t i = 1; i < arr.size(); ++i) { | |
| if (value_compare(arr[i], result, value_compare_op::gt)) { | |
| result = arr[i]; | |
| } | |
| } | |
| return result; | |
| }}, | |
| {"unique", array_unique_not_implemented}, | |
| }; | |
| return builtins; | |
| } | |
| [[noreturn]] static value object_join_not_implemented(const func_args &) { | |
| throw not_implemented_exception("object join not implemented"); | |
| } | |
| const func_builtins & value_object_t::get_builtins() const { | |
| if (!has_builtins) { | |
| static const func_builtins no_builtins = {}; | |
| return no_builtins; | |
| } | |
| static const func_builtins builtins = { | |
| // {"default", default_value}, // cause issue with gpt-oss | |
| {"get", [](const func_args & args) -> value { | |
| args.ensure_count(2, 3); | |
| if (!is_val<value_object>(args.get_pos(0))) { | |
| throw raised_exception("get: first argument must be an object"); | |
| } | |
| if (!is_val<value_string>(args.get_pos(1))) { | |
| throw raised_exception("get: second argument must be a string (key)"); | |
| } | |
| value default_val = mk_val<value_none>(); | |
| if (args.count() == 3) { | |
| default_val = args.get_pos(2); | |
| } | |
| const value obj = args.get_pos(0); | |
| const value key = args.get_pos(1); | |
| return obj->at(key, default_val); | |
| }}, | |
| {"keys", [](const func_args & args) -> value { | |
| args.ensure_vals<value_object>(); | |
| const auto & obj = args.get_pos(0)->as_ordered_object(); | |
| auto result = mk_val<value_array>(); | |
| for (const auto & pair : obj) { | |
| result->push_back(pair.first); | |
| } | |
| return result; | |
| }}, | |
| {"values", [](const func_args & args) -> value { | |
| args.ensure_vals<value_object>(); | |
| const auto & obj = args.get_pos(0)->as_ordered_object(); | |
| auto result = mk_val<value_array>(); | |
| for (const auto & pair : obj) { | |
| result->push_back(pair.second); | |
| } | |
| return result; | |
| }}, | |
| {"items", [](const func_args & args) -> value { | |
| args.ensure_vals<value_object>(); | |
| const auto & obj = args.get_pos(0)->as_ordered_object(); | |
| auto result = mk_val<value_array>(); | |
| for (const auto & pair : obj) { | |
| auto item = mk_val<value_tuple>(pair); | |
| result->push_back(std::move(item)); | |
| } | |
| return result; | |
| }}, | |
| {"tojson", tojson}, | |
| {"string", [](const func_args & args) -> value { | |
| args.ensure_vals<value_object>(); | |
| if (args.ctx.is_get_stats) { | |
| // mark as used (recursively) for stats | |
| auto val_input = args.get_pos(0); | |
| value_t::stats_t::mark_used(const_cast<value&>(val_input), true); | |
| } | |
| return mk_val<value_string>(args.get_pos(0)->as_string()); | |
| }}, | |
| {"length", [](const func_args & args) -> value { | |
| args.ensure_vals<value_object>(); | |
| const auto & obj = args.get_pos(0)->as_ordered_object(); | |
| return mk_val<value_int>(static_cast<int64_t>(obj.size())); | |
| }}, | |
| {"tojson", [](const func_args & args) -> value { | |
| args.ensure_vals<value_object>(); | |
| // use global to_json | |
| return global_builtins().at("tojson")(args); | |
| }}, | |
| {"dictsort", [](const func_args & args) -> value { | |
| value val_input = args.get_pos(0); | |
| value val_case = args.get_kwarg_or_pos("case_sensitive", 1); | |
| value val_by = args.get_kwarg_or_pos("by", 2); | |
| value val_reverse = args.get_kwarg_or_pos("reverse", 3); | |
| // FIXME: sorting is currently always case sensitive | |
| //const bool case_sensitive = val_case->as_bool(); // undefined == false | |
| const bool reverse = val_reverse->as_bool(); // undefined == false | |
| const bool by_value = is_val<value_string>(val_by) && val_by->as_string().str() == "value" ? true : false; | |
| auto result = mk_val<value_object>(val_input); // copy | |
| std::sort(result->val_obj.begin(), result->val_obj.end(), [&](const auto & a, const auto & b) { | |
| if (by_value) { | |
| return value_compare(a.second, b.second, reverse ? value_compare_op::gt : value_compare_op::lt); | |
| } else { | |
| return value_compare(a.first, b.first, reverse ? value_compare_op::gt : value_compare_op::lt); | |
| } | |
| }); | |
| return result; | |
| }}, | |
| {"join", object_join_not_implemented}, | |
| }; | |
| return builtins; | |
| } | |
| const func_builtins & value_none_t::get_builtins() const { | |
| static const func_handler tostring = [](const func_args &) -> value { | |
| return mk_val<value_string>("None"); | |
| }; | |
| static const func_builtins builtins = { | |
| {"default", default_value}, | |
| {"tojson", tojson}, | |
| {"string", tostring}, | |
| {"safe", tostring}, | |
| {"items", empty_value_fn<value_array>}, | |
| {"map", empty_value_fn<value_array>}, | |
| {"reject", empty_value_fn<value_array>}, | |
| {"rejectattr", empty_value_fn<value_array>}, | |
| {"select", empty_value_fn<value_array>}, | |
| {"selectattr", empty_value_fn<value_array>}, | |
| {"unique", empty_value_fn<value_array>}, | |
| }; | |
| return builtins; | |
| } | |
| const func_builtins & value_undefined_t::get_builtins() const { | |
| static const func_builtins builtins = { | |
| {"default", default_value}, | |
| {"capitalize", empty_value_fn<value_string>}, | |
| {"first", empty_value_fn<value_undefined>}, | |
| {"items", empty_value_fn<value_array>}, | |
| {"join", empty_value_fn<value_string>}, | |
| {"last", empty_value_fn<value_undefined>}, | |
| {"length", empty_value_fn<value_int>}, | |
| {"list", empty_value_fn<value_array>}, | |
| {"lower", empty_value_fn<value_string>}, | |
| {"map", empty_value_fn<value_array>}, | |
| {"max", empty_value_fn<value_undefined>}, | |
| {"min", empty_value_fn<value_undefined>}, | |
| {"reject", empty_value_fn<value_array>}, | |
| {"rejectattr", empty_value_fn<value_array>}, | |
| {"replace", empty_value_fn<value_string>}, | |
| {"reverse", empty_value_fn<value_array>}, | |
| {"safe", empty_value_fn<value_string>}, | |
| {"select", empty_value_fn<value_array>}, | |
| {"selectattr", empty_value_fn<value_array>}, | |
| {"sort", empty_value_fn<value_array>}, | |
| {"string", empty_value_fn<value_string>}, | |
| {"strip", empty_value_fn<value_string>}, | |
| {"sum", empty_value_fn<value_int>}, | |
| {"title", empty_value_fn<value_string>}, | |
| {"truncate", empty_value_fn<value_string>}, | |
| {"unique", empty_value_fn<value_array>}, | |
| {"upper", empty_value_fn<value_string>}, | |
| {"wordcount", empty_value_fn<value_int>}, | |
| }; | |
| return builtins; | |
| } | |
| ////////////////////////////////// | |
| static value from_json(const nlohmann::ordered_json & j, bool mark_input) { | |
| if (j.is_null()) { | |
| return mk_val<value_none>(); | |
| } else if (j.is_boolean()) { | |
| return mk_val<value_bool>(j.get<bool>()); | |
| } else if (j.is_number_integer()) { | |
| return mk_val<value_int>(j.get<int64_t>()); | |
| } else if (j.is_number_float()) { | |
| return mk_val<value_float>(j.get<double>()); | |
| } else if (j.is_string()) { | |
| auto str = mk_val<value_string>(j.get<std::string>()); | |
| if (mark_input) { | |
| str->mark_input(); | |
| } | |
| return str; | |
| } else if (j.is_array()) { | |
| auto arr = mk_val<value_array>(); | |
| for (const auto & item : j) { | |
| arr->push_back(from_json(item, mark_input)); | |
| } | |
| return arr; | |
| } else if (j.is_object()) { | |
| auto obj = mk_val<value_object>(); | |
| for (auto it = j.begin(); it != j.end(); ++it) { | |
| obj->insert(it.key(), from_json(it.value(), mark_input)); | |
| } | |
| return obj; | |
| } else { | |
| throw std::runtime_error("Unsupported JSON value type"); | |
| } | |
| } | |
| // compare operator for value_t | |
| bool value_compare(const value & a, const value & b, value_compare_op op) { | |
| auto cmp = [&]() { | |
| // compare numeric types | |
| if ((is_val<value_int>(a) || is_val<value_float>(a)) && | |
| (is_val<value_int>(b) || is_val<value_float>(b))){ | |
| try { | |
| if (op == value_compare_op::eq) { | |
| return a->as_float() == b->as_float(); | |
| } else if (op == value_compare_op::ge) { | |
| return a->as_float() >= b->as_float(); | |
| } else if (op == value_compare_op::gt) { | |
| return a->as_float() > b->as_float(); | |
| } else if (op == value_compare_op::lt) { | |
| return a->as_float() < b->as_float(); | |
| } else if (op == value_compare_op::ne) { | |
| return a->as_float() != b->as_float(); | |
| } else { | |
| throw std::runtime_error("Unsupported comparison operator for numeric types"); | |
| } | |
| } catch (...) {} | |
| } | |
| // compare string and number | |
| // TODO: not sure if this is the right behavior | |
| if ((is_val<value_string>(b) && (is_val<value_int>(a) || is_val<value_float>(a))) || | |
| (is_val<value_string>(a) && (is_val<value_int>(b) || is_val<value_float>(b))) || | |
| (is_val<value_string>(a) && is_val<value_string>(b))) { | |
| try { | |
| if (op == value_compare_op::eq) { | |
| return a->as_string().str() == b->as_string().str(); | |
| } else if (op == value_compare_op::ge) { | |
| return a->as_string().str() >= b->as_string().str(); | |
| } else if (op == value_compare_op::gt) { | |
| return a->as_string().str() > b->as_string().str(); | |
| } else if (op == value_compare_op::lt) { | |
| return a->as_string().str() < b->as_string().str(); | |
| } else if (op == value_compare_op::ne) { | |
| return a->as_string().str() != b->as_string().str(); | |
| } else { | |
| throw std::runtime_error("Unsupported comparison operator for string/number types"); | |
| } | |
| } catch (...) {} | |
| } | |
| // compare boolean simple | |
| if (is_val<value_bool>(a) && is_val<value_bool>(b)) { | |
| if (op == value_compare_op::eq) { | |
| return a->as_bool() == b->as_bool(); | |
| } else if (op == value_compare_op::ne) { | |
| return a->as_bool() != b->as_bool(); | |
| } else { | |
| throw std::runtime_error("Unsupported comparison operator for bool type"); | |
| } | |
| } | |
| // compare by type | |
| if (a->type() != b->type()) { | |
| return false; | |
| } | |
| return false; | |
| }; | |
| auto result = cmp(); | |
| JJ_DEBUG("Comparing types: %s and %s result=%d", a->type().c_str(), b->type().c_str(), result); | |
| return result; | |
| } | |
| template<> | |
| void global_from_json(context & ctx, const nlohmann::ordered_json & json_obj, bool mark_input) { | |
| // printf("global_from_json: %s\n" , json_obj.dump(2).c_str()); | |
| if (json_obj.is_null() || !json_obj.is_object()) { | |
| throw std::runtime_error("global_from_json: input JSON value must be an object"); | |
| } | |
| for (auto it = json_obj.begin(); it != json_obj.end(); ++it) { | |
| JJ_DEBUG("global_from_json: setting key '%s'", it.key().c_str()); | |
| ctx.set_val(it.key(), from_json(it.value(), mark_input)); | |
| } | |
| } | |
| // recursively convert value to JSON string | |
| // TODO: avoid circular references | |
| static void value_to_json_internal(std::ostringstream & oss, const value & val, int curr_lvl, int indent, const std::string_view item_sep, const std::string_view key_sep) { | |
| auto indent_str = [indent, curr_lvl]() -> std::string { | |
| return (indent > 0) ? std::string(curr_lvl * indent, ' ') : ""; | |
| }; | |
| auto newline = [indent]() -> std::string { | |
| return (indent >= 0) ? "\n" : ""; | |
| }; | |
| if (is_val<value_none>(val) || val->is_undefined()) { | |
| oss << "null"; | |
| } else if (is_val<value_bool>(val)) { | |
| oss << (val->as_bool() ? "true" : "false"); | |
| } else if (is_val<value_int>(val)) { | |
| oss << val->as_int(); | |
| } else if (is_val<value_float>(val)) { | |
| oss << val->as_float(); | |
| } else if (is_val<value_string>(val)) { | |
| oss << "\""; | |
| for (char c : val->as_string().str()) { | |
| switch (c) { | |
| case '"': oss << "\\\""; break; | |
| case '\\': oss << "\\\\"; break; | |
| case '\b': oss << "\\b"; break; | |
| case '\f': oss << "\\f"; break; | |
| case '\n': oss << "\\n"; break; | |
| case '\r': oss << "\\r"; break; | |
| case '\t': oss << "\\t"; break; | |
| default: | |
| if (static_cast<unsigned char>(c) < 0x20) { | |
| char buf[7]; | |
| snprintf(buf, sizeof(buf), "\\u%04x", static_cast<unsigned char>(c)); | |
| oss << buf; | |
| } else { | |
| oss << c; | |
| } | |
| } | |
| } | |
| oss << "\""; | |
| } else if (is_val<value_array>(val)) { | |
| const auto & arr = val->as_array(); | |
| oss << "["; | |
| if (!arr.empty()) { | |
| oss << newline(); | |
| for (size_t i = 0; i < arr.size(); ++i) { | |
| oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : ""); | |
| value_to_json_internal(oss, arr[i], curr_lvl + 1, indent, item_sep, key_sep); | |
| if (i < arr.size() - 1) { | |
| oss << item_sep; | |
| } | |
| oss << newline(); | |
| } | |
| oss << indent_str(); | |
| } | |
| oss << "]"; | |
| } else if (is_val<value_object>(val)) { | |
| const auto & obj = val->as_ordered_object(); // IMPORTANT: need to keep exact order | |
| oss << "{"; | |
| if (!obj.empty()) { | |
| oss << newline(); | |
| size_t i = 0; | |
| for (const auto & pair : obj) { | |
| oss << indent_str() << (indent > 0 ? std::string(indent, ' ') : ""); | |
| value_to_json_internal(oss, mk_val<value_string>(pair.first->as_string().str()), curr_lvl + 1, indent, item_sep, key_sep); | |
| oss << key_sep; | |
| value_to_json_internal(oss, pair.second, curr_lvl + 1, indent, item_sep, key_sep); | |
| if (i < obj.size() - 1) { | |
| oss << item_sep; | |
| } | |
| oss << newline(); | |
| ++i; | |
| } | |
| oss << indent_str(); | |
| } | |
| oss << "}"; | |
| } else { | |
| oss << "null"; | |
| } | |
| } | |
| std::string value_to_json(const value & val, int indent, const std::string_view item_sep, const std::string_view key_sep) { | |
| std::ostringstream oss; | |
| value_to_json_internal(oss, val, 0, indent, item_sep, key_sep); | |
| JJ_DEBUG("value_to_json: result=%s", oss.str().c_str()); | |
| return oss.str(); | |
| } | |
| // TODO: avoid circular references | |
| std::string value_to_string_repr(const value & val) { | |
| if (is_val<value_string>(val)) { | |
| const std::string val_str = val->as_string().str(); | |
| if (val_str.find('\'') != std::string::npos) { | |
| return value_to_json(val); | |
| } else { | |
| return "'" + val_str + "'"; | |
| } | |
| } else { | |
| return val->as_repr(); | |
| } | |
| } | |
| // stats utility | |
| void value_t::stats_t::mark_used(value & val, bool deep) { | |
| val->stats.used = true; | |
| if (deep) { | |
| if (is_val<value_array>(val)) { | |
| for (auto & item : val->val_arr) { | |
| mark_used(item, deep); | |
| } | |
| } else if (is_val<value_object>(val)) { | |
| for (auto & pair : val->val_obj) { | |
| mark_used(pair.first, deep); | |
| mark_used(pair.second, deep); | |
| } | |
| } | |
| } | |
| } | |
| } // namespace jinja | |