Compare commits

...

10 commits

Author SHA1 Message Date
29070c8f41 Fix memory leak in code generated by j2s code.
Closes #17.
2023-11-20 09:51:08 -05:00
bc67393036 Update CHANGELOG.md 2023-11-06 21:28:10 -05:00
ba1ac5b42b Add JsonMerge().
Closes #15.
2023-11-06 19:59:46 -05:00
01da37f7d7 Bump version number. 2023-11-06 14:20:42 -05:00
618bcbbac3 j2s: Add 'extern' type and ignored fields.
Closes #14.
2023-11-06 14:19:49 -05:00
d242597e73 Allow customization of compiler used in configure. 2023-11-04 15:58:23 -04:00
4e73273cbd Add CHANGELOG.md 2023-11-01 12:20:52 -04:00
dadc1ac5c7 Provide build instructions and logo.
Closes #8.
2023-11-01 11:26:48 -04:00
8ffade37b1 Add a meta header.
This allows programs to print the name and version of the library that
is currently in use.
2023-11-01 11:26:27 -04:00
6ab1c7919b Explicitly set the make compiler to cc.
This makes it work out of the box on GNU systems where GNU make defaults
to c99, which doesn't accept -std=c89.
2023-11-01 11:25:32 -04:00
8 changed files with 288 additions and 27 deletions

29
CHANGELOG.md Normal file
View file

@ -0,0 +1,29 @@
# Cytoplasm Change Log
This document contains the complete change log for every official release of
Cytoplasm. It is intended to be updated with every commit that makes a user-facing
change worth reporting in the change log. As such, it changes frequently between
releases. Final change log entries are published as [Releases](releases).
## v0.4.1
### New Features
- Added an option to `j2s` to allow additional fields in structures and ignore them in
encoding and decoding. Note that additional fields are totally untouched—they
are not even initialized to a default value.
- Fixed a memory leak that would occur in code generated by `j2s` under
specific circumstances.
- Added `JsonMerge()` to the JSON API to merge two JSON objects together.
## v0.4.0
**Released on November 1, 2023**
This is the first independent release of Cytoplasm! Last month, Cytoplasm was
split off of [Telodendria](/Telodendria/Telodendria) to become its own independent
project with its own independent releases. This allows it to develop at a much more
rapid pace than Telodendria.
Changes in future releases will be reported here. Since this is the first release,
there are no changes to show.

View file

@ -1,4 +1,5 @@
# Cytoplasm (libcytoplasm)
<p align="center"><img src="https://telodendria.io/assets/Cytoplasm.png"></p>
<h1 align="center">Cytoplasm (<code>libcytoplasm</code>)</h1>
Cytoplasm is a general-purpose C library for creating high-level (particularly networked and multi-threaded) C applications. It allows applications to take advantage of the speed, flexibility, and simplicity of the C programming language, while providing helpful code to allow applications to perform various complex tasks with minimal effort. Cytoplasm provides high-level data structures, a basic logging facility, an HTTP client and server, and more. It also reports memory leaks, which can aid in debugging.
@ -14,16 +15,27 @@ The name "Cytoplasm" was chosen for a few reasons. It plays off the precedent se
Cytoplasm also starts with a C, which I think is a nice touch for C libraries. It's also fun to say and unique enough that searching for "libcytoplasm" should bring you to this project and not some other one.
## Building
## Requirements
If your operating system or software distribution provides a pre-built package of Cytoplasm, you should prefer to use that instead of building it from source.
Cytoplasm makes the following assumptions about the underlying hardware:
Cytoplasm aims to have zero dependencies beyond what is mandated by POSIX. You only need the standard math and pthread libraries to build it. TLS support can optionally be enabled with the configuration script. The supported TLS implementations are as follows:
- It has words sizes that are powers of 2, and a native 32-bit integer type exists.
- Integers are represented using two's compliment for negatives.
The ANSI C standard requires an integer type of at least 32 bits, but does not require any more. If Cytoplasm is built on 32-bit platforms that don't provide a native 64-bit integer type, Cytoplasm emulates 64-bit integers. This can make it more portable.
Cytoplasm aims to have zero software dependencies beyond what is mandated by POSIX. You only need the standard math and pthread libraries to build it. TLS support can optionally be enabled with the configuration script. The supported TLS implementations are as follows:
- OpenSSL
- LibreSSL
If TLS support is not enabled, all APIs that use it should fall back to non-TLS behavior in a sensible manner. For example, if TLS support is not enabled, then the HTTP client API will simply return an error if a TLS connection is requested. Cytoplasm uses the standard C library build procedure. Just run these commands:
If TLS support is not enabled, all APIs that use it should fall back to non-TLS behavior in a sensible manner. For example, if TLS support is not enabled, then the HTTP client API will simply return an error if a TLS connection is requested.
## Building
If your operating system or software distribution provides a pre-built package of Cytoplasm, you should prefer to use that instead of building it from source.
Cytoplasm uses the standard C library build procedure. Just run these commands:
```
./configure
@ -43,6 +55,40 @@ This will produce the following out/ directory:
You can also run `make install` as `root` to install Cytoplasm to the system. This will install the libraries, tools, and `man` pages.
The `configure` script has a number of optional flags, which are as follows:
- `--with-(openssl|libressl)`: Select the TLS implementation to use. OpenSSL is selected by default.
- `--disable-tls`: Disable TLS altogether.
- `--prefix=<path>`: Set the install prefix to set by default in the `Makefile`. This defaults to `/usr/local`, which should be appropriate for most Unix-like systems.
- `--(enable|disable)-ld-extra`: Control whether or not to enable additional linking flags that create a more optimized binary. For large compilers such as GCC and Clang, these flags should be enabled. However, if you are using a small or more obscure compiler, then these flags may not be supported, so you can disable them with this option.
- `--(enable|disable)-debug`: Control whether or not to enable debug mode. This sets the optimization level to 0 and builds with debug symbols. Useful for running with a debugger.
- `--static` and `--no-static`: Controls whether static binaries for tools are built by default. On BSD systems, `--static` is perfectly acceptable, but on GNU systems, `--no-static` is often desirable to silence warnings about static binaries emitted by the GNU linker.
Cytoplasm can be customized with the following options:
- `--lib-name=<name>`: The output name of the library. This defaults to `Cytoplasm` and should in most cases not be changed.
- `--lib-version=<version>`: The version string to embed in the library binaries. This can be used to indicate build customizations or non-release versions of Cytoplasm.
The following recipes are available in the generated `Makefile`:
- `all`: This is the default target. It builds everything.
- `Cytoplasm`: Build the `libCytoplasm.(so|a)` binaries. If you specified an alternative `--lib-name`, then this target will be named after that.
- `docs`: Generate the header documentation as `man` pages.
- `tools`: Build the supplemental tools which may be useful for development.
- `clean`: Remove the build and output directories. Cytoplasm builds are out-of-tree, which greatly simplifies this recipe compared to in-tree builds.
If you're developing Cytoplasm, these recipes may also be helpful:
- `format`: Format the source code using `indent`. This may require a BSD `indent` because last time I tried GNU `indent`, it didn't like the flags in `indent.pro`. Your mileage may vary.
- `license`: Update the license headers in all source code files with the contents of the `LICENSE.txt`.
To install Telodendria to your system, the following recipes are available:
- `install`: This installs Cytoplasm under the prefix set with `./configure --prefix=<dir>` or with `make PREFIX=<dir>`. By default, the `make` `PREFIX` is set to whatever was set with `configure --prefix`.
- `uninstall`: Uninstall Cytoplasm from the same prefix as specified above.
After a build, you can find the object files in `build/` and the output binaries in `out/lib/`.
## Usage
Cytoplasm provides the typical .so and .a files, which can be used to link programs with it in the usual way. Somewhat *unusually* for C libraries, however, it provides its own `main()` function, so programs written with Cytoplasm provide `Main()` instead, which is called by Cytoplasm. Cytoplasm works this way because it needs to perform some setup logic before user code runs and some teardown logic after user code returns.
@ -63,7 +109,7 @@ If this file is `Hello.c`, then you can compile it by doing this:
$ cc -o hello Hello.c -lCytoplasm
The full form of Main() expected by the stub is as follows:
The full form of `Main()` expected by the stub is as follows:
```c
int Main(Array *args, HashMap *env);
@ -72,3 +118,9 @@ The full form of Main() expected by the stub is as follows:
The first argument is a Cytoplasm array of the command line arguments, and the second is a Cytoplasm hash map of environment variables. Most linkers will let programs omit the `env` argument, or both arguments if you don't need either. The return value of `Main()` is returned to the operating system, as would be expected.
Note that both arguments to Main may be treated like any other Cytoplasm array or hash map. However, do not invoke `ArrayFree()` or `HashMapFree()` on the passed pointers, because memory is cleaned up after `Main()` returns.
## License
All of the code and documentation for Cytoplasm is licensed under the same license as Telodendria itself. Please refer to [Telodendria &rightarrow; License](/Telodendria/Telodendria#license) for details.
The Cytoplasm logo was designed by [Tobskep](https://tobskep.com) and is licensed under the [Creative Commons Attribution-ShareAlike 4.0](https://creativecommons.org/licenses/by-sa/4.0/) license.

10
configure vendored
View file

@ -18,7 +18,7 @@ LIBS="-lm -pthread"
# Set default args for all platforms
SCRIPT_ARGS="--prefix=/usr/local --enable-ld-extra --lib-name=Cytoplasm --lib-version=0.4.0 --static $@"
SCRIPT_ARGS="--cc=cc --prefix=/usr/local --enable-ld-extra --lib-name=Cytoplasm --lib-version=0.4.1 --static $@"
# Set platform specific args
case "$(uname)" in
@ -36,6 +36,9 @@ echo "Ran with arguments: $SCRIPT_ARGS"
# Process all arguments
for arg in $SCRIPT_ARGS; do
case "$arg" in
--cc=*)
CC=$(echo "$arg" | cut -d '=' -f 2-)
;;
--with-openssl)
TLS_IMPL="TLS_OPENSSL"
TLS_LIBS="-lcrypto -lssl"
@ -81,7 +84,7 @@ for arg in $SCRIPT_ARGS; do
STATIC=""
;;
*)
echo "Invalid argument: $1"
echo "Invalid argument: $arg"
exit 1
;;
esac
@ -129,7 +132,7 @@ compile_obj() {
src="$1"
obj="$2"
cc -I${INCLUDE} -MM -MT "${obj}" "${src}"
${CC} -I${INCLUDE} -MM -MT "${obj}" "${src}"
echo "${TAB}@mkdir -p $(dirname ${obj})"
echo "${TAB}\$(CC) \$(CFLAGS) -fPIC -c -o \"${obj}\" \"${src}\""
}
@ -195,6 +198,7 @@ cat << EOF > Makefile
# Generated by '$0' on $(date).
# This file should generally not be manually edited.
CC = ${CC}
PREFIX = ${PREFIX}
CFLAGS = ${CFLAGS}
LDFLAGS = ${LDFLAGS}

35
src/Cytoplasm.c Normal file
View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Cytoplasm.h>
char *CytoplasmGetName()
{
return LIB_NAME;
}
char *CytoplasmGetVersion()
{
return LIB_VERSION;
}

View file

@ -1433,3 +1433,32 @@ finish:
va_end(argp);
return val;
}
void
JsonMerge(HashMap *obj1, HashMap *obj2)
{
char *key;
JsonValue *val2;
while (HashMapIterate(obj2, &key, (void **) &val2))
{
JsonValue *val1 = HashMapGet(obj1, key);
if (val1)
{
if (JsonValueType(val1) == JsonValueType(val2) &&
JsonValueType(val1) == JSON_OBJECT)
{
JsonMerge(JsonValueAsObject(val1), JsonValueAsObject(val2));
}
else
{
JsonValueFree(HashMapSet(obj1, key, JsonValueDuplicate(val2)));
}
}
else
{
HashMapSet(obj1, key, JsonValueDuplicate(val2));
}
}
}

60
src/include/Cytoplasm.h Normal file
View file

@ -0,0 +1,60 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef CYTOPLASM_CYTOPLASM_H
#define CYTOPLASM_CYTOPLASM_H
/***
* @Nm Cytoplasm
* @Nd A simple API that provides metadata on the library itself.
* @Dd September 30 2022
* @Xr Sha2
*
* This API simply provides name and versioning information for the
* currently loaded library.
*/
/**
* Get the name that this library was compiled with. In most cases,
* this will be hard-coded to "Cytoplasm", but it may differ if, for
* some reason, there exists another ABI-compatible library that
* wishes to report its name.
*
* This function really only exists because the information is
* available along side of the version information so for
* consistency, it made sense to include both.
*/
extern char * CytoplasmGetName(void);
/**
* Get the library version. This will be useful mostly for printing
* to log files, but it may also be used by a program to verify that
* the version is new enough.
*
* This function returns a string, which should usually be able to be
* parsed using sscanf() if absolutely necessary.
*/
extern char * CytoplasmGetVersion(void);
#endif /* CYTOPLASM_CYTOPLASM_H */

View file

@ -320,4 +320,13 @@ extern JsonValue * JsonGet(HashMap *, size_t,...);
*/
extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...);
/**
* Recursively merge two JSON objects. The second object is merged
* on top of the first; any keys present in the first object that are
* also present in the second object are replaced with those in the
* second object, and any keys present in the second object that are
* not present in the first object are copied to the first object.
*/
extern void JsonMerge(HashMap *, HashMap *);
#endif /* CYTOPLASM_JSON_H */

View file

@ -309,6 +309,8 @@ Main(Array * args)
ArrayAdd(requiredTypes, StrDuplicate(type));
}
if (StrEquals(typeType, "struct"))
{
typeFieldsVal = HashMapGet(typeObj, "fields");
if (JsonValueType(typeFieldsVal) != JSON_OBJECT)
{
@ -318,13 +320,12 @@ Main(Array * args)
typeFields = JsonValueAsObject(typeFieldsVal);
if (StrEquals(typeType, "struct"))
{
while (HashMapIterate(typeFields, &fieldName, (void **) &fieldVal))
{
char *fieldType;
int isArrType = 0;
JsonValue *requiredVal;
JsonValue *ignoreVal;
if (JsonValueType(fieldVal) != JSON_OBJECT)
{
@ -379,10 +380,26 @@ Main(Array * args)
Log(LOG_ERR, "Validation error: 'types.%s.fields.%s.required' must be a boolean.", type, fieldName);
goto finish;
}
ignoreVal = HashMapGet(fieldObj, "ignore");
if (ignoreVal && JsonValueType(ignoreVal) != JSON_BOOLEAN)
{
Log(LOG_ERR, "Validation error: 'types.%s.fields.%s.ignore' must be a boolean.", type, fieldName);
goto finish;
}
}
}
else if (StrEquals(typeType, "enum"))
{
typeFieldsVal = HashMapGet(typeObj, "fields");
if (JsonValueType(typeFieldsVal) != JSON_OBJECT)
{
Log(LOG_ERR, "Validation error: 'types.%s.fields' must be an object.", type);
goto finish;
}
typeFields = JsonValueAsObject(typeFieldsVal);
while (HashMapIterate(typeFields, &fieldName, (void **) &fieldVal))
{
char *name;
@ -403,16 +420,17 @@ Main(Array * args)
}
}
}
else if (StrEquals(typeType, "extern"))
{
/*
* No code will be generated for this type. We simply assume that it exists.
*/
}
else
{
Log(LOG_ERR, "Validation error: 'types.%s.type' must be 'struct' or 'enum'.", type);
goto finish;
}
/*
* TODO: Add "extern" type that doesn't actually generate any code,
* but trusts the user that it has been generated somewhere else. This
* is effectively "importing" types.
*/
}
sortedNodes = GraphTopologicalSort(dependencyGraph, &sortedNodesLen);
@ -471,6 +489,12 @@ Main(Array * args)
}
typeType = JsonValueAsString(JsonGet(types, 2, type, "type"));
if (StrEquals(typeType, "extern"))
{
continue;
}
fields = JsonValueAsObject(JsonGet(types, 2, type, "fields"));
StreamPrintf(headerFile, "typedef %s %s\n{\n", typeType, type);
@ -615,11 +639,18 @@ Main(Array * args)
{
char *key = ArrayGet(keys, i);
int required = JsonValueAsBoolean(JsonGet(fields, 2, key, "required"));
int ignore = JsonValueAsBoolean(JsonGet(fields, 2, key, "ignore"));
char *fieldType = JsonValueAsString(JsonGet(fields, 2, key, "type"));
int isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum");
JsonType jsonType = isEnum ? JSON_STRING : TypeToJsonType(fieldType);
char *jsonTypeStr = JsonTypeToStr(jsonType);
if (ignore)
{
StreamPrintf(implFile, " /* Ignored field: %s */\n\n", key);
continue;
}
StreamPrintf(implFile, " val = HashMapGet(json, \"%s\");\n", Trim('_', key));
StreamPrintf(implFile, " if (val)\n {\n");
@ -629,11 +660,15 @@ Main(Array * args)
StreamPrintf(implFile, " }\n\n");
if (StrEquals(fieldType, "array"))
{
StreamPrintf(implFile, " out->%s = JsonValueAsArray(JsonValueDuplicate(val));\n", key);
StreamPrintf(implFile, " val = JsonValueDuplicate(val);\n");
StreamPrintf(implFile, " out->%s = JsonValueAsArray(val);\n", key);
StreamPrintf(implFile, " Free(val); /* Not JsonValueFree() because we want the inner value. */\n");
}
else if (StrEquals(fieldType, "object"))
{
StreamPrintf(implFile, " out->%s = JsonValueAsObject(JsonValueDuplicate(val));\n", key);
StreamPrintf(implFile, " val = JsonValueDuplicate(val);\n");
StreamPrintf(implFile, " out->%s = JsonValueAsObject(val);\n", key);
StreamPrintf(implFile, " Free(val); /* Not JsonValueFree() because we want the inner value. */\n");
}
else if (*fieldType == '[' && fieldType[strlen(fieldType) - 1] == ']')
{
@ -847,6 +882,13 @@ Main(Array * args)
char *key = ArrayGet(keys, i);
char *fieldType = JsonValueAsString(JsonGet(fields, 2, key, "type"));
int isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum");
int ignore = JsonValueAsBoolean(JsonGet(fields, 2, key, "ignore"));
if (ignore)
{
StreamPrintf(implFile, " /* Ignored field: %s */\n\n", key);
continue;
}
if (StrEquals(fieldType, "array"))
{
@ -1022,8 +1064,9 @@ Main(Array * args)
else
{
/* Ignore primitives but call the appropriate free
* method on declared types */
if (!isEnum && HashMapGet(types, fieldType))
* method on declared types that aren't "extern". */
char *fieldTypeType = JsonValueAsString(JsonGet(types, 2, fieldType, "type"));
if (!isEnum && HashMapGet(types, fieldType) && !StrEquals(fieldTypeType, "extern"))
{
StreamPrintf(implFile, " %sFree(&val->%s);\n", fieldType, key);
}