Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ target_sources(simfil PUBLIC

include/simfil/model/arena.h
include/simfil/model/column.h
include/simfil/model/memory.h
include/simfil/model/string-pool.h
include/simfil/model/model.h
include/simfil/model/nodes.h
Expand Down
31 changes: 31 additions & 0 deletions docs/simfil-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,37 @@ sum(range(1, 10)..., $sum * $val, 1) => 3628800
sum(list, #$sum > 0 and $sum + ', ' + $val or $val, '')
```

#### `min(values...)`

Returns the smallest non-null value produced by its arguments. Missing and null
values are ignored, and arguments may produce several values. If no argument
produces a value, the result is null. Values are compared with the ordinary `<`
operator and should therefore be mutually comparable.

A final literal participates in the comparison like every other value. It can
therefore provide both a fallback for missing paths and an upper bound.

*Example*
```
min(8, 3, 5) => 3
min(optional.rank, 7) => 7 # when optional.rank is absent
```

#### `max(values...)`

Returns the largest non-null value produced by its arguments. Missing and null
values are ignored, and arguments may produce several values. If no argument
produces a value, the result is null. Values are compared with the ordinary `>`
operator and should therefore be mutually comparable.

A final literal can provide both a fallback for missing paths and a lower
bound.

*Example*
```
max(8, 3, 5) => 8
max(optional.rank, 0) => 0 # when optional.rank is absent
```

#### `keys(object)`

Expand Down
24 changes: 24 additions & 0 deletions include/simfil/function.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,30 @@ class SumFn : public Function
auto eval(Context, const Value&, const std::vector<ExprPtr>&, const ResultFn&) const -> tl::expected<Result, Error> override;
};

/** Returns the smallest non-null value emitted by its argument expressions. */
class MinFn : public Function
{
public:
static MinFn Fn;

MinFn();

auto ident() const -> const FnInfo& override;
auto eval(Context, const Value&, const std::vector<ExprPtr>&, const ResultFn&) const -> tl::expected<Result, Error> override;
};

/** Returns the largest non-null value emitted by its argument expressions. */
class MaxFn : public Function
{
public:
static MaxFn Fn;

MaxFn();

auto ident() const -> const FnInfo& override;
auto eval(Context, const Value&, const std::vector<ExprPtr>&, const ResultFn&) const -> tl::expected<Result, Error> override;
};

class KeysFn : public Function
{
public:
Expand Down
26 changes: 26 additions & 0 deletions include/simfil/model/arena.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
size_t PageSize = 4096,
size_t ChunkPageSize = 4096,
typename SizeType_ = uint32_t>
class ArrayArena

Check warning on line 62 in include/simfil/model/arena.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Class has 36 methods, which is greater than the 35 authorized. Split it into smaller classes.

See more on https://sonarcloud.io/project/issues?id=Klebert-Engineering_simfil&issues=AZ_cnzchRcrGVBac0AAn&open=AZ_cnzchRcrGVBac0AAn&pullRequest=150
{
friend struct bitsery::ext::ArrayArenaExt;

Expand Down Expand Up @@ -265,6 +265,32 @@
return result + singletonBytes;
}

/**
* Return compact logical payload and all retained arena backing capacity.
*
* Unlike `byte_size()`, allocated bytes include unused array capacity,
* continuation chunks, and storage retained after clear operations.
*/
[[nodiscard]] MemoryUsage memory_usage() const
{
#ifdef ARRAY_ARENA_THREAD_SAFE
std::shared_lock guard(lock_);
#endif
MemoryUsage result;
result.logicalBytes = byte_size();
result.allocatedBytes =
heads_.memory_usage().allocatedBytes +
continuations_.memory_usage().allocatedBytes +
data_.memory_usage().allocatedBytes +
singletonValues_.memory_usage().allocatedBytes +
singletonOccupied_.memory_usage().allocatedBytes;
if (compactHeads_) {
result.allocatedBytes += compactHeads_->memory_usage().allocatedBytes;
}
result.allocatedBytes = std::max(result.logicalBytes, result.allocatedBytes);
return result;
}

/**
* Returns a reference to the element at the specified index in the array.
*
Expand Down
19 changes: 19 additions & 0 deletions include/simfil/model/column.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include <sfl/segmented_vector.hpp>
#include <tl/expected.hpp>

#include "simfil/model/memory.h"

namespace simfil
{

Expand Down Expand Up @@ -249,7 +251,7 @@
typename T,
std::size_t T_RecordsPerPage = 256,
template <typename, std::size_t> typename T_StoragePolicy = default_column_storage>
class ModelColumn

Check warning on line 254 in include/simfil/model/column.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Class has 36 methods, which is greater than the 35 authorized. Split it into smaller classes.

See more on https://sonarcloud.io/project/issues?id=Klebert-Engineering_simfil&issues=AZ_cnzdcRcrGVBac0AAo&open=AZ_cnzdcRcrGVBac0AAo&pullRequest=150
{
public:
/**
Expand Down Expand Up @@ -293,6 +295,17 @@
return values_.size() * sizeof(value_type);
}

/** Return live payload and retained backing capacity for this column. */
[[nodiscard]] MemoryUsage memory_usage() const
{
auto const logicalBytes = byte_size();
auto const allocatedBytes = values_.capacity() * sizeof(value_type);
return {
logicalBytes,
std::max(logicalBytes, allocatedBytes),
};
}

bool empty() const { return values_.empty(); }

void clear() { values_.clear(); }
Expand Down Expand Up @@ -658,6 +671,12 @@
return first_values_.byte_size() + second_values_.byte_size();
}

/** Return combined live payload and retained capacity of both split columns. */
[[nodiscard]] MemoryUsage memory_usage() const
{
return first_values_.memory_usage() + second_values_.memory_usage();
}

[[nodiscard]] bool empty() const { return size() == 0; }

void clear()
Expand Down
39 changes: 39 additions & 0 deletions include/simfil/model/memory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Navigation Data Standard e.V. - See "LICENSE" file.

#pragma once

#include <cstddef>

namespace simfil
{

/**
* Capacity-oriented memory measurement for one owned storage component.
*
* `logicalBytes` describes live payload, while `allocatedBytes` describes the
* retained backing capacity. Container objects and allocator bookkeeping are
* intentionally excluded, so allocated bytes form a stable lower bound rather
* than pretending to equal process-resident memory.
*/
struct MemoryUsage
{
std::size_t logicalBytes = 0;
std::size_t allocatedBytes = 0;

/** Add another independently owned storage component. */
MemoryUsage& operator+=(MemoryUsage const& other)
{
logicalBytes += other.logicalBytes;
allocatedBytes += other.allocatedBytes;
return *this;
}
};

/** Combine two independently owned storage components. */
inline MemoryUsage operator+(MemoryUsage left, MemoryUsage const& right)

Check warning on line 33 in include/simfil/model/memory.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this overloaded operator a hidden friend of class "MemoryUsage".

See more on https://sonarcloud.io/project/issues?id=Klebert-Engineering_simfil&issues=AZ_cnzZ5RcrGVBac0AAm&open=AZ_cnzZ5RcrGVBac0AAm&pullRequest=150
{
left += right;
return left;
}

} // namespace simfil
28 changes: 28 additions & 0 deletions include/simfil/model/model.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#pragma once

#include "simfil/model/string-pool.h"
#include "simfil/model/memory.h"
#include "simfil/model/schema.h"
#include "simfil/byte-array.h"
#include "tl/expected.hpp"
Expand Down Expand Up @@ -305,6 +306,33 @@ class ModelPool : public Model

[[nodiscard]] SerializationSizeStats serializationSizeStats() const;

/** Capacity-oriented memory breakdown of the generic model-pool columns. */
struct MemoryUsageStats
{
MemoryUsage implementation;
MemoryUsage roots;
MemoryUsage int64Values;
MemoryUsage doubleValues;
MemoryUsage stringData;
MemoryUsage stringRanges;
MemoryUsage byteArrayRanges;
MemoryUsage objectMembers;
MemoryUsage objectSchemas;
MemoryUsage arrayMembers;
MemoryUsage arraySchemas;

/** Sum all independently owned model-pool storage. */
[[nodiscard]] MemoryUsage total() const
{
return implementation + roots + int64Values + doubleValues + stringData +
stringRanges + byteArrayRanges + objectMembers + objectSchemas +
arrayMembers + arraySchemas;
}
};

/** Return live payload and retained capacity for generic model-pool storage. */
[[nodiscard]] MemoryUsageStats memoryUsageStats() const;

#if defined(SIMFIL_WITH_MODEL_JSON)
/** JSON Serialization */
virtual nlohmann::json toJson() const;
Expand Down
4 changes: 4 additions & 0 deletions include/simfil/model/string-pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <tl/expected.hpp>

#include "simfil/error.h"
#include "simfil/model/memory.h"

namespace simfil
{
Expand Down Expand Up @@ -69,6 +70,9 @@ struct StringPool
size_t hits() const;
size_t misses() const;

/** Return interned string payload and estimated retained container capacity. */
[[nodiscard]] MemoryUsage memoryUsage() const;

/// Add a static key-string mapping - Warning: Not thread-safe.
void addStaticKey(StringId id, std::string const& value);

Expand Down
2 changes: 2 additions & 0 deletions src/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Environment::Environment(std::shared_ptr<StringPool> strings)
functions["split"] = &SplitFn::Fn;
functions["select"] = &SelectFn::Fn;
functions["sum"] = &SumFn::Fn;
functions["min"] = &MinFn::Fn;
functions["max"] = &MaxFn::Fn;
functions["keys"] = &KeysFn::Fn;
functions["trace"] = &TraceFn::Fn;
functions["re"] = &ReFn::Fn;
Expand Down
106 changes: 106 additions & 0 deletions src/function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,68 @@ auto boolify(const Value& v) -> bool
return false;
return UnaryOperatorDispatcher<OperatorBool>::dispatch(v).value_or(Value::f()).as<ValueType::Bool>();
}

/** Reduce all defined, non-null argument values using the supplied comparison operator. */
template <class ComparisonOperator>
auto evalExtremum(
std::string_view functionName,
Context ctx,
const Value& val,
const std::vector<ExprPtr>& args,
const ResultFn& res) -> tl::expected<Result, Error>
{
if (args.empty()) {
return tl::unexpected<Error>(
Error::InvalidArguments,
fmt::format("function '{}' expects at least one argument", functionName));
}

std::optional<Value> extremum;
bool compilationUndef = false;
for (auto const& arg : args) {
auto argResult = arg->eval(
ctx,
val,
LambdaResultFn(
[&](Context valueContext, Value&& value)
-> tl::expected<Result, Error>
{
if (value.isa(ValueType::Undef)) {
// Preserve runtime-dependent expressions instead of folding around them.
compilationUndef = compilationUndef ||
valueContext.phase == Context::Phase::Compilation;
return Result::Continue;
}
// Null represents no candidate, just like an expression that emits no value.
if (value.isa(ValueType::Null)) {
return Result::Continue;
}
if (!extremum) {
extremum = std::move(value);
return Result::Continue;
}

auto candidateWins =
BinaryOperatorDispatcher<ComparisonOperator>::dispatch(
value,
*extremum);
TRY_EXPECTED(candidateWins);
if (candidateWins->template as<ValueType::Bool>()) {
extremum = std::move(value);
}
return Result::Continue;
}));
TRY_EXPECTED(argResult);
}

if (compilationUndef) {
return res(ctx, Value::undef());
}
if (!extremum) {
return res(ctx, Value::null());
}
return res(ctx, std::move(*extremum));
}
}

CountFn CountFn::Fn;
Expand Down Expand Up @@ -538,6 +600,50 @@ auto SumFn::eval(Context ctx, const Value& val, const std::vector<ExprPtr>& args
return res(ctx, sum);
}

MinFn MinFn::Fn;
MinFn::MinFn() = default;

auto MinFn::ident() const -> const FnInfo&
{
static const FnInfo info{
"min",
"Returns the smallest non-null value produced by its arguments.",
"min(values...) -> <any>"
};
return info;
}

auto MinFn::eval(
Context ctx,
const Value& val,
const std::vector<ExprPtr>& args,
const ResultFn& res) const -> tl::expected<Result, Error>
{
return evalExtremum<OperatorLt>("min", ctx, val, args, res);
}

MaxFn MaxFn::Fn;
MaxFn::MaxFn() = default;

auto MaxFn::ident() const -> const FnInfo&
{
static const FnInfo info{
"max",
"Returns the largest non-null value produced by its arguments.",
"max(values...) -> <any>"
};
return info;
}

auto MaxFn::eval(
Context ctx,
const Value& val,
const std::vector<ExprPtr>& args,
const ResultFn& res) const -> tl::expected<Result, Error>
{
return evalExtremum<OperatorGt>("max", ctx, val, args, res);
}

KeysFn KeysFn::Fn;
KeysFn::KeysFn() = default;

Expand Down
Loading
Loading