Skip to content

Commit

Permalink
API: Added printf-style formatted builder functions
Browse files Browse the repository at this point in the history
These functions let you create complex nested Fleece structures with
a single call. It's basically JSON with values substituted from
arguments using printf-style "%" specs.

See comments in Builder.hh for details.
  • Loading branch information
snej committed Jan 27, 2024
1 parent 9223583 commit c8ed02d
Show file tree
Hide file tree
Showing 10 changed files with 967 additions and 1 deletion.
67 changes: 67 additions & 0 deletions API/fleece/FLMutable.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define _FLMUTABLE_H

#include "FLValue.h"
#include <stdarg.h>

FL_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -339,6 +340,72 @@ extern "C" {
FLSlot_SetValue(slot, (FLValue)dict);
}

/** @} */


//====== FORMATTED VALUE BUILDER


/** \defgroup builder Fleece Formatted Value Builder
@{
These functions use the `printf` idiom to make it convenient to create structured Fleece
values in memory with one call. They create or modify a `FLMutableArray` or `FLMutableDict
by reading the given format string and the following arguments.
The format string is basically JSON5, except that any value in it may be a printf-style
'%' specifier instead of a literal, in which case that value will be read from the next
argument. The supported format specifiers are:
- Boolean: `%c` (cast the arg to `char` to avoid a compiler warning)
- Integer: `%i` or `%d` (use size specifiers `l`, `ll`, or `z`)
- Unsigned integer: `%u` (use size specifiers `l`, `ll`, or `z`)
- Floating point: `%f` (arg can be `float` or `double`; no size spec needed)
- C string: `%s`
- Ptr+length string: `%.*s` (takes two args, a `const char*` and an `int`. See `FMTSLICE`.)
- Fleece value: `%p` (arg must be a `FLValue`)
- [Core]Foundation: `%@` (Apple platforms only: arg must be a `NSString`, `NSNumber`,
`NSArray`, `NSDictionary`, `NSNull`, or equivalent `CFTypeRef`)
A `-` can appear after the `%`, indicating that the argument should be ignored if it has
a default value, namely `false`, 0, or an empty string. This means the corresponding item
won't be written (a Dict item will be erased if it previously existed.)
If an argument is a NULL pointer nothing is written, and any pre-existing Dict item will
be removed.
\note It's legal for a dict key to be repeated; later occurrences take precedence,
i.e. each one overwrites the last.
*/

/** Translates the JSON-style format string into a tree of mutable Fleece objects, adding
values from the following arguments wherever a printf-style `%` specifier appears.
\note The result will be either an `FLMutableArray` or `FLMutableString` depending on
the syntax of the format string.
\warning The returned value must be released when you're done with it. */
NODISCARD
FLEECE_PUBLIC FLValue FLValue_NewWithFormat(const char *format, ...) __printflike(1, 2);

/** Variant of \ref FLValue_NewWithFormat that takes a pre-existing `va_list`.
\warning The returned value must be released when you're done with it. */
NODISCARD
FLEECE_PUBLIC FLValue FLValue_NewWithFormatV(const char *format, va_list args);

/** Like \ref FLValue_NewWithFormat, except it operates on an existing mutable array.
The values parsed from the format string and arguments will be appended to it. */
FLEECE_PUBLIC void FLMutableArray_UpdateWithFormat(FLMutableArray, const char *format, ...)
__printflike(2, 3);

/** Like \ref FLValue_NewWithFormat, except it operates on an existing mutable dict.
(Pre-existing properties not appearing in the format string are preserved.) */
FLEECE_PUBLIC void FLMutableDict_UpdateWithFormat(FLMutableDict, const char *format, ...)
__printflike(2, 3);

/** Like \ref FLMutableArray_UpdateWithFormat / \ref FLMutableDict_UpdateWithFormat
but takes a pre-existing `va_list`. */
FLEECE_PUBLIC void FLValue_UpdateWithFormatV(FLValue, const char *format, va_list args);

/** @} */


// implementations of the inline methods declared earlier:

Expand Down
69 changes: 69 additions & 0 deletions API/fleece/Mutable.hh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ namespace fleece {
/** Creates a new, empty mutable array. */
static MutableArray newArray() {return MutableArray(FLMutableArray_New(), false);}

/** Creates a mutable array with values in it, based on a JSON-like format string.
Argument values can be substituted into it.
See the documentation of the "Fleece Formatted Value Builder" in FLMutable.h. */
static inline MutableArray newWithFormat(const char *format, ...) __printflike(1, 2);
static inline MutableArray newWithFormatV(const char *format, va_list args);

MutableArray() :Array() { }
MutableArray(FLMutableArray FL_NULLABLE a) :Array((FLArray)FLMutableArray_Retain(a)) { }
MutableArray(const MutableArray &a) :Array((FLArray)FLMutableArray_Retain(a)) { }
Expand Down Expand Up @@ -147,6 +153,11 @@ namespace fleece {
inline MutableArray getMutableArray(uint32_t i);
inline MutableDict getMutableDict(uint32_t i);

/** Like \ref newWithFormat, except it operates on an existing mutable array.
(Pre-existing properties not appearing in the format string are preserved.) */
inline void updateWithFormat(const char* format, ...) __printflike(2, 3);
inline void updateWithFormatV(const char* format, va_list);

private:
MutableArray(FLMutableArray FL_NULLABLE a, bool) :Array((FLArray)a) {}
friend class RetainedValue;
Expand All @@ -162,6 +173,12 @@ namespace fleece {
public:
static MutableDict newDict() {return MutableDict(FLMutableDict_New(), false);}

/** Creates a mutable dict with values in it, based on a JSON-like format string.
Argument values can be substituted into it.
See the documentation of the "Fleece Formatted Value Builder" in FLMutable.h. */
static inline MutableDict newWithFormat(const char *format, ...) __printflike(1, 2);
static inline MutableDict newWithFormatV(const char *format, va_list args);

MutableDict() :Dict() { }
MutableDict(FLMutableDict FL_NULLABLE d):Dict((FLDict)d) {FLMutableDict_Retain(*this);}
MutableDict(const MutableDict &d) :Dict((FLDict)d) {FLMutableDict_Retain(*this);}
Expand Down Expand Up @@ -212,6 +229,11 @@ namespace fleece {
inline MutableArray getMutableArray(slice key);
inline MutableDict getMutableDict(slice key);

/** Like \ref newWithFormat, except it operates on an existing mutable dict.
(Pre-existing properties not appearing in the format string are preserved.) */
inline void updateWithFormat(const char* format, ...) __printflike(2, 3);
inline void updateWithFormatV(const char* format, va_list);

private:
MutableDict(FLMutableDict FL_NULLABLE d, bool) :Dict((FLDict)d) {}
friend class RetainedValue;
Expand Down Expand Up @@ -358,6 +380,53 @@ namespace fleece {
return MutableDict(FLDict_AsMutable(*this));
}

MutableArray MutableArray::newWithFormat(const char *format, ...) {
va_list args;
va_start(args, format);
auto result = newWithFormatV(format, args);
va_end(args);
return result;
}

MutableArray MutableArray::newWithFormatV(const char *format, va_list args) {
return MutableArray(FLArray_AsMutable(FLValue_AsArray(FLValue_NewWithFormatV(format, args))));
}

void MutableArray::updateWithFormat(const char* format, ...) {
va_list args;
va_start(args, format);
updateWithFormatV(format, args);
va_end(args);
}

void MutableArray::updateWithFormatV(const char* format, va_list args) {
FLValue_UpdateWithFormatV(*this, format, args);
}


MutableDict MutableDict::newWithFormat(const char *format, ...) {
va_list args;
va_start(args, format);
auto result = newWithFormatV(format, args);
va_end(args);
return result;
}

MutableDict MutableDict::newWithFormatV(const char *format, va_list args) {
return MutableDict(FLDict_AsMutable(FLValue_AsDict(FLValue_NewWithFormatV(format, args))));
}

void MutableDict::updateWithFormat(const char* format, ...) {
va_list args;
va_start(args, format);
updateWithFormatV(format, args);
va_end(args);
}

void MutableDict::updateWithFormatV(const char* format, va_list args) {
FLValue_UpdateWithFormatV(*this, format, args);
}

}

FL_ASSUME_NONNULL_END
Expand Down
12 changes: 12 additions & 0 deletions Fleece.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@
27DE2EEB2125FC9300123597 /* FleeceException.cc in Sources */ = {isa = PBXBuildFile; fileRef = 275CED501D3EF7BE001DE46C /* FleeceException.cc */; };
27DFAE12219F83AB00DF57EB /* InstanceCounted.hh in Headers */ = {isa = PBXBuildFile; fileRef = 27DFAE10219F83AB00DF57EB /* InstanceCounted.hh */; };
27DFAE13219F83AB00DF57EB /* InstanceCounted.cc in Sources */ = {isa = PBXBuildFile; fileRef = 27DFAE11219F83AB00DF57EB /* InstanceCounted.cc */; };
27E3CE0F263B1B0700CA7056 /* Builder.hh in Headers */ = {isa = PBXBuildFile; fileRef = 27E3CE0D263B1B0700CA7056 /* Builder.hh */; };
27E3CE10263B1B0700CA7056 /* Builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 27E3CE0E263B1B0700CA7056 /* Builder.cc */; };
27E3CE12263B525E00CA7056 /* BuilderTests.cc in Sources */ = {isa = PBXBuildFile; fileRef = 27E3CE11263B525E00CA7056 /* BuilderTests.cc */; };
27E3DD421DB6A14200F2872D /* SharedKeys.cc in Sources */ = {isa = PBXBuildFile; fileRef = 27E3DD401DB6A14200F2872D /* SharedKeys.cc */; };
27E3DD431DB6A14200F2872D /* SharedKeys.hh in Headers */ = {isa = PBXBuildFile; fileRef = 27E3DD411DB6A14200F2872D /* SharedKeys.hh */; };
27E3DD4C1DB6C32400F2872D /* CaseListReporter.hh in Headers */ = {isa = PBXBuildFile; fileRef = 27E3DD4A1DB6C32400F2872D /* CaseListReporter.hh */; };
Expand Down Expand Up @@ -472,6 +475,9 @@
27DE2EDF2125FA1700123597 /* libfleeceBase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libfleeceBase.a; sourceTree = BUILT_PRODUCTS_DIR; };
27DFAE10219F83AB00DF57EB /* InstanceCounted.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InstanceCounted.hh; sourceTree = "<group>"; };
27DFAE11219F83AB00DF57EB /* InstanceCounted.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = InstanceCounted.cc; sourceTree = "<group>"; };
27E3CE0D263B1B0700CA7056 /* Builder.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Builder.hh; sourceTree = "<group>"; };
27E3CE0E263B1B0700CA7056 /* Builder.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Builder.cc; sourceTree = "<group>"; };
27E3CE11263B525E00CA7056 /* BuilderTests.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BuilderTests.cc; sourceTree = "<group>"; };
27E3DD401DB6A14200F2872D /* SharedKeys.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SharedKeys.cc; sourceTree = "<group>"; };
27E3DD411DB6A14200F2872D /* SharedKeys.hh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SharedKeys.hh; sourceTree = "<group>"; };
27E3DD471DB6B86000F2872D /* catch.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch.hpp; sourceTree = "<group>"; };
Expand Down Expand Up @@ -674,6 +680,7 @@
27F666432017E26700A8ED31 /* SupportTests.cc */,
279AC52A1C07776A002C80DB /* ValueTests.cc */,
272E5A5B1BF800A100848580 /* EncoderTests.cc */,
27E3CE11263B525E00CA7056 /* BuilderTests.cc */,
27E3DD521DB7DB1C00F2872D /* SharedKeysTests.cc */,
276D15481E008E7A00543B1B /* JSON5Tests.cc */,
27298E771C01A461000CFBA8 /* PerfTests.cc */,
Expand Down Expand Up @@ -900,6 +907,8 @@
27A924CE1D9C32E800086206 /* Path.hh */,
27298E7F1C04E665000CFBA8 /* Encoder.cc */,
270FA26F1BF53CEA005DCB13 /* Encoder.hh */,
27E3CE0E263B1B0700CA7056 /* Builder.cc */,
27E3CE0D263B1B0700CA7056 /* Builder.hh */,
27298E3A1C00F812000CFBA8 /* JSONConverter.cc */,
27298E761C00FB48000CFBA8 /* JSONConverter.hh */,
27E3DD401DB6A14200F2872D /* SharedKeys.cc */,
Expand Down Expand Up @@ -989,6 +998,7 @@
2734B8AD1F859AEC00BE5249 /* FleeceDocument.h in Headers */,
2776AA792093C982004ACE85 /* sliceIO.hh in Headers */,
278163BD1CE7A72300B94E32 /* KeyTree.hh in Headers */,
27E3CE0F263B1B0700CA7056 /* Builder.hh in Headers */,
27C4ACAD1CE5146500938365 /* Array.hh in Headers */,
2776AA22208678AA004ACE85 /* DeepIterator.hh in Headers */,
2734B89E1F8583FF00BE5249 /* MArray.hh in Headers */,
Expand Down Expand Up @@ -1329,6 +1339,7 @@
27A924CF1D9C32E800086206 /* Path.cc in Sources */,
274D824C209A7577008BB39F /* HeapArray.cc in Sources */,
2734B8B11F870FB400BE5249 /* MContext.cc in Sources */,
27E3CE10263B1B0700CA7056 /* Builder.cc in Sources */,
27A0E3E024DCD86900380563 /* ConcurrentArena.cc in Sources */,
275CED521D3EF7BE001DE46C /* FleeceException.cc in Sources */,
278163B51CE69CA800B94E32 /* Fleece.cc in Sources */,
Expand Down Expand Up @@ -1374,6 +1385,7 @@
27298E781C01A461000CFBA8 /* PerfTests.cc in Sources */,
2734B8A71F85842300BE5249 /* MTests.mm in Sources */,
272E5A5F1BF91DBE00848580 /* ObjCTests.mm in Sources */,
27E3CE12263B525E00CA7056 /* BuilderTests.cc in Sources */,
277F45B4208FDA1800A0D159 /* HashTreeTests.cc in Sources */,
27CEE41A20EFE92E00089A85 /* KeyTree.cc in Sources */,
);
Expand Down
36 changes: 36 additions & 0 deletions Fleece/API_Impl/Fleece.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "fleece/Fleece.h"
#include "JSON5.hh"
#include "ParseDate.hh"
#include "Builder.hh"
#include "betterassert.hh"
#include <chrono>

Expand Down Expand Up @@ -779,6 +780,41 @@ FLSliceResult FLEncoder_Finish(FLEncoder e, FLError * FL_NULLABLE outError) FLAP
}


#pragma mark - BUILDER


FLValue FLValue_NewWithFormat(const char *format, ...) {
va_list args;
va_start(args, format);
auto result = FLValue_NewWithFormatV(format, args);
va_end(args);
return result;
}

FLValue FLValue_NewWithFormatV(const char *format, va_list args) {
return std::move(builder::VBuild(format, args)).detach();
}

void FLMutableArray_UpdateWithFormat(FLMutableArray array, const char *format, ...) {
va_list args;
va_start(args, format);
FLValue_UpdateWithFormatV(array, format, args);
va_end(args);
}

void FLMutableDict_UpdateWithFormat(FLMutableDict dict, const char *format, ...) {
va_list args;
va_start(args, format);
FLValue_UpdateWithFormatV(dict, format, args);
va_end(args);
}

void FLValue_UpdateWithFormatV(FLValue v, const char *format, va_list args) {
assert(FLValue_IsMutable(v));
builder::VPut(const_cast<Value*>(v), format, args);
}


#pragma mark - DOCUMENTS


Expand Down
Loading

0 comments on commit c8ed02d

Please sign in to comment.