diff --git a/.gitignore b/.gitignore index e743624..f492aaf 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ w_dev *.os *.o .sconsign.dblite +site +*.pyc +wayward_synth.plugin diff --git a/.gitmodules b/.gitmodules index affcc3b..bc19368 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/simonask/libevhtp.git [submodule "3rdparty/synth"] path = 3rdparty/synth - url = https://github.com/ajg/synth.git + url = https://github.com/simonask/synth.git diff --git a/3rdparty/synth b/3rdparty/synth index 7540072..51af35e 160000 --- a/3rdparty/synth +++ b/3rdparty/synth @@ -1 +1 @@ -Subproject commit 7540072bde2ea9c8258c2dca69d2ed3bd62fb991 +Subproject commit 51af35e7bf50c069a64b2c8894cb4bd755d3f928 diff --git a/SConstruct b/SConstruct index eeb7db0..92fff2c 100644 --- a/SConstruct +++ b/SConstruct @@ -2,13 +2,15 @@ from wayward_build import * import os wayward_support_sources = Split(""" + wayward/support/any.cpp + wayward/support/benchmark.cpp wayward/support/format.cpp wayward/support/uri.cpp - wayward/support/node.cpp wayward/support/json.cpp wayward/support/datetime/datetime.cpp wayward/support/datetime/clock.cpp wayward/support/datetime/interval.cpp + wayward/support/datetime/type.cpp wayward/support/logger.cpp wayward/support/error.cpp wayward/support/command_line_options.cpp @@ -17,29 +19,98 @@ wayward_support_sources = Split(""" wayward/support/http.cpp wayward/support/teamwork.cpp wayward/support/plugin.cpp - wayward/support/mutable_node.cpp + wayward/support/data_franca/spectator.cpp + wayward/support/data_franca/mutator.cpp + wayward/support/data_franca/object.cpp + wayward/support/string.cpp + wayward/support/types.cpp 3rdparty/libevhtp/evhtp.c 3rdparty/libevhtp/htparse/htparse.c 3rdparty/libevhtp/evthr/evthr.c """) +wayward_support_headers = Split(""" + wayward/support/uri.hpp + wayward/support/types.hpp + wayward/support/type_info.hpp + wayward/support/type.hpp + wayward/support/thread_local.hpp + wayward/support/teamwork.hpp + wayward/support/string.hpp + wayward/support/result.hpp + wayward/support/plugin.hpp + wayward/support/options.hpp + wayward/support/monad.hpp + wayward/support/meta.hpp + wayward/support/maybe.hpp + wayward/support/logger.hpp + wayward/support/json.hpp + wayward/support/intrusive_list.hpp + wayward/support/http.hpp + wayward/support/format.hpp + wayward/support/fiber.hpp + wayward/support/event_loop.hpp + wayward/support/error.hpp + wayward/support/either.hpp + wayward/support/datetime.hpp + wayward/support/data_visitor.hpp + wayward/support/data_franca.hpp + wayward/support/command_line_options.hpp + wayward/support/cloning_ptr.hpp + wayward/support/bitflags.hpp + wayward/support/benchmark.hpp + wayward/support/any.hpp + wayward/support/data_franca/adapter.hpp + wayward/support/data_franca/adapters.hpp + wayward/support/data_franca/mutator.hpp + wayward/support/data_franca/node.hpp + wayward/support/data_franca/object.hpp + wayward/support/data_franca/reader.hpp + wayward/support/data_franca/spectator.hpp + wayward/support/data_franca/types.hpp + wayward/support/data_franca/writer.hpp + wayward/support/datetime/clock.hpp + wayward/support/datetime/datetime.hpp + wayward/support/datetime/duration_units.hpp + wayward/support/datetime/interval.hpp + wayward/support/datetime/timezone.hpp + wayward/support/datetime/type.hpp + """) + wayward_sources = Split(""" wayward/app.cpp wayward/render.cpp wayward/log.cpp wayward/template_engine.cpp + wayward/respond_to.cpp + wayward/content_type.cpp + """) + +wayward_headers = Split(""" + wayward/content_type.hpp + wayward/respond_to.hpp + wayward/routes.hpp + wayward/session.hpp + wayward/template_engine.hpp + wayward/w.hpp + w """) w_util_sources = Split(""" w_util/server.cpp w_util/recompiler.cpp w_util/main.cpp + w_util/init.cpp """) wayward_testing_sources = Split(""" wayward/testing/time_machine.cpp """) +wayward_testing_headers = Split(""" + wayward/testing/time_machine.hpp + """) + persistence_sources = Split(""" persistence/connection_provider.cpp persistence/connection_retainer.cpp @@ -47,12 +118,57 @@ persistence_sources = Split(""" persistence/data_store.cpp persistence/connection_pool.cpp persistence/p.cpp - persistence/postgresql.cpp - persistence/postgresql_renderers.cpp + persistence/adapters/postgresql/connection.cpp + persistence/adapters/postgresql/renderers.cpp persistence/primary_key.cpp persistence/relational_algebra.cpp - persistence/types.cpp - persistence/datetime.cpp + persistence/insert.cpp + persistence/property.cpp + persistence/data_as_literal.cpp + persistence/projection.cpp + persistence/column.cpp + persistence/assign_attributes.cpp + """) + +persistence_headers = Split(""" + persistence/adapters/postgresql/connection.hpp + persistence/adapters/postgresql/renderers.hpp + persistence/adapter.hpp + persistence/assign_attributes.hpp + persistence/association.hpp + persistence/ast.hpp + persistence/belongs_to.hpp + persistence/column.hpp + persistence/column_abilities.hpp + persistence/column_traits.hpp + persistence/connection.hpp + persistence/connection_pool.hpp + persistence/connection_provider.hpp + persistence/connection_retainer.hpp + persistence/context.hpp + persistence/create.hpp + persistence/data_as_literal.hpp + persistence/data_store.hpp + persistence/datetime.hpp + persistence/destroy.hpp + persistence/has_many.hpp + persistence/has_one.hpp + persistence/insert.hpp + persistence/p.hpp + persistence/persistence_macro.hpp + persistence/primary_key.hpp + persistence/projection.hpp + persistence/projection_as_structured_data.hpp + persistence/property.hpp + persistence/record.hpp + persistence/record_as_structured_data.hpp + persistence/record_ptr.hpp + persistence/record_type.hpp + persistence/record_type_builder.hpp + persistence/relational_algebra.hpp + persistence/result_set.hpp + persistence/validation_errors.hpp + p """) wayward_synth_sources = Split(""" @@ -61,14 +177,14 @@ wayward_synth_sources = Split(""" env = WaywardEnvironment(DefaultEnvironment()) -wayward_support = WaywardLibrary(env, 'wayward_support', wayward_support_sources) +wayward_support = WaywardLibrary(env, 'wayward_support', wayward_support_sources, wayward_support_headers) env_with_ws = env.Clone() env_with_ws.Append(LIBS = [wayward_support]) -wayward = WaywardLibrary(env_with_ws, 'wayward', wayward_sources) -wayward_testing = WaywardLibrary(env_with_ws, 'wayward_testing', wayward_testing_sources) +wayward = WaywardLibrary(env_with_ws, 'wayward', wayward_sources, wayward_headers) +wayward_testing = WaywardLibrary(env_with_ws, 'wayward_testing', wayward_testing_sources, wayward_headers) env_for_synth = env.Clone() -env_for_synth.Append(CPPPATH = Split('3rdparty/synth')) +env_for_synth.Append(CPPPATH = Split('3rdparty/synth 3rdparty/synth/external/boost')) wayward_synth = WaywardPlugin(env_for_synth, 'wayward_synth', wayward_synth_sources) if platform.system() == 'Linux': @@ -79,13 +195,15 @@ else: persistence_env.Append(CCFLAGS = Split(libpq_cflags)) persistence_env.Append(LINKFLAGS = Split(libpq_libs)) -persistence = WaywardLibrary(persistence_env, 'persistence', persistence_sources) +persistence = WaywardLibrary(persistence_env, 'persistence', persistence_sources, persistence_headers) WaywardAddDefaultLibraries([wayward, persistence, wayward_support]) w_dev = WaywardProgram(env_with_ws, 'w_dev', w_util_sources, rpaths = ['.']) -Export('env', 'wayward', 'wayward_support', 'persistence', 'wayward_testing', 'WaywardProgram', 'WaywardLibrary', 'WaywardEnvironment') +env.Alias('install', ['install-bin', 'install-lib', 'install-inc']) + +Export('env', 'wayward', 'wayward_support', 'persistence', 'wayward_testing', 'WaywardProgram', 'WaywardInternalProgram', 'WaywardLibrary', 'WaywardEnvironment') build_tests = ARGUMENTS.get('build_tests', 'yes') if build_tests == 'yes': diff --git a/TODO.md b/TODO.md index cd7cc59..82921dc 100644 --- a/TODO.md +++ b/TODO.md @@ -13,8 +13,6 @@ Persistence Wayward ------- -- Params de-/restructuralization -- Dictionary class that returns Maybe for non-existing values. - Make sure that plugin architecture (and Synth) is working on Linux. Wayward Support @@ -24,3 +22,4 @@ Wayward Support - Path utility functions (maybe a path class). - A string class that does proper UTF-8 handling. - A HTML class representing a string that is unescaped HTML. +- Find better names for everything in data_franca. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..bef22c6 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,70 @@ +# Wayward Overview + +[GitHub](https://github.com/simonask/w) + +Did your dad ever tell you: "Writing web apps in C++ is dangerous and stupid! You should know better!"? + +Well he's not the boss of you! You can do whatever you want! + +Wayward ("``") is a web framework for writing HTTP-aware apps in C++. It uses [libevent](http://libevent.org/) behind +the scenes to create a compact HTTP server that can act as the backend for your app. + +`` is heavily inspired by the Ruby framework Sinatra, and aims to keep things as simple as possible. +It uses the C++ Standard Template Library extensively. + +***WARNING!*** Wayward is as of yet still in early alpha, and should not, I repeat *NOT* be used in production just yet. Several big pieces are missing, and the rest is a semi-explosive mess that is probably unfit for human *and* animal consumption. Don't say you weren't warned. If you're looking for production-quality code, come back in a few releases, I'm sure we'll have something cooking for you. + + +## Getting Started + +Wayward is both a library and a command-line utility, named `w_dev`. Wayward apps can exist without `w_dev`, but it does make life for developers a lot easier. `w_dev` creates projects, generates standardized components in projects, and has a built-in development server that automatically rebuilds and restarts your app when you make changes to it, allowing for live "Rails-style" coding. + +### Building `w_dev` + +1. Make sure dependencies are installed (git, scons, clang, libevent). +2. `git clone --recursive https://github.com/simonask/w.git` +3. `cd w` +4. `scons -j8 w_dev` (This will only build the necessary components for `w_dev`. Running with `-jN` makes scons run tasks in parallel.) + +### Creating a Project + +1. `cd ~/your/path/to/projects` +2. `path/to/w_dev new my_project` +3. Wait for `w_dev` to finish installing dependencies in your project dir. +4. You now have a project named `my_project` in `~/your/path/to/projects`. + +### Building and Running + +1. `cd my_project` +3. `path/to/w_dev server my_project` +4. Browse to `localhost:3000`. The first request in the lifetime of the project will take some time. + +On the first request to the app server, `w_dev` will build your app. This may take about a minute the very first time because it also needs to build Wayward and dependencies, but subsequent requests will only cause a rebuild of the things that have changed in your own project. Normal turnaround for an incremental build should be very short. + + +## Sample "Hello World" + + #include + + int main(int argc, char** argv) { + w::App app { argc, argv }; + + app.get("/", [](w::Request& req) { + return w::render_text("Hello, World!"); + }); + + return app.run(); + } + +## Dependencies + +Dependency management in C++ apps is a pain, so `` tries to keep it to a minimum. + +- [libevent 2.0.x](http://libevent.org/) +- [Clang](http://clang.llvm.org/) and [libc++](http://libcxx.llvm.org/) — GCC will be supported in the future, but there are some disagreements between Clang++ and g++ that are holding back that development +- SCons 2.3.0 or newer. +- For Persistence: `libpq` (from PostgreSQL). + +`` has been tested on the following platforms: + +- OS X 10.9.2 with Apple Clang-503.0.40 diff --git a/docs/persistence.md b/docs/persistence.md new file mode 100644 index 0000000..10f48da --- /dev/null +++ b/docs/persistence.md @@ -0,0 +1 @@ +# Persistence ORM Framework diff --git a/docs/wayward/advanced_routes.md b/docs/wayward/advanced_routes.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/wayward/overview.md b/docs/wayward/overview.md new file mode 100644 index 0000000..897e010 --- /dev/null +++ b/docs/wayward/overview.md @@ -0,0 +1,37 @@ +# Wayward Web Framework + +Wayward lives in the namespace `wayward`, which by default is aliased as `w`. To disable this, compile your app with `WAYWARD_NO_SHORTHAND_NAMESPACE` defined. Every time you see `w::`, it can be substituted for `wayward::`. + +The core of a Wayward app is an instance of `App`, which is used to define the routes and bring up a listening HTTP server. + +The simplest Wayward app looks like this: + + #include + + int main(int argc, char** argv) { + w::App app { argc, argv }; + + app.get("/", [](w::Request& req) { + return w::render_text("Hello, World!"); + }); + + return app.run(); + } + +A slightly more advanced example could include a parameter in the route definition: + + #include + + int main(int argc, char** argv) { + w::App app { argc, argv }; + + app.get("/hello/:person", [](w::Request& req) { + return w::render_text("Hello, {0}!", req.params["person"]); + }); + } + +Going to `/hello/Steve` will then render the text "Hello, Steve!" in the browser. + +See the documentation for [Routing](routing.md) for more ways to define routes. + +See the [Advanced Routes](advanced_routes.md) for ways to define more complex route handlers. diff --git a/docs/wayward/request.md b/docs/wayward/request.md new file mode 100644 index 0000000..7d10f5d --- /dev/null +++ b/docs/wayward/request.md @@ -0,0 +1,9 @@ +# Requests + +All route handlers get called with a reference to a `w::Request` object. The `Request` object is a simple structure representing an HTTP request. + +See also: [Wayward Support: HTTP Library](support/http.md) + +From the perspective of a route handler, the most interesting parts of a request are the headers, the params, the [URI](support/uri.md), and the request body, if one is provided. + +Params defined in the route with the `:` syntax will appear in the `params` member, as well as any GET and POST parameters passed from the client. diff --git a/docs/wayward/response.md b/docs/wayward/response.md new file mode 100644 index 0000000..d43f5f8 --- /dev/null +++ b/docs/wayward/response.md @@ -0,0 +1,23 @@ +# Rendering Responses + +Wayward comes with a number of predefined ways to render a quick HTTP response. + +## w::not_found + +Returns a generic 404 Not Found with Content-Type `text/plain`. + +## w::render_text + +Invoke: `render_text(format, ...)` + +Returns a 200 OK response with Content-Type `text/plain`. The body of the response is the interpolation of the arguments. `w::render_text` treats its arguments equivalent to [`w::format`](support/format.md). + +## w::redirect + +Invoke: `redirect(new_url, [status])` + +Returns a 302 Found response (or a different status code, if `status` is set), with the `Location` header set to `new_url`. + +## w::render + +See: [Templates](templates.md) diff --git a/docs/wayward/routing.md b/docs/wayward/routing.md new file mode 100644 index 0000000..36c6b2a --- /dev/null +++ b/docs/wayward/routing.md @@ -0,0 +1,42 @@ +# Routing + +Routes in Wayward are essentially functions that take a HTTP request as input and return a HTTP response. + +The basic way to define a route is to simply call app.get/post/put/etc. with the desired path and a function object that can respond to request matching the path. + +The `App` class does the heavy lifting in routes, and has the following interface: + + +--- + +# App + +--- + +[wayward/w.hpp](https://github.com/simonask/w/blob/master/wayward/w.hpp) + +## assets + +Invoke: `assets(uri_path, filesystem_path)` + +Define how and where static assets are located and accessed. + +## get, put, patch, post, del, head, options + +Invoke: `method(route, handler)` + +Handler is a function with the signature `Response(Request&)`. + +Route is a slash-separated path. Path elements beginning with `:` will be interpreted as input parameters to the request handler. Example: + + app.post("/foo/:bar", handler) + +This route will respond to any path starting with "/foo/", and whatever comes after that will be passed to the handler in `request.params["bar"]`. + +The method `del` defines a route that responds to DELETE requests, and is abbreviated to avoid collision with the C++ keyword `delete`. + +## run + +Invoke: `run()` + +Starts listening and serving requests. diff --git a/docs/wayward/support/any.md b/docs/wayward/support/any.md new file mode 100644 index 0000000..8593b4c --- /dev/null +++ b/docs/wayward/support/any.md @@ -0,0 +1,84 @@ +# Class: Any + +[wayward/support/any.hpp](https://github.com/simonask/w/blob/master/wayward/support/any.hpp) + +`Any` can contain any object by value. When an `Any` is constructed, the input value is copied or moved into the `Any` instance's private storage as well as a piece of unique type information. + +The underlying type of an `Any` instance can be queried with the method [`is_a()`](#is_a), which returns a boolean. The held value can be accessed with [`get()`](#get), which returns a [`Maybe`](maybe.md). If the held value is not of type `T`, `Nothing` is returned. + +The held value can also be accessed by reference by passing a closure to [`when(...)`](#whenclosure), where the closure will only be run if the type matches. The argument to the closure will be a reference to the held value. + +The two related classes `AnyRef` and `AnyConstRef` behave identical to `Any`, except they don't own the value they refer to. Instead, they hold a reference and a const reference, respectively. Care must be taken to ensure that the lifetime of the `AnyRef` or `AnyConstRef` does not exceed the lifetime of the object they refer to (as a rule of thumb: use `AnyRef`/`AnyConstRef` in method signatures only, and never store the reference). + +--- + +# Constructors + +--- + +## Any(T&& value) + +Construct an `Any` by moving `value`. + +## Any(const T& value) + +Construct an `Any` by copying `value`. + +## Any(NothingType) + +Construct an empty `Any`. + +## Any() + +Identical to `Any(NothingType)`. + +## Any(Any&&) and Any(const Any&) + +Move or copy an `Any`, if the underlying type supports the respective operation. + +If the underlying type does not support moving, the value will be copied instead. If the underlying type does not support copying, and a copy is requested, an exception is thrown. + +--- + +# Methods + +--- + +## is_a + +Invoke: `is_a()` + +Returns: `bool` + +Takes a template parameter `T`. Returns true if the held value has type `T`. + +## get + +Invoke: `get()` + +Returns: [`Maybe`](maybe.md) + +Takes a template parameter `T`. Returns a `Maybe` containing the value if the value has type `T`. Otherwise, returns `Nothing`. + +## when(closure) + +Invoke: `when(closure)` + +Returns: `Maybe`, where `U` is the return type of `closure`. + +Takes a template parameter `T` and a generic closure/callback. If the held value is of type `T`, the closure is called with a reference to the held value. + +Example: + + Any any { (int)123 }; + any.when([](const int& number) { + std::cout << "Any was an int: " << number << std::endl; + }); + +## type_info + +Invoke: `type_info()` + +Returns: A pointer to a [TypeInfo](reflection.md#typeinfo). + + diff --git a/docs/wayward/support/command_line_options.md b/docs/wayward/support/command_line_options.md new file mode 100644 index 0000000..7c6dcdb --- /dev/null +++ b/docs/wayward/support/command_line_options.md @@ -0,0 +1,89 @@ +# Class: CommandLineOptions + +[wayward/support/command_line_options.hpp](https://github.com/simonask/w/blob/master/wayward/support/command_line_options.hpp) + +`CommandLineOptions` is a very simple command line option parser. + +Example usage: + + CommandLineOptions cmd; + + cmd.description("Provide option 'foo'."); + cmd.option("--foo", "-f", []() { ... }); + + cmd.description("Set the number with --number=123 or -n 123."); + cmd.option("--number", "-n", [](int64_t) { ... }); + + cmd.description("Provide a string with --string=foo or -s foo"); + cmd.option("--string", "-s", [](std::string) { ... }); + + cmd.usage(); + cmd.parse(argc, argv); + +`CommandLineOptions` recognizes the `--help` and `-h` options by default after the call to [`usage()`](#usage), and displays all available options with the associated description (if any). The closures provided with the [`option`](#option) are run when the option is encountered during [`parse`](#parse). + +--- + +# Constructors + +## CommandLineOptions() + +Empty `CommandLineOptions`. + +--- + +# Methods + +--- + +## program_name + +Invoke: `program_name(std::string)` + +Set the program name. Defaults to `argv[0]`. If a program name is provided manually, `argv[0]` is interpreted as an option instead of the binary path. + +## description + +Invoke: `description(std::string)` + +Set the description of the next option that is defined. Each successive call to `description` overwrites the previous value. + +## option + +Invoke: `option(long_form, short_form, callback)` or `option(long_form, callback)` + +`callback` is either an `std::function`, or an `std::function`, or an `std::function`. If the callback takes no parameters, the option is assumed to not take a value argument. If the callback takes a string argument, the value argument is passed directly. If the callback takes an integer argument, the value argument is converted to an integer first. + +## usage + +Invoke: `usage(long_form, short_form)` or `usage(action, long_form, short_form)` + +Configures the "usage" display. By default, `CommandLineOptions` interprets the options `--help` and `-h` and calls [`display_usage_and_exit()`](#displayusageandexit). + +## unrecognized + +Invoke: `unrecognized(callback)` + +`callback` is an `std::function`, and it will be invoked for each unrecognized option. By default, `CommandLineOptions` does nothing when encountering an unrecognized option. + +## display_usage + +Invoke: `display_usage()` + +Print usage text to `std::cout`. + +## display_usage_and_exit + +Invoke: `dispay_usage_and_exit()` + +Print usage text to `std::cout` and call `std::exit(1)`. + +## parse + +Invoke: `parse(argc, argv)` + +Returns: `std::vector` (a list of unrecognized command line options) + +Parse `argc` arguments of `argv` and execute the relevant actions. + + diff --git a/docs/wayward/support/data_franca.md b/docs/wayward/support/data_franca.md new file mode 100644 index 0000000..6f6795d --- /dev/null +++ b/docs/wayward/support/data_franca.md @@ -0,0 +1,246 @@ +# Data Franca API + +Github: [wayward/support/data_franca](https://github.com/simonask/w/tree/master/wayward/support/data_franca) + +Namespace: `wayward::data_franca` + +Data Franca is a data observation/mutation library. It provides a single, unified interface to querying and modifying arbitrary data structures. + +Possible use cases: + +- Serialization +- Data binding +- Interfacing with scripting languages, namely template languages + +Data Franca is based around 4 core concepts: + +- **[Spectators](#spectators):** Query objects providing a read-only interface to any data structure. +- **[Mutators](#mutators):** Same as spectators, except they can also modify data structures. +- **[Adapters](#adapters):** Interface classes that bind the spectator/mutator interface to arbitrary data types. Simply put, to be able to inspect or mutate a custom object, an adapter needs to be defined for it. +- **[Objects](#objects):** Opaque, mutable object structure that always supports all operations. This is handy for building structured data on the fly, e.g. constructing a JSON document. Objects behave like mutators, except they own their data. + +--- + +# Spectators + +--- + +Making a spectator can be constructed from a reference to any object that has an [adapter](#adapters). + + std::map dict = {{"foo", "bar"}, {"baz", "car"}}; + + Spectator spec { dict }; + std::string result; + if (spec["foo"] >> result) { + std::cout << result; // Will print "bar". + } + +Scalar values (strings, numbers) are extracted with `operator>>`, which returns a boolean that is true if the value could be successfully extracted to the requested type. Extraction attempts to convert the underlying value to the requested type, so if the underlying type is a string, but an integer or float is requested, it will be interpreted as a string representation of a number. Extraction as string always succeeds, unless the accessed value is a dictionary or list. + +Compound values (dictionaries, lists) are accessed with `operator[]`. If called with a string argument, the value is assumed to be a dictionary, and if called with an integer argument, it is assumed to be a list. If the underlying type is not the requested container type (e.g. if a list or scalar is accessed with a string subscript), an empty spectator representing `Nothing` will be returned. + +## type + +Returns: `DataType` + +One of `DataType::Nothing`, `DataType::Boolean`, `DataType::Integer`, `DataType::Real`, `DataType::String`, `DataType::List`, `DataType::Dictionary`. + +## is_nothing + +Returns: `bool` + +True if the represented value is conceptually "nothing" (`Nothing`, `NULL`, etc.). + +## operator bool + +Returns: `bool` + +True if the represented value is not conceptually "nothing". Reverse of [is_nothing](#isnothing). + +## operator>>(Boolean&) + +Returns: `bool` + +Extract boolean value. Returns true if the underlying type is a boolean, or could be converted to a boolean. + +## operator>>(Integer&) + +Returns: `bool` + +Extract integer value. Returns true if the underlying type is an integer, or could be converted to an integer. + +## operator>>(Real&) + +Returns: `bool` + +Extract float value. Returns true if the underlying type is a float, or could be converted to a float. + +## operator>>(String&) + +Returns: `bool` + +Extract string value. Returns true if the underlying type is a string, or could be converted to a string. + +## operator[](std::string) + +Returns: `Spectator` + +Access value as a dictionary. If the value is not a dictionary, or the key does not exist in the dictionary, a spectator representing "nothing" is returned. + +## operator[](size_t) + +Returns: `Spectator` + +Access value as a list. If the value is not a list, or the index is out of bounds, a spectator representing "nothing" is returned. + +## has_key + +Invoke: `has_key(key)` + +Returns: `bool` + +True if the underlying value is a dictionary and has key `key`. + +## length + +Invoke: `length()` + +Returns: `size_t` + +If the underlying value is a dictionary or list, returns the number of pairs or elements. Otherwise, returns 0. + +## begin + +Invoke: `begin()` + +Returns: An iterator that can be used to enumerate all elements or pairs of the underlying list or dictionary, or "end" if they are empty, or "end" if the underlying value is not a container. + +## end + +Invoke: `end()` + +Returns: An iterator pointing to the conceptual "end" (nothing). May never be dereferenced. + +--- + +# Mutators + +--- + +Mutators are used like [Spectators](#spectators), except they can also modify the underlying data structures. + +Example: + + std::map dict = {{"foo", "bar"}, {"baz", "car"}}; + + Mutator mut { dict }; + if (mut["foo"] << "lol") { + // dict["foo"] == "lol" + } + +In contrast to spectators, type coercion is enforced at the adapter level — some adapters allow changing the type of represented values at runtime, while other don't, so the mutator interface can't make the decision. Therefore, setting a value can fail, and the return value of `operator<<` determines if the write was successful. + +In addition to the interface provided by [Spectators](#spectators), they implement the following: + +## operator<<(NothingType) + +Returns: `bool` + +Set the represented value to `Nothing`. + +## operator<<(Boolean) + +Returns: `bool` + +Set the represented value to the boolean value. + +## operator<<(Integer) + +Returns: `bool` + +Set the represented value to the integer value. + +## operator<<(Real) + +Returns: `bool` + +Set the represented value to the floating-point value. + +## operator<<(String) + +Returns: `bool` + +Set the represented value to the string value. + +## operator[](std::string) + +Returns: `Mutator` + +Access dictionary value. Some adapters may coerce the underlying type to become a dictionary. + +## operator[](size_t) + +Returns: `Mutator` + +Access list element. Some adapters may coerce the underlying type to become a list. + +## erase + +Invoke: `erase(std::string)` + +Returns: `bool` + +Erase the dictionary element. Returns true if the erase was successful. + +## push_back + +Invoke: `push_back(value)` + +Returns: `bool` + +Append a value to the end of a list. Some adapters may coerce the underlying type to become a list. + + +--- + +# Adapters + +--- + +## Implementing custom adapters + +To allow inspection of a custom type, implement `Adapter`, where `T` is your custom type. + +`Adapter` must implement the interface `IAdapter`, which implies `IReader` and `IWriter`. + +Relevant interfaces: + +- IAdapter: [wayward/support/data_franca/adapter.hpp](https://github.com/simonask/w/blob/master/wayward/support/data_franca/adapter.hpp) +- IReader: [wayward/support/data_franca/reader.hpp](https://github.com/simonask/w/blob/master/wayward/support/data_franca/reader.hpp) +- IWriter: [wayward/support/data_franca/writer.hpp](https://github.com/simonask/w/blob/master/wayward/support/data_franca/writer.hpp) + +--- + +# Objects + +--- + +A Data Franca Object implements the [Mutator](#mutators) interface (including the [Spectator](#spectators) interface), but owns its own data. + +Write-operations on objects always convert the internal type of the object to match the corresponding write operation. Therefore, a write operation on an Object never fails. + +Example: + + Object o; + o["foo"]["bar"] << 123; + o["lol"].push_back("baz"); + o["lol"].push_back("boo"); + +If `o` in the above were converted to, say, JSON, it would look like this: + + { + "foo": { + "bar": 123 + }, + "lol": ["baz", "boo"] + } diff --git a/docs/wayward/support/datetime.md b/docs/wayward/support/datetime.md new file mode 100644 index 0000000..5fefa73 --- /dev/null +++ b/docs/wayward/support/datetime.md @@ -0,0 +1,29 @@ +# Library: DateTime + +[wayward/support/datetime](https://github.com/simonask/w/tree/master/wayward/support/datetime) + +TODO: Overview + +--- + +# Time points + +--- + +--- + +# Intervals + +--- + +--- + +# Parsing + +--- + +--- + +# Formatting + +--- diff --git a/docs/wayward/support/either.md b/docs/wayward/support/either.md new file mode 100644 index 0000000..a1f4f7c --- /dev/null +++ b/docs/wayward/support/either.md @@ -0,0 +1,18 @@ +# Class: Either + +[wayward/support/either.hpp](https://github.com/simonask/w/blob/master/wayward/support/either.hpp) + +TODO: Overview + +--- + +# Constructors + +--- + +--- + +# Methods + +--- + diff --git a/docs/wayward/support/error.md b/docs/wayward/support/error.md new file mode 100644 index 0000000..9238180 --- /dev/null +++ b/docs/wayward/support/error.md @@ -0,0 +1,35 @@ +# Class: Error + +[wayward/support/error.hpp](https://github.com/simonask/w/blob/master/wayward/support/error.hpp) + +`Error` is a base class for all exceptions that want to capture a stack trace. + +It derives from `std::runtime_error` so as to be compatible with standard C++ error handling. + +--- + +# Constructors + +--- + +## Error(const std::string& message) + +Construct an `Error` with a `message`. + +--- + +# Methods + +--- + +## what + +Invoke: `what()` + +Returns: `const char*` pointing to an error message describing the message. + +## backtrace + +Invoke: `backtrace()` + +Returns: `std::vector` — a list of stack trace entries (demangled where applicable), in descending order from call site to program entry. diff --git a/docs/wayward/support/fiber.md b/docs/wayward/support/fiber.md new file mode 100644 index 0000000..e1c98f1 --- /dev/null +++ b/docs/wayward/support/fiber.md @@ -0,0 +1,69 @@ +# Class: Fiber + +[wayward/support/fiber.hpp](https://github.com/simonask/w/blob/master/wayward/support/fiber.hpp) + +Fiber is an implementation of coroutines. It uses `setjmp`/`longjmp` internally to manage execution contexts, but supports exceptions and stack unwinding. + +--- + +# Types + +--- + +## FiberPtr + +An opaque pointer with value semantics representing a reference to a fiber/coroutine. + +It is currently defined in terms of `std::shared_ptr`. When the last reference to a fiber is destroyed, the fiber is terminated and destroyed as well. + +## fiber::Function + +Defined as `std::function` — The entry point of a fiber. + +## fiber::ErrorHandler + +Defined as `std::function` — A callback that will be called if an exception is uncaught and the stack unwinds beyond a fiber boundary. + +--- + +# Functions + +--- + +## fiber::current + +Returns: A `FiberPtr` representing the currently running fiber. If a thread has not launched any fibers yet, a special `FiberPtr` representing the thread is returned. There is no difference between this and regular fibers, except all threads including the main thread always have an implicit reference to the main thread fiber, so it never goes out of scope before the thread finishes. + +## fiber::create + +Invoke: `fiber::create(Function)` or `fiber::create(Function, ErrorHandler)` + +Create a new fiber (optionally with an error handler) without starting it. + +If an [`ErrorHandler`](#fiberErrorHandler) is provided, it will be called if the fiber throws an exception that remains uncaught at the fiber boundary. The default error handler terminates the program (similar to how an uncaught exception outside of fibers behave). + +## fiber::resume + +Invoke: `fiber::resume(FiberPtr)` + +Start or resume a fiber. + +## fiber::create + +Invoke: `fiber::start(Function)` + +Equivalent to [`fiber::create`](#fibercreate) + [`fiber::resume`](#fiberresume) + +## fiber::terminate + +Invoke: `fiber::terminate(FiberPtr)` + +Terminates a fiber, unwinding its stack and running all destructors. + +Internally, this resumes the fiber with a signal that tells the fiber to throw a special exception handled silently at the fiber boundary before termination. + +## fiber::yield + +Invoke: `fiber::yield()` + +Resumes the fiber that resumed the current fiber. Throws an exception if the current fiber is orphaned (i.e., was never resumed by anybody else, i.e. is most likely the main fiber). diff --git a/docs/wayward/support/format.md b/docs/wayward/support/format.md new file mode 100644 index 0000000..46af952 --- /dev/null +++ b/docs/wayward/support/format.md @@ -0,0 +1,32 @@ +# Library: format + +[wayward/support/format.hpp](https://github.com/simonask/w/blob/master/wayward/support/format.hpp) + +`w::format` is the main entry point for string interpolation in Wayward. It has two main modes of operation: Numbered and Named interpolation. + +`w::format` uses the standard `operator<<(std::ostream&)` interface internally to convert arguments to strings. This may or may not change in the future. + +# Numbered Interpolation + +This performs positional interpolation, and expects string placeholders in the format *{n}*, where *n* is a number starting at 0. + +Example: + + w::format("Hello, {0}!", "World"); // => "Hello, World!" + +Example with numbers: + + w::format("{0} + {1} = {2}", 123, 456, 579); // => "123 + 456 = 579" + +# Named Interpolation + +Instead of positional arguments, `w::format` also supports named arguments, like so: + + w::format("Hello, {entity}! I am {subject}.", { + {"entity", "World"}, + {"subject", "Simon"} + }); // => "Hello, World! I am Simon." + +# Output formatting + +TODO (NIY) diff --git a/docs/wayward/support/http.md b/docs/wayward/support/http.md new file mode 100644 index 0000000..8fbcd30 --- /dev/null +++ b/docs/wayward/support/http.md @@ -0,0 +1,5 @@ +# Library: HTTP + +# HTTPServer + +# HTTPClient diff --git a/docs/wayward/support/json.md b/docs/wayward/support/json.md new file mode 100644 index 0000000..ad799c8 --- /dev/null +++ b/docs/wayward/support/json.md @@ -0,0 +1,3 @@ +# Library: JSON + +TODO diff --git a/docs/wayward/support/logger.md b/docs/wayward/support/logger.md new file mode 100644 index 0000000..5797170 --- /dev/null +++ b/docs/wayward/support/logger.md @@ -0,0 +1,3 @@ +# Library: Logger + +TODO diff --git a/docs/wayward/support/maybe.md b/docs/wayward/support/maybe.md new file mode 100644 index 0000000..4efce64 --- /dev/null +++ b/docs/wayward/support/maybe.md @@ -0,0 +1,83 @@ +# Class: Maybe + +`Maybe` is an option type that either holds a value of type `T` or `Nothing`. + +The contents of a `Maybe` can be accessed either with an "unsafe" API equivalent to `boost::optional` (or `std::optional` from C++14), or with a safer "monadic" interface that prevents errors through the type system. + +If the contents of a `Maybe` are accessed when the `Maybe` contains `Nothing`, an exception is thrown of type `EmptyMaybeDereference`. + +`Maybe` is specialized to support reference-like `Maybe`s, in which case they behave exactly like `Maybe>`. + +`Maybe` is also specialized for all ["pointer-like" types](static.md#ispointerlike), so a `Maybe>` takes exactly the same memory as an `std::unique_ptr`, and treats the NULL pointer as the "Nothing" value. + +--- + +# Constructors + +--- + +## Maybe(T value) + +Construct a `Maybe` containing `value`. + +## Maybe(NothingType) + +Construct an empty `Maybe`. + +## Maybe(const Maybe&) + +Copy-constructor. + +## Maybe(Maybe&&) + +Move-constructor. + +--- + +# Methods + +--- + +## get + +Returns a point to the object. WARNING: Throws exception if the Maybe is empty. + +## operator bool + +Returns true if the Maybe has an object, otherwise false. + +## operator-> + +Access the object as a pointer. WARNING: Throws exception if the Maybe is empty. + +## operator* + +Access the object by dereferencing. WARNING: Throws exception if the Maybe is empty. + +## swap(Maybe&) + +Swaps the contents of this Maybe with another of the same type. + +--- + +Functions + +--- + +## Just + +Invoke: `Just(x)` + +Returns: `Maybe` + +Convenience function for constructing `Maybe` on the go without typing out the full type name for the constructor. + +## monad::fmap + +Invoke: `fmap(maybe, closure)` + +Returns: `Maybe`, where `U` is the return type of `closure`. + +The safer way to access the internals of a `Maybe` — the closure will be invoked when the `Maybe` contains a value, and the return value is another `Maybe` with the return value of the closure. + +See also: [Monads/fmap](monads.md#monadfmap) diff --git a/docs/wayward/support/monads.md b/docs/wayward/support/monads.md new file mode 100644 index 0000000..52cd669 --- /dev/null +++ b/docs/wayward/support/monads.md @@ -0,0 +1,3 @@ +# Monads + +TODO diff --git a/docs/wayward/support/overview.md b/docs/wayward/support/overview.md new file mode 100644 index 0000000..61f0f83 --- /dev/null +++ b/docs/wayward/support/overview.md @@ -0,0 +1,29 @@ +# Wayward Support API + +Wayward Support is a collection of classes and functions designed to work in addition to the C++ Standard Library. + +Some classes in Wayward Support are simpler implementations of equivalents in Boost without as much template cruft, while implement concepts that are unique to Wayward. + +## Classes + +- [Any](any.md) — Hold any value. +- [CommandLineOptions](command_line_options.md) — Simple command line option parser. +- [Either](either.md) — Hold one of a predefined set of value types (tagged union). +- [Error](error.md) — Base class for all exceptions that captures a stack trace. +- [Fiber](fiber.md) — Coroutine implementation for cooperative multitasking. +- [Logger](logger.md) — Logging interface. +- [Maybe](maybe.md) — Option type, equivalent to `boost::optional` with some monadic extensions. +- [Result](result.md) — Either a value or an Error. +- [URI](uri.md) — Structured representation of a Uniform Resource Identifier. + +## Libraries + +- [Data Franca library](data_franca.md) — Data observation library. Provides unified access to any data type that implements an adapter, allowing users to query arbitrary data structures. The main use is to provide a way for templating languages to access structured data at runtime. +- [DateTime library](datetime.md) — The DateTime library provides an intuitive interface to POSIX date/time functions. +- [Format library](format.md) — String formatting and interpolation library. +- [HTTP library](http.md) — Evented HTTP Server and Client classes. +- [JSON library](json.md) — Convert arbitrary data structures (via [reflection](reflection.md) or [Data Franca](data_franca.md)) to and from JSON. +- [Monads](monads.md) — Common "monadic" idioms that can make some code patterns safer and easier to use. +- [Reflection library](reflection.md) — Runtime type reflection library. +- [Static Metaprogramming library](static.md) — Compile-time type reflection library (various extensions to the standard ``). +- [String utilities](string.md) — Commonly used string functions missing from the C++ Standard Library. diff --git a/docs/wayward/support/reflection.md b/docs/wayward/support/reflection.md new file mode 100644 index 0000000..f94882f --- /dev/null +++ b/docs/wayward/support/reflection.md @@ -0,0 +1,3 @@ +# Reflection + +TODO diff --git a/docs/wayward/support/result.md b/docs/wayward/support/result.md new file mode 100644 index 0000000..858ff08 --- /dev/null +++ b/docs/wayward/support/result.md @@ -0,0 +1,3 @@ +# Class: Result + +TODO diff --git a/docs/wayward/support/static.md b/docs/wayward/support/static.md new file mode 100644 index 0000000..7af0a93 --- /dev/null +++ b/docs/wayward/support/static.md @@ -0,0 +1,84 @@ +# Static Metaprogramming + +[wayward/support/meta.hpp](https://github.com/simonask/w/blob/master/wayward/support/meta.hpp) + +These are mainly supplements to the type traits supplied by the standard `` header. Admittedly, some of them are slightly idiosynchratic (I prefer CamelCased types, while the Standard Library always goes for snake\_case). Nevertheless, these utilities are used throughout Wayward Support, and some of them are generally handy. + +# Types + +## TrueType, FalseType + +Compile-time true/false. + +## TypeList + +Use: `TypeList<...>` + +A list of types. + +# Traits + +## IsPointerLike + +Use: `IsPointerLike::Value` + +True if `T` is a pointer-like type, i.e. if it is dereferenceable and nullable. Defaults to `std::is_pointer::value`, but specialized by default for `std::unique_ptr` and `std::shared_ptr` (as well as `CloningPtr`). This trait can be used to specialize container types that want to treat pointer-like types specially. + + +## Contains + +Use: `Contains>::Value` + +True if `TypeList<...>` contains `T`. + +## IndexOf + +Use: `IndexOf>::Value` + +The index of `T` in `TypeList<...>`, or `SIZE_T_MAX` if it doesn't exist. + +## RemoveConstRef + +Use: `typedef RemoveConstRef::Type` + +Input | Output +--------:|:------ +T | T +T& | T +const T& | T + +## MaxSize + +Use: `MaxSize>::Value` + +`Value` is the maximum size of all the types in the `TypeList<...>`. + +## MaxAlignment + +Use: `MaxAlignment>::Value` + +`Value` is the maximum alignment of all the types in the `TypeList<...>`. + +## AreAllCopyConstructible + +Use: `AreAllCopyConstructible>::Value` + +True if all types in the `TypeList<...>` are copy-constructible. + +## AreAllCopyAssignable + +Use: `AreAllCopyAssignable>::Value` + +True if all types in the `TypeList<...>` are copy-assignable. + +## AreAllMoveConstructible + +Use: `AreAllMoveConstructible>::Value` + +True if all types in the `TypeList<...>` are move-constructible. + +## AreAllMoveAssignable + +Use: `AreAllMoveAssignable>::Value` + +True if all types in the `TypeList<...>` are move-assignable. diff --git a/docs/wayward/support/string.md b/docs/wayward/support/string.md new file mode 100644 index 0000000..8595d50 --- /dev/null +++ b/docs/wayward/support/string.md @@ -0,0 +1,17 @@ +# Library: String utilities + +[wayward/support/string.hpp](https://github.com/simonask/w/blob/master/wayward/support/string.hpp) + +--- + +# Functions + +--- + +## split + +Use: `split(input, delimiter)` or `split(input, delimiter, max)` + +Returns: `std::vector` + +Splits the input string by delimiter and returns a vector of string, not including the delimiters. If `max` is provided, a maximum of `max` strings are returned. diff --git a/docs/wayward/support/uri.md b/docs/wayward/support/uri.md new file mode 100644 index 0000000..1c77aff --- /dev/null +++ b/docs/wayward/support/uri.md @@ -0,0 +1,100 @@ +# Class: URI + +[wayward/support/uri.hpp](https://github.com/simonask/w/blob/master/wayward/support/uri.hpp) + +`URI` represents a [Uniform Resource Identifier](http://en.wikipedia.org/wiki/Uniform_resource_identifier). + +--- + +# Constructors + +--- + +## URI() + +An empty URI. + +## URI(scheme, host, port, path, query, fragment) + +An URI representing `scheme://host:port/path?query#fragment`. Each component, except for host and scheme, may be empty. + +## URI::parse + +Invoke: `URI::parse(input)` + +Returns: `Maybe` + +Parses the `input` string and returns a `Maybe`. If the input string failed to parse as an URI, the return value is `Nothing`. + +--- + +# Members + +--- + +## scheme + +std::string + +## username + +std::string + +## password + +std::string + +## host + +std::string + +## port + +int + +## path + +std::string + +## query + +std::string + +## fragment + +std::string + + +--- + +# Methods + +--- + +## to_string + +Use: `to_string()` + +Returns: `std::string` — String representation of the URI. + +--- + +# Static Methods + +--- + +## URI::decode + +Use: `URI::decode(input)` + +Returns: `std::string` with UTF-8 encoded characters. + +Decodes URI entities. + +## URI::encode + +Use: `URI::encode(utf8)` + +Returns: `std::string` with URI-encoded characters. + +Encodes URI entities. diff --git a/docs/wayward/templates.md b/docs/wayward/templates.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/blog/blog.cpp b/examples/blog/blog.cpp index 4ff2c35..7326617 100644 --- a/examples/blog/blog.cpp +++ b/examples/blog/blog.cpp @@ -6,6 +6,12 @@ namespace app { using p::RecordPtr; + using p::ValidationErrors; + using w::redirect; + using w::render; + using w::format; + using w::HTML; + using w::JSON; struct PostsRoutes : w::Routes { w::Response get_all_posts(w::Request& req) { @@ -25,36 +31,53 @@ namespace app { void before(w::Request& req) override { int64_t id; if (req.params["post_id"] >> id) { - post = from().where(p::column(&Post::id) == id).first(); + post = from().where(p::eq(id, &Post::id)).inner_join(&Post::author).first(); } if (!post) throw w::not_found(); } w::Response get_post(w::Request& req) { - return w::render("post.html", {{"post", post}}); + return w::respond_to(req) + .when([&]() { + auto comments = post->comments.scope().left_outer_join(&Comment::author).order(&Comment::created_at); + return w::render("post.html", {{"post", post}, {"comments", comments}}); + }) + .when([&]() { + return w::render_json(post); + }); } w::Response put_post(w::Request& req) { p::assign_attributes(post, req.params["post"]); p::save(post); - return w::redirect(w::format("/posts/{0}", post->id)); + return w::respond_to(req) + .when([&]() { return w::redirect(w::format("/posts/{0}", post->id)); }) + .when([&]() { return w::render_json(post); }); } w::Response delete_post(w::Request& req) { - p::destroy(post); + // destroy(post); return w::redirect(w::format("/posts")); } w::Response get_comments(w::Request& req) { - return w::render("comments.html", {{"post", post}}); + auto comments = post->comments.scope().left_outer_join(&Comment::author); + return w::render("comments.html", {{"post", post}, {"comments", comments}}); } w::Response post_comment(w::Request& req) { - auto comment = create(req["comment"]); + auto comment = create(req.params["comment"]); comment->post = post; - p::save(comment); - return w::redirect(w::format("/posts/{0}", post->id); + + auto r = p::save(comment); + if (r) { + return redirect(format("/posts/{0}", post->id)); + } else { + throw *r.error(); + //session.flash["error"] = r.error(); + return redirect(format("/posts/{0}/comments", post->id)); + } } }; @@ -65,7 +88,7 @@ namespace app { PostRoutes::before(req); int64_t id; if (req.params["comment_id"] >> id) { - comment = from().where(p::column(&Comment::post) == post->id && p::column(&Comment::id) == id).first(); + comment = from().where(p::eq(&Comment::post, post->id) && p::eq(id, &Comment::id)).first(); } } diff --git a/examples/blog/models.hpp b/examples/blog/models.hpp index 46f5e25..8c71841 100644 --- a/examples/blog/models.hpp +++ b/examples/blog/models.hpp @@ -48,8 +48,8 @@ PERSISTENCE(User) { property(&User::password_salt, "password_salt"); property(&User::name, "name"); - has_many(&User::posts, "author_id"); - has_many(&User::comments, "author_id"); + has_many(&User::posts, "posts", "author_id"); + has_many(&User::comments, "comments", "author_id"); } PERSISTENCE(Post) { @@ -60,8 +60,8 @@ PERSISTENCE(Post) { property(&Post::title, "title"); property(&Post::text, "text"); - belongs_to(&Post::author, "author_id"); - has_many(&Post::comments, "author_id"); + belongs_to(&Post::author, "author"); + has_many(&Post::comments, "comments", "post_id"); } PERSISTENCE(Comment) { @@ -70,6 +70,6 @@ PERSISTENCE(Comment) { property(&Comment::updated_at, "updated_at"); property(&Comment::text, "text"); - belongs_to(&Comment::author, "author_id"); - belongs_to(&Comment::post, "post_id"); + belongs_to(&Comment::author, "author"); + belongs_to(&Comment::post, "post"); } diff --git a/examples/blog/views/comments.html b/examples/blog/views/comments.html new file mode 100644 index 0000000..a99949d --- /dev/null +++ b/examples/blog/views/comments.html @@ -0,0 +1,10 @@ +{% extends "views/layout.html" %} +{% block content %} +

Comments for post: {{ post.title }}

+{% for comment in comments %} +
+

{{ comment.text }}

+

Author: {{ comment.author.name }} ({{ comment.author.email }})

+
+{% endfor %} +{% endblock %} diff --git a/examples/blog/views/post.html b/examples/blog/views/post.html index aa8f3c1..4ed1467 100644 --- a/examples/blog/views/post.html +++ b/examples/blog/views/post.html @@ -4,5 +4,20 @@ Back to posts

{{ post.title }}

+

{{ post.text }}

+ +

Comments

+{% for comment in comments %} +
+

{{ comment.text }}

+ +
+{% endfor %} + +
+ + + +
{% endblock %} diff --git a/examples/blog/views/test.html b/examples/blog/views/test.html index a25b749..f060212 100644 --- a/examples/blog/views/test.html +++ b/examples/blog/views/test.html @@ -1 +1,3 @@ -Synth message: {{ message }} +

Synth message: {{ message }}

+ +

{{ params }}

diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..9ef222d --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,42 @@ +site_name: Wayward Documentation +site_description: Wayward Web Framework documentation + +repo_url: https://github.com/simonask/w + +pages: +# Base: +- [index.md, "Overview"] + + +# Wayward API: +- ["wayward/overview.md", "Wayward API", "Overview"] +- ["wayward/routing.md", "Wayward API", "Routing"] +- ["wayward/request.md", "Wayward API", "Requests"] +- ["wayward/response.md", "Wayward API", "Rendering Responses"] +- ["wayward/advanced_routes.md", "Wayward API", "Advanced Routes"] +- ["wayward/templates.md", "Wayward API", "Templates"] + +# Wayward Support API: +- ["wayward/support/overview.md", "Wayward Support API", "Overview"] +- ["wayward/support/any.md", "Wayward Support API", "Any"] +- ["wayward/support/command_line_options.md", "Wayward Support API", "CommandLineOptions"] +- ["wayward/support/data_franca.md", "Wayward Support API", "Data Franca"] +- ["wayward/support/datetime.md", "Wayward Support API", "DateTime"] +- ["wayward/support/either.md", "Wayward Support API", "Either"] +- ["wayward/support/error.md", "Wayward Support API", "Error"] +- ["wayward/support/fiber.md", "Wayward Support API", "Fiber"] +- ["wayward/support/format.md", "Wayward Support API", "Format"] +- ["wayward/support/http.md", "Wayward Support API", "HTTP"] +- ["wayward/support/json.md", "Wayward Support API", "JSON"] +- ["wayward/support/logger.md", "Wayward Support API", "Logger"] +- ["wayward/support/maybe.md", "Wayward Support API", "Maybe"] +- ["wayward/support/monads.md", "Wayward Support API", "Monads"] +- ["wayward/support/reflection.md", "Wayward Support API", "Reflection"] +- ["wayward/support/result.md", "Wayward Support API", "Result"] +- ["wayward/support/static.md", "Wayward Support API", "Static Metaprogramming"] +- ["wayward/support/string.md", "Wayward Support API", "String utilities"] +- ["wayward/support/uri.md", "Wayward Support API", "URI"] + +# Persistence API: +- [persistence.md, "Persistence API"] +theme: flatly diff --git a/persistence/postgresql.cpp b/persistence/adapters/postgresql/connection.cpp similarity index 91% rename from persistence/postgresql.cpp rename to persistence/adapters/postgresql/connection.cpp index d16c38d..87a95fd 100644 --- a/persistence/postgresql.cpp +++ b/persistence/adapters/postgresql/connection.cpp @@ -1,9 +1,11 @@ -#include -#include +#include "persistence/adapters/postgresql/connection.hpp" +#include "persistence/adapters/postgresql/renderers.hpp" #include +#include #include #include +#include #include #include @@ -40,9 +42,14 @@ namespace persistence { return PQgetisnull(result, row, idx); } - std::string get(size_t row, const std::string& col) const final { + Maybe get(size_t row, const std::string& col) const final { auto idx = PQfnumber(result, col.c_str()); - return std::string{PQgetvalue(result, row, idx)}; + bool is_null = PQgetisnull(result, row, idx); + if (!is_null) { + return std::string{PQgetvalue(result, row, idx)}; + } else { + return wayward::Nothing; + } } std::vector columns() const final { diff --git a/persistence/postgresql.hpp b/persistence/adapters/postgresql/connection.hpp similarity index 88% rename from persistence/postgresql.hpp rename to persistence/adapters/postgresql/connection.hpp index 376faf0..0094b60 100644 --- a/persistence/postgresql.hpp +++ b/persistence/adapters/postgresql/connection.hpp @@ -1,6 +1,6 @@ #pragma once -#ifndef PERSISTENCE_POSTGRESQL_HPP_INCLUDED -#define PERSISTENCE_POSTGRESQL_HPP_INCLUDED +#ifndef PERSISTENCE_ADAPTERS_POSTGRESQL_CONNECTION_HPP_INCLUDED +#define PERSISTENCE_ADAPTERS_POSTGRESQL_CONNECTION_HPP_INCLUDED #include #include @@ -36,7 +36,6 @@ namespace persistence { std::unique_ptr execute(std::string sql) final; std::unique_ptr execute(const ast::IQuery& query, const relational_algebra::IResolveSymbolicRelation&) final; - // static std::unique_ptr connect(std::string connection_string, std::string* out_error = nullptr); private: @@ -46,4 +45,4 @@ namespace persistence { }; } -#endif // PERSISTENCE_POSTGRESQL_HPP_INCLUDED +#endif // PERSISTENCE_ADAPTERS_POSTGRESQL_CONNECTION_HPP_INCLUDED diff --git a/persistence/postgresql_renderers.cpp b/persistence/adapters/postgresql/renderers.cpp similarity index 88% rename from persistence/postgresql_renderers.cpp rename to persistence/adapters/postgresql/renderers.cpp index 224a1af..d12379d 100644 --- a/persistence/postgresql_renderers.cpp +++ b/persistence/adapters/postgresql/renderers.cpp @@ -1,4 +1,4 @@ -#include +#include "persistence/adapters/postgresql/renderers.hpp" #include #include @@ -77,7 +77,38 @@ namespace persistence { } std::string PostgreSQLQueryRenderer::render(const InsertQuery& x) { - return "NIY"; + std::stringstream ss; + ss << "INSERT INTO " << x.relation << " ("; + for (auto it = x.columns.begin(); it != x.columns.end();) { + ss << "\"" << *it << "\""; + ++it; + if (it != x.columns.end()) { + ss << ", "; + } + } + ss << ") VALUES ("; + PostgreSQLValueRenderer vr { conn, symbolic_relation_resolver }; + for (auto it = x.values.begin(); it != x.values.end();) { + ss << (*it)->to_sql(vr); + ++it; + if (it != x.values.end()) { + ss << ", "; + } + } + ss << ")"; + + if (x.returning_columns.size()) { + ss << " RETURNING "; + for (auto it = x.returning_columns.begin(); it != x.returning_columns.end();) { + ss << "\"" << *it << "\""; + ++it; + if (it != x.returning_columns.end()) { + ss << ", "; + } + } + } + + return ss.str(); } std::string PostgreSQLValueRenderer::render(const StarFrom& x) { diff --git a/persistence/postgresql_renderers.hpp b/persistence/adapters/postgresql/renderers.hpp similarity index 89% rename from persistence/postgresql_renderers.hpp rename to persistence/adapters/postgresql/renderers.hpp index 2c95f10..472b037 100644 --- a/persistence/postgresql_renderers.hpp +++ b/persistence/adapters/postgresql/renderers.hpp @@ -1,10 +1,9 @@ #pragma once -#ifndef PERSISTENCE_POSTGRESQL_RENDERERS_HPP_INCLUDED -#define PERSISTENCE_POSTGRESQL_RENDERERS_HPP_INCLUDED +#ifndef PERSISTENCE_ADAPTERS_POSTGRESQL_RENDERERS_HPP_INCLUDED +#define PERSISTENCE_ADAPTERS_POSTGRESQL_RENDERERS_HPP_INCLUDED #include -#include - +#include #include namespace persistence { @@ -46,4 +45,4 @@ namespace persistence { }; } -#endif // PERSISTENCE_POSTGRESQL_RENDERERS_HPP_INCLUDED +#endif // PERSISTENCE_ADAPTERS_POSTGRESQL_RENDERERS_HPP_INCLUDED diff --git a/persistence/assign_attributes.cpp b/persistence/assign_attributes.cpp new file mode 100644 index 0000000..d2fb44e --- /dev/null +++ b/persistence/assign_attributes.cpp @@ -0,0 +1,198 @@ +#include "persistence/assign_attributes.hpp" +#include + +namespace persistence { + namespace { + using namespace wayward::data_franca; + + struct BestFaithDeserializerDataVisitor : wayward::DataVisitor { + const Spectator& spec; + BestFaithDeserializerDataVisitor(const Spectator& spec) : spec(spec) {} + + void visit_nil() final { + return; + } + + void visit_boolean(bool& b) final { + if (spec >> b) return; + Integer n; + if (spec >> n) { b = n != 0; return; } + Real r; + if (spec >> r) { b = r != 0; return; } + String str; + if (spec >> str) { b = str != "false"; return; } + } + + void visit_int8(std::int8_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_int16(std::int16_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_int32(std::int32_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_int64(std::int64_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_uint8(std::uint8_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_uint16(std::uint16_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_uint32(std::uint32_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_uint64(std::uint64_t& o) final { + Integer i; + if (spec >> i) { o = static_cast(i); } + Real r; + if (spec >> r) { o = static_cast(r); } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_float(float& o) final { + Real r; + if (spec >> r) { o = static_cast(r); return; } + Integer i; + if (spec >> i) { o = static_cast(i); return; } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_double(double& o) final { + Real r; + if (spec >> r) { o = static_cast(r); return; } + Integer i; + if (spec >> i) { o = static_cast(i); return; } + String str; + if (spec >> str) { + std::stringstream ss { std::move(str) }; + ss >> o; + } + } + + void visit_string(std::string& o) final { + Integer i; + if (spec >> i) { + std::stringstream ss; + ss << i; + o = ss.str(); + return; + } + Real r; + if (spec >> r) { + std::stringstream ss; + ss << r; + o = ss.str(); + return; + } + + spec >> o; + } + + void visit_key_value(const std::string& key, AnyRef data, const IType* type) final { + auto sub_spectator = spec[key]; + BestFaithDeserializerDataVisitor sub_visitor { sub_spectator }; + type->visit_data(data, sub_visitor); + } + + void visit_element(std::int64_t idx, AnyRef data, const IType* type) final { + auto sub_spectator = spec[idx]; + BestFaithDeserializerDataVisitor sub_visitor { sub_spectator }; + type->visit_data(data, sub_visitor); + } + + void visit_special(AnyRef data, const IType* type) final { + // XXX: TODO + } + + bool can_modify() const { + return true; + } + + bool is_nil_at_current() const { + return spec.type() == DataType::Nothing; + } + + }; + } + + namespace detail { + void assign_attributes(AnyRef record, const IRecordType* record_type, const wayward::data_franca::Spectator& data) { + BestFaithDeserializerDataVisitor visitor { data }; + record_type->visit_data(record, visitor); + } + } +} diff --git a/persistence/assign_attributes.hpp b/persistence/assign_attributes.hpp index 3180062..a536943 100644 --- a/persistence/assign_attributes.hpp +++ b/persistence/assign_attributes.hpp @@ -3,13 +3,19 @@ #define PERSISTENCE_ASSIGN_ATTRIBUTES_HPP_INCLUDED #include -#include +#include namespace persistence { - template - void assign_attributes(RecordPtr ptr, const wayward::Node& data) { - const IRecordType* record_type = get_type(); + namespace detail { + using wayward::AnyRef; + + void assign_attributes(AnyRef record, const IRecordType* record_type, const wayward::data_franca::Spectator& data); + } + template + void assign_attributes(RecordPtr ptr, const wayward::data_franca::Spectator& data) { + auto record_type = get_type(); + detail::assign_attributes(*ptr, record_type, data); } } diff --git a/persistence/association.hpp b/persistence/association.hpp index 5e79920..094ec78 100644 --- a/persistence/association.hpp +++ b/persistence/association.hpp @@ -5,106 +5,160 @@ #include #include +#include + namespace persistence { struct IRecordType; + struct Context; + struct IAssociationAnchor; - /// Descriptors for the type reflection system: + struct AssociationError : wayward::Error { + AssociationError(const std::string& msg) : wayward::Error(msg) {} + }; + // Descriptors for the type reflection system: struct IAssociation { virtual ~IAssociation() {} - virtual const IRecordType& self_type() const = 0; - virtual const IRecordType& foreign_type() const = 0; + virtual const IRecordType* self_type() const = 0; + virtual const IRecordType* foreign_type() const = 0; virtual std::string foreign_key() const = 0; + virtual std::string name() const = 0; + virtual IAssociationAnchor* get_anchor(AnyRef) const = 0; }; - template - struct IAssociationTo { - virtual ~IAssociationTo() {} + // Base class for things like BelongsTo<>, HasMany<>, etc. + struct IAssociationAnchor { + virtual ~IAssociationAnchor() {} + virtual void initialize(const IAssociation* association, Context* context) = 0; + virtual void load() = 0; + virtual bool is_loaded() const = 0; + virtual const IAssociation* association() const = 0; + virtual Context* context() const = 0; }; - template - struct ISingularAssociationTo : IAssociationTo { - virtual ~ISingularAssociationTo() {} + template + struct ISingularAssociationAnchor : IAssociationAnchor { + using AssociatedType = T; + virtual void populate(RecordPtr record) = 0; + virtual RecordPtr get() const = 0; }; - template - struct IPluralAssociationTo : IAssociationTo { - virtual ~IPluralAssociationTo() {} + template + struct IPluralAssociationAnchor : IAssociationAnchor { + using AssociatedType = T; + virtual void populate(std::vector> records) = 0; + virtual std::vector> get() = 0; }; - template - struct IAssociationFrom : IAssociation { - virtual ~IAssociationFrom() {} + template + struct AssociationAnchorBase : Base { + const IAssociation* association_; + Context* context_; - virtual void initialize_in_object(Owner& object) const = 0; - }; + const IAssociation* association() const final { return association_; } + Context* context() const final { return context_; } - struct ISingularAssociationField; - template - struct ISingularAssociationFrom : IAssociationFrom { - virtual ~ISingularAssociationFrom() {} + void initialize(const IAssociation* association, Context* context) override { + association_ = association; + context_ = context; + } + }; - virtual ISingularAssociationField* get_field(Owner& object) const = 0; - virtual const ISingularAssociationField* get_field(const Owner& object) const = 0; + template + struct SingularAssociationAnchor : AssociationAnchorBase> { }; - struct IPluralAssociationField; - template - struct IPluralAssociationFrom : IAssociationFrom { - virtual ~IPluralAssociationFrom() {} + template + struct PluralAssociationAnchor : AssociationAnchorBase> { + }; - virtual IPluralAssociationField* get_field(Owner& object) const = 0; - virtual const IPluralAssociationField* get_field(const Owner& object) const = 0; + template + struct IAssociationFrom : IAssociation { + virtual void initialize_in_object(Owner& object, Context* context) const = 0; + virtual IAssociationAnchor* get_anchor_known(Owner& object) const = 0; + virtual const IAssociationAnchor* get_anchor_known(const Owner& object) const = 0; + virtual wayward::data_franca::ReaderPtr get_member_reader(const Owner& object, wayward::Bitflags) const = 0; + virtual wayward::data_franca::AdapterPtr get_member_adapter(Owner& object, wayward::Bitflags) const = 0; }; - template - struct SingularAssociation : ISingularAssociationFrom, ISingularAssociationTo { - explicit SingularAssociation(std::string key) : key_(std::move(key)) {} - virtual ~SingularAssociation() {} + template + struct AssociationFrom : IAssociationFrom { + // XXX: For some reason, Clang doesn't see these, and complains that + // derived classes are abstract, unless they also get defined in AssociationBase. :() + const IRecordType* self_type() const { return get_type(); } + IAssociationAnchor* get_anchor(AnyRef record) const { + if (&record.type_info() != &get_type()->type_info()) { + throw wayward::TypeError{"Tried to get association with wrong object type."}; + } + return get_anchor_known(*record.get()); + } + }; - const IRecordType& self_type() const final { return *get_type(); } - const IRecordType& foreign_type() const final { return *get_type(); } - std::string foreign_key() const override { return key_; } - protected: - std::string key_; + template + struct AssociationBetween : IAssociationFrom { + using Owner = Owner_; + using AssociatedType = T_; + const IRecordType* foreign_type() const { return get_type(); } }; - template - struct PluralAssociation : IPluralAssociationFrom, IPluralAssociationTo { - explicit PluralAssociation(std::string key) : key_(std::move(key)) {} - virtual ~PluralAssociation() {} + template + struct AssociationBase : Base { + using Owner = typename Base::Owner; + using AssociatedType = typename Base::AssociatedType; + using MemberPtr = Anchor Owner::*; + AssociationBase(MemberPtr member, std::string name, std::string key) : member_(member), name_(std::move(name)), key_(std::move(key)) {} - const IRecordType& self_type() const final { return *get_type(); } - const IRecordType& foreign_type() const final { return *get_type(); } - std::string foreign_key() const override { return key_; } - protected: - std::string key_; - }; + Anchor* get(Owner& object) const { return &(object.*member_); } + const Anchor* get(const Owner& object) const { return &(object.*member_); } - /// Interface for struct members: + IAssociationAnchor* get_anchor_known(Owner& object) const final { return get(object); } + const IAssociationAnchor* get_anchor_known(const Owner& object) const final { return get(object); } - struct ISingularAssociationField { - virtual ~ISingularAssociationField() {} - virtual const IRecordType& foreign_type() const = 0; - }; + void initialize_in_object(Owner& object, Context* context) const override { + get(object)->initialize(this, context); + } - template - struct ISingularAssociationFieldTo : ISingularAssociationField { - virtual ~ISingularAssociationFieldTo() {} - virtual void populate(RecordPtr record) = 0; - }; + wayward::data_franca::ReaderPtr get_member_reader(const Owner& object, wayward::Bitflags options) const final { + return wayward::data_franca::make_reader(*get(object), options); + } + + wayward::data_franca::AdapterPtr get_member_adapter(Owner& object, wayward::Bitflags options) const final { + return wayward::data_franca::make_adapter(*get(object), options); + } + + MemberPtr member_ptr() const { return member_; } - struct IPluralAssociationField { - virtual ~IPluralAssociationField() {} - virtual const IRecordType& foreign_type() const = 0; + const IRecordType* foreign_type() const final { return get_type(); } + const IRecordType* self_type() const final { return get_type(); } + std::string name() const final { return name_; } + std::string foreign_key() const final { return key_; } + + // See line 86. + IAssociationAnchor* get_anchor(AnyRef record) const final { + if (&record.type_info() != &get_type()->type_info()) { + throw wayward::TypeError{"Tried to get association with wrong object type."}; + } + return get_anchor_known(*record.get()); + } + + protected: + std::string name_; + std::string key_; + MemberPtr member_; }; - template - struct IPluralAssociationFieldTo : IPluralAssociationField { - virtual ~IPluralAssociationFieldTo() {} + template + struct SingularAssociationBase : AssociationBase> { + using BaseClass = AssociationBase>; + using MemberPtr = typename BaseClass::MemberPtr; + SingularAssociationBase(MemberPtr member, std::string name, std::string fkey) : BaseClass(member, std::move(name), std::move(fkey)) {} + }; - // TODO: Use a better RecordCollection type, that doesn't copy the context lifetime sentinel for each record. - virtual void populate(std::vector> records) = 0; + template + struct PluralAssociationBase : AssociationBase> { + using BaseClass = AssociationBase>; + using MemberPtr = typename BaseClass::MemberPtr; + PluralAssociationBase(MemberPtr member, std::string name, std::string fkey) : BaseClass(member, std::move(name), std::move(fkey)) {} }; } diff --git a/persistence/ast.hpp b/persistence/ast.hpp index b86dd56..0778d3b 100644 --- a/persistence/ast.hpp +++ b/persistence/ast.hpp @@ -15,6 +15,8 @@ namespace persistence { using wayward::ICloneable; using wayward::Maybe; + template using Ptr = CloningPtr; + struct StarFrom; struct StringLiteral; struct NumericLiteral; @@ -104,6 +106,7 @@ namespace persistence { struct BooleanLiteral : Cloneable { bool value; + BooleanLiteral(bool b) : value(b) {} std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } }; @@ -133,10 +136,10 @@ namespace persistence { // function(arguments...) struct Aggregate : Cloneable { virtual ~Aggregate() {} - Aggregate(std::string function, std::vector> arguments) : function(std::move(function)), arguments(std::move(arguments)) {} + Aggregate(std::string function, std::vector> arguments) : function(std::move(function)), arguments(std::move(arguments)) {} std::string function; - std::vector> arguments; + std::vector> arguments; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } }; @@ -144,7 +147,7 @@ namespace persistence { // (elements...) struct List : Cloneable { virtual ~List() {} - std::vector> elements; + std::vector> elements; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } }; @@ -155,12 +158,12 @@ namespace persistence { // ELSE otherwise // END struct CaseSimple : Cloneable { - CloningPtr value; + Ptr value; struct When { - CloningPtr when; - CloningPtr then; + Ptr when; + Ptr then; }; - CloningPtr otherwise; + Ptr otherwise; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } }; @@ -177,10 +180,10 @@ namespace persistence { // END struct Case : Cloneable { struct When { - CloningPtr cond; - CloningPtr then; + Ptr cond; + Ptr then; }; - CloningPtr otherwise; + Ptr otherwise; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } }; @@ -196,7 +199,7 @@ namespace persistence { struct NotCondition : Cloneable { virtual ~NotCondition() {} - CloningPtr subcondition; + Ptr subcondition; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } }; @@ -214,7 +217,7 @@ namespace persistence { IsUnknown, IsNotUnknown, }; - CloningPtr value; + Ptr value; Cond op; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } @@ -238,10 +241,10 @@ namespace persistence { }; virtual ~BinaryCondition() {} - BinaryCondition(CloningPtr lhs, CloningPtr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {} + BinaryCondition(Ptr lhs, Ptr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {} - CloningPtr lhs; - CloningPtr rhs; + Ptr lhs; + Ptr rhs; Cond op; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } @@ -250,9 +253,9 @@ namespace persistence { // value BETWEEN lower_bound AND upper_bound struct BetweenCondition : Cloneable { virtual ~BetweenCondition() {} - CloningPtr value; - CloningPtr lower_bound; - CloningPtr upper_bound; + Ptr value; + Ptr lower_bound; + Ptr upper_bound; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } }; @@ -265,10 +268,10 @@ namespace persistence { }; virtual ~LogicalCondition() {} - LogicalCondition(CloningPtr lhs, CloningPtr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {} + LogicalCondition(Ptr lhs, Ptr rhs, Cond op) : lhs(std::move(lhs)), rhs(std::move(rhs)), op(op) {} - CloningPtr lhs; - CloningPtr rhs; + Ptr lhs; + Ptr rhs; Cond op; std::string to_sql(ISQLValueRenderer& visitor) const final { return visitor.render(*this); } @@ -291,24 +294,24 @@ namespace persistence { Type type; std::string relation; std::string alias; - CloningPtr on; + Ptr on; - Join(Type type, std::string relation, std::string alias, CloningPtr on) : type(type), relation(std::move(relation)), alias(std::move(alias)), on(std::move(on)) {} + Join(Type type, std::string relation, std::string alias, Ptr on) : type(type), relation(std::move(relation)), alias(std::move(alias)), on(std::move(on)) {} }; struct SelectAlias { - CloningPtr value; + Ptr value; wayward::Maybe alias; SelectAlias() {} - SelectAlias(CloningPtr val) : value(std::move(val)) {} - SelectAlias(CloningPtr val, std::string alias) : value(std::move(val)), alias(std::move(alias)) {} + SelectAlias(Ptr val) : value(std::move(val)) {} + SelectAlias(Ptr val, std::string alias) : value(std::move(val)), alias(std::move(alias)) {} SelectAlias(const SelectAlias&) = default; SelectAlias(SelectAlias&&) = default; }; struct Ordering { - CloningPtr value; + Ptr value; enum OrderingType { Ascending, Descending, @@ -316,8 +319,8 @@ namespace persistence { OrderingType ordering = Ascending; Ordering() {} - Ordering(CloningPtr val) : value(std::move(val)) {} - Ordering(CloningPtr val, OrderingType ordering) : value(std::move(val)), ordering(ordering) {} + Ordering(Ptr val) : value(std::move(val)) {} + Ordering(Ptr val, OrderingType ordering) : value(std::move(val)), ordering(ordering) {} Ordering(const Ordering&) = default; Ordering(Ordering&&) = default; }; @@ -329,9 +332,9 @@ namespace persistence { std::vector select; std::string relation; Maybe relation_alias; - CloningPtr where; - std::vector> joins; - std::vector> group; + Ptr where; + std::vector> joins; + std::vector> group; std::vector order; Maybe limit; @@ -344,10 +347,10 @@ namespace persistence { struct UpdateQuery : Cloneable, IQuery { virtual ~UpdateQuery() {} std::string relation; - CloningPtr where; + Ptr where; Maybe limit; std::vector columns; - std::vector> values; + std::vector> values; std::string to_sql(ISQLQueryRenderer& visitor) const final { return visitor.render(*this); } }; @@ -355,7 +358,7 @@ namespace persistence { struct DeleteQuery : Cloneable, IQuery { virtual ~DeleteQuery() {} std::string relation; - CloningPtr where; + Ptr where; Maybe limit; std::string to_sql(ISQLQueryRenderer& visitor) const final { return visitor.render(*this); } @@ -365,7 +368,8 @@ namespace persistence { virtual ~InsertQuery() {} std::string relation; std::vector columns; - std::vector> values; + std::vector> values; + std::vector returning_columns; std::string to_sql(ISQLQueryRenderer& visitor) const final { return visitor.render(*this); } }; diff --git a/persistence/belongs_to.hpp b/persistence/belongs_to.hpp index 62e9450..8a289c7 100644 --- a/persistence/belongs_to.hpp +++ b/persistence/belongs_to.hpp @@ -6,69 +6,202 @@ #include #include +#include + namespace persistence { - template - struct BelongsTo : ISingularAssociationFieldTo { - using Type = AssociatedType; - const ISingularAssociationTo* association_ = nullptr; - RecordPtr ptr_; - PrimaryKey id; + using wayward::Either; - bool operator==(const RecordPtr& rhs) const { return ptr_ == rhs; } - bool operator!=(const RecordPtr& rhs) const { return ptr_ != rhs; } + struct Context; - AssociatedType* operator->() const { return ptr_.get(); } // TODO: Populate on-demand + template + RecordPtr find(Context& ctx, PrimaryKey key); - // ISingularAssociationTo<> interface - void populate(RecordPtr ptr) final { - ptr_ = std::move(ptr); + template + const PrimaryKey* get_pk_for_record(const RecordPtr& record) { + auto pk = get_type()->primary_key(); + if (pk) { + auto pk_typed = dynamic_cast*>(pk); + if (pk_typed) { + auto& pk_id = pk_typed->get_known(*record); + if (pk_id.is_persisted()) { + return &pk_id; + } + } } - const IRecordType& foreign_type() const final { - return *get_type(); + return nullptr; + } + + template + struct BelongsTo : SingularAssociationAnchor { + using AssociatedType = T; + + BelongsTo() : value_(PrimaryKey{}) {} + Either> value_; + + bool operator==(const RecordPtr& rhs) const { return get() == rhs; } + bool operator!=(const RecordPtr& rhs) const { return !(*this == rhs); } + + BelongsTo& operator=(RecordPtr ptr) { + value_ = ptr; + return *this; } - }; - template - struct BelongsToAssociation : SingularAssociation { - using MemberPointer = BelongsTo O::*; - explicit BelongsToAssociation(std::string key, MemberPointer ptr) : SingularAssociation{std::move(key)}, ptr_(ptr) {} - MemberPointer ptr_; + BelongsTo& operator=(int64_t id) { + value_ = PrimaryKey{id}; + return *this; + } + + PrimaryKey id() const { + auto ptr = id_ptr(); + if (ptr) { + return *ptr; + } else { + return PrimaryKey{}; + } + } + + const PrimaryKey* id_ptr() const { + const PrimaryKey* ptr = nullptr; + + value_.template when([&](const PrimaryKey& key) { + if (key.is_persisted()) { + ptr = &key; + } + }); + + value_.template when>([&](const RecordPtr& referenced) { + if (referenced) { + ptr = get_pk_for_record(referenced); + } + }); + + return ptr; + } + + RecordPtr operator->() { + load(); + return get(); + } - void initialize_in_object(O& object) const final { - (object.*ptr_).association_ = this; + RecordPtr operator->() const { + return get(); } - ISingularAssociationField* get_field(O& object) const final { - return &(object.*ptr_); + bool is_set() const { + bool b = false; + value_.template when([&](const PrimaryKey& key) { + b = key.is_persisted(); + }); + value_.template when>([&](const RecordPtr& ptr) { + b = ptr != nullptr; + }); + return b; } - const ISingularAssociationField* get_field(const O& object) const final { - return &(object.*ptr_); + + /// IAssociationAnchor interface: + + bool is_loaded() const final { + return value_.template is_a>(); + } + + void load() final { + value_.template when([&](const PrimaryKey& key) { + if (key.is_persisted()) { + value_ = persistence::find(*this->context_, key); + } + }); + } + + + /// ISingularAssociationAnchor interface: + + void populate(RecordPtr ptr) final { + value_ = std::move(ptr); + } + + RecordPtr get() { + load(); + RecordPtr ptr; + value_.template when>([&](const RecordPtr& p) { + ptr = p; + }); + return std::move(ptr); + } + + RecordPtr get() const { + RecordPtr ptr; + value_.template when>([&](const RecordPtr& record) { + ptr = record; + }); + return std::move(ptr); } }; + template + struct BelongsToAssociation : SingularAssociationBase> { + using MemberPointer = BelongsTo O::*; + explicit BelongsToAssociation(MemberPointer ptr, std::string name, std::string fkey) : SingularAssociationBase>(ptr, std::move(name), std::move(fkey)) {} + }; + template struct ColumnAbilities>: LiteralEqualityAbilities {}; + struct IBelongsToType : IType { + virtual ~IBelongsToType() {} + }; + template - struct BelongsToType : IDataTypeFor> { + struct BelongsToType : wayward::DataTypeFor, IBelongsToType> { std::string name() const final { return wayward::format("BelongsTo<{0}>", get_type()->name()); } bool is_nullable() const final { return false; } bool has_value(const BelongsTo& value) const final { - return value.id.is_persisted(); + return value.id().is_persisted(); } - void extract_from_results(BelongsTo& value, const IResultSet& r, size_t row, const std::string& col) const final { - get_type()->extract_from_results(value.id, r, row, col); + void visit(BelongsTo& value, wayward::DataVisitor& visitor) const final { + if (visitor.can_modify() && !visitor.is_nil_at_current()) { + auto pk = value.id(); + get_type()->visit_data(pk, visitor); + value = pk; + } else { + auto ptr = value.id_ptr(); + if (ptr) { + get_type()->visit_data(*ptr, visitor); + } else { + visitor(Nothing); + } + } } }; template - const BelongsToType* build_type(const TypeIdentifier>*) { + const BelongsToType* build_type(const wayward::TypeIdentifier>*) { static const auto p = new BelongsToType; return p; } + + /* + We're specializing the Property class for BelongsTo associations, because + when seeing the association as a property (i.e., when dealing with it in SQL), + it should be a primary key, rather than the object data that data_franca adapters + perceive it to be. + */ + template + struct PropertyOf> : PropertyOfBase> { + using MemberPtr = typename PropertyOfBase>::MemberPtr; + PropertyOf(MemberPtr ptr, std::string col) : PropertyOfBase>(ptr, std::move(col)) {} + + Result get(AnyConstRef record) const override { + if (!record.is_a()) { + return detail::make_type_error_for_mismatching_record_type(get_type(), record.type_info()); + } + auto object = record.get(); + auto& assoc = this->get_known(*object); + return Any{assoc}; + } + }; } #endif // PERSISTENCE_BELONGS_TO_HPP_INCLUDED diff --git a/persistence/column.cpp b/persistence/column.cpp new file mode 100644 index 0000000..39037f0 --- /dev/null +++ b/persistence/column.cpp @@ -0,0 +1,7 @@ +#include "persistence/column.hpp" + +namespace persistence { + UnregisteredPropertyError::UnregisteredPropertyError(const std::string& type_name) + : wayward::Error{wayward::format("Attempted to use unregistered property on type {0}. Use property(member, column) in the PERSISTENCE block for the type to register the property.", type_name)} + {} +} diff --git a/persistence/column.hpp b/persistence/column.hpp new file mode 100644 index 0000000..05c17ac --- /dev/null +++ b/persistence/column.hpp @@ -0,0 +1,91 @@ +#pragma once +#ifndef PERSISTENCE_COLUMN_HPP_INCLUDED +#define PERSISTENCE_COLUMN_HPP_INCLUDED + +#include +#include + +#include +#include +#include +#include + +namespace persistence { + struct UnregisteredPropertyError : wayward::Error { + UnregisteredPropertyError(const std::string& type_name); + }; + + template + struct Column : ColumnAbilities, ColumnType> { + std::string column_name; + Maybe explicit_alias; + + Column(ColumnType Type::*member) { + const RecordType* t = get_type(); + Maybe column = t->find_column_by_member_pointer(member); + if (column) { + column_name = *column; + } else { + throw UnregisteredPropertyError(t->name()); + } + } + + Column(std::string relation_alias, ColumnType Type::*member) : explicit_alias(std::move(relation_alias)) { + const RecordType* t = get_type(); + Maybe column = t->find_column_by_member_pointer(member); + if (column) { + column_name = *column; + } else { + throw UnregisteredPropertyError(t->name()); + } + } + + Column(std::string column_name) : column_name(std::move(column_name)) {} + Column(std::string relation_alias, std::string column_name) : column_name(std::move(column_name)), explicit_alias(std::move(relation_alias)) {} + + relational_algebra::Value value() const& { + if (explicit_alias) { + return relational_algebra::column(*explicit_alias, column_name); + } else { + return relational_algebra::column(reinterpret_cast(get_type()), column_name); + } + } + + relational_algebra::Value value() && { + if (explicit_alias) { + return relational_algebra::column(std::move(*explicit_alias), std::move(column_name)); + } else { + return relational_algebra::column(reinterpret_cast(get_type()), std::move(column_name)); + } + } + }; + + template + Column column(ColumnType Type::*member) { + return Column(member); + } + template + Column column(std::string alias, ColumnType Type::*member) { + return Column(std::move(alias), member); + } + template + Column column(std::string column) { + return Column(std::move(column)); + } + template + Column column(std::string alias, std::string column) { + return Column(std::move(alias), std::move(column)); + } + + template + auto eq(ColumnType Type::*a, Other b) -> decltype(column(a) == b) { + return column(a) == b; + } + + template + auto eq(Other b, ColumnType Type::*a) -> decltype(column(a) == b) { + return column(a) == b; + } +} + +#endif // PERSISTENCE_COLUMN_HPP_INCLUDED diff --git a/persistence/connection.hpp b/persistence/connection.hpp index e44ad62..4873457 100644 --- a/persistence/connection.hpp +++ b/persistence/connection.hpp @@ -4,13 +4,22 @@ #include +#include + #include namespace wayward { struct ILogger; + struct AnyConstRef; + + namespace data_franca { + struct Spectator; + } } namespace persistence { + using wayward::AnyConstRef; + namespace ast { struct IQuery; } diff --git a/persistence/connection_pool.hpp b/persistence/connection_pool.hpp index f6dd7e2..b9ebcba 100644 --- a/persistence/connection_pool.hpp +++ b/persistence/connection_pool.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace persistence { struct IConnectionPool; @@ -31,6 +32,7 @@ namespace persistence { std::shared_ptr logger() const final { return connection_->logger(); } void set_logger(std::shared_ptr l) final { connection_->set_logger(std::move(l)); } private: + AcquiredConnection(const AcquiredConnection&) = delete; IConnectionPool* pool_ = nullptr; IConnection* connection_ = nullptr; void release(); diff --git a/persistence/context.hpp b/persistence/context.hpp index f277db4..2dbfb50 100644 --- a/persistence/context.hpp +++ b/persistence/context.hpp @@ -36,7 +36,7 @@ namespace persistence { pool = dynamic_cast*>(it->second.get()); } auto ptr = std::unique_ptr(new T); - get_type()->initialize_associations_in_object(ptr.get()); + get_type()->initialize_associations_in_object(ptr.get(), this); RecordPtr rptr { ptr.get(), sentinel_ }; pool->objects_.push_back(std::move(ptr)); return std::move(rptr); @@ -56,7 +56,7 @@ namespace persistence { LifetimeError(const std::string& msg) : wayward::Error(msg) {} }; - Context::~Context() { + inline Context::~Context() { clear(); // Clear first to delete any RecordPtrs in associations. auto count = sentinel_.use_count() - 1; if (count > 0) { diff --git a/persistence/create.hpp b/persistence/create.hpp new file mode 100644 index 0000000..6d22e46 --- /dev/null +++ b/persistence/create.hpp @@ -0,0 +1,18 @@ +#pragma once +#ifndef PERSISTENCE_CREATE_HPP_INCLUDED +#define PERSISTENCE_CREATE_HPP_INCLUDED + +#include +#include +#include + +namespace persistence { + template + RecordPtr create(Context& ctx, const wayward::data_franca::Spectator& data = wayward::data_franca::Spectator{}) { + auto record = ctx.create(); + assign_attributes(record, data); + return std::move(record); + } +} + +#endif // PERSISTENCE_CREATE_HPP_INCLUDED diff --git a/persistence/data_as_literal.cpp b/persistence/data_as_literal.cpp new file mode 100644 index 0000000..b2bfc9c --- /dev/null +++ b/persistence/data_as_literal.cpp @@ -0,0 +1,118 @@ +#include "persistence/data_as_literal.hpp" + +#include +#include + +namespace persistence { + using namespace ast; + using wayward::TypeError; + using wayward::DateTime; + + namespace { + template + ast::Ptr make_ast_literal(Args&&... args) { + return ast::Ptr{ new T{std::forward(args)...} }; + } + } + + struct DataAsLiteral::Visitor : wayward::DataVisitor { + DataAsLiteral& p; + Visitor(DataAsLiteral& owner) : p(owner) {} + + void visit_nil() { + p.result = make_ast_literal("NULL"); + } + + void visit_boolean(bool& value) { + p.result = make_ast_literal(value); + } + + void visit_int8(std::int8_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_int16(std::int16_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_int32(std::int32_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_int64(std::int64_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_uint8(std::uint8_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_uint16(std::uint16_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_uint32(std::uint32_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_uint64(std::uint64_t& value) { + p.result = make_ast_literal((double)value); + } + + void visit_float(float& value) { + p.result = make_ast_literal((double)value); + } + + void visit_double(double& value) { + p.result = make_ast_literal((double)value); + } + + void visit_string(std::string& value) { + p.result = make_ast_literal(value); + } + + void visit_key_value(const std::string& key, AnyRef data, const IType* type) { + throw TypeError{"Cannot represent key-value data type as SQL literal."}; + } + + void visit_element(std::int64_t idx, AnyRef data, const IType* type) { + if (idx < 0) { + throw TypeError{"Negative list element index has no meaning in SQL."}; + } + auto q = dynamic_cast(p.result.get()); + if (!q) { + q = new List; + p.result = ast::Ptr{ q }; + } + q->elements.reserve(idx); + if (q->elements.size() <= idx) { + q->elements.resize(idx+1); + } + DataAsLiteral sub; + q->elements[idx] = sub.make_literal(data, type); + } + + void visit_special(AnyRef data, const IType* type) { + if (data.is_a()) { + auto& datetime = *data.get(); + p.result = make_ast_literal(datetime.iso8601()); + } else { + throw TypeError{wayward::format("Unsupported type in SQL literals: {0}", type->name())}; + } + } + + bool can_modify() const { + return false; + } + + bool is_nil_at_current() const { + return false; + } + }; + + ast::Ptr DataAsLiteral::make_literal(AnyRef data, const IType* type) { + Visitor visitor { *this }; + type->visit_data(data, visitor); + return std::move(result); + } +} diff --git a/persistence/data_as_literal.hpp b/persistence/data_as_literal.hpp new file mode 100644 index 0000000..f7e9b4e --- /dev/null +++ b/persistence/data_as_literal.hpp @@ -0,0 +1,28 @@ +#pragma once +#ifndef PERSISTENCE_DATA_AS_LITERAL_HPP_INCLUDED +#define PERSISTENCE_DATA_AS_LITERAL_HPP_INCLUDED + +#include +#include + +#include + +namespace persistence { + using wayward::AnyRef; + using wayward::IType; + + struct DataAsLiteral { + struct Visitor; + + template + ast::Ptr make_literal(T& data) { + return make_literal(data, wayward::get_type()); + } + + ast::Ptr make_literal(AnyRef, const IType*); + private: + ast::Ptr result; + }; +} + +#endif // PERSISTENCE_DATA_AS_LITERAL_HPP_INCLUDED diff --git a/persistence/datetime.cpp b/persistence/datetime.cpp index a9cf912..a33eba1 100644 --- a/persistence/datetime.cpp +++ b/persistence/datetime.cpp @@ -1,27 +1,51 @@ #include #include +#include + +#include +#include + +#include namespace persistence { using wayward::DateTime; - const DateTimeType* build_type(const TypeIdentifier*) { - static DateTimeType* t = new DateTimeType; - return t; - } - - void DateTimeType::extract_from_results(DateTime& value, const IResultSet& rs, size_t row, const std::string& col) const { - auto string = rs.get(row, col); - - // TODO: Handle time zones! - auto m = DateTime::strptime(string, "%Y-%m-%d %H:%M:%s"); - if (m) { - value = *m; - } - } - - namespace relational_algebra { - Value RepresentAsLiteral::literal(const DateTime& dt) { - return Value{make_cloning_ptr(new persistence::ast::StringLiteral{dt.iso8601()})}; - } - } + + // Result DateTimeType::deserialize_value(DateTime& value, const wayward::data_franca::ScalarSpectator& source) const { + // std::string string_rep; + // if (source >> string_rep) { + // // PostgreSQL timestamp with time zone looks like this: YYYY-mm-dd HH:MM:ss+ZZ + // // Unfortunately, POSIX strptime can't deal with the two-digit timezone at the end, so we tinker with the string + // // to get it into a parseable state. + // std::string local_time_string = string_rep.substr(0, 19); + // std::string timezone_string = string_rep.substr(string_rep.size() - 3); + // local_time_string += timezone_string; + // if (timezone_string.size() == 3) { + // local_time_string += "00"; + // } + + // auto m = DateTime::strptime(local_time_string, "%Y-%m-%d %T%z"); + + // if (m) { + // value = std::move(*m); + // return Nothing; + // } + // return wayward::make_error(wayward::format("Couldn't parse DateTime from string: '{0}'", string_rep)); + // } + // return wayward::make_error("ERROR: Couldn't parse DateTime (input not a string)."); + // } + + // Result DateTimeType::serialize_value(const DateTime& value, wayward::data_franca::ScalarMutator& target) const { + // std::stringstream ss { value.strftime("%Y-%m-%d %T%z") }; + // target << ss.str(); + // return Nothing; + // } + + // ast::Ptr DateTimeType::make_literal(AnyConstRef data) const { + // if (!data.is_a()) { + // throw TypeError("DateTimeType::make_literal called with a value that isn't a DateTime."); + // } + // auto& dt = *data.get(); + // return make_cloning_ptr(new persistence::ast::StringLiteral{dt.iso8601()}); + // } } diff --git a/persistence/datetime.hpp b/persistence/datetime.hpp index 2bbc691..31824c8 100644 --- a/persistence/datetime.hpp +++ b/persistence/datetime.hpp @@ -5,26 +5,9 @@ #include #include #include -#include +#include namespace persistence { - struct DateTimeType : IDataTypeFor { - std::string name() const final { return "DateTime"; } - bool is_nullable() const final { return false; } - void extract_from_results(wayward::DateTime&, const IResultSet&, size_t row, const std::string& col) const final; - bool has_value(const wayward::DateTime&) const final { return true; } - }; - - const DateTimeType* build_type(const TypeIdentifier*); - - // Enable use of values as SQL literals. - namespace relational_algebra { - template <> - struct RepresentAsLiteral { - static Value literal(const wayward::DateTime&); - }; - } - // Enable SQL comparison. template struct ColumnAbilities: LiteralOrderingAbilities {}; } diff --git a/persistence/destroy.hpp b/persistence/destroy.hpp new file mode 100644 index 0000000..b2479ea --- /dev/null +++ b/persistence/destroy.hpp @@ -0,0 +1,15 @@ +#pragma once +#ifndef PERSISTENCE_DESTROY_HPP_INCLUDED +#define PERSISTENCE_DESTROY_HPP_INCLUDED + +#include +#include + +namespace persistence { + template + bool destroy(Context& ctx, RecordPtr& ptr) { + return false; // TODO + } +} + +#endif // PERSISTENCE_DESTROY_HPP_INCLUDED diff --git a/persistence/has_many.hpp b/persistence/has_many.hpp index 426b0f4..f8080d4 100644 --- a/persistence/has_many.hpp +++ b/persistence/has_many.hpp @@ -3,38 +3,69 @@ #define PERSISTENCE_HAS_MANY_HPP_INCLUDED #include +#include namespace persistence { template - struct HasMany : IPluralAssociationFieldTo { - const IAssociationTo* association_ = nullptr; - std::vector> records_; - - const IRecordType& foreign_type() const final { - return *get_type(); - } + struct HasMany : PluralAssociationAnchor { + Maybe>> records_; + std::function&)> get_id_of_owner_; void populate(std::vector> records) final { records_ = std::move(records); } + + PrimaryKey id_of_owner() const { + return get_id_of_owner_(*this); + } + + Projection scope() const { + auto pk = id_of_owner(); + if (!pk.is_persisted()) { + throw AssociationError{"Cannot load HasMany association for an unsaved record."}; + } + return persistence::from(*this->context_).where(column(this->association()->foreign_key()) == pk); + } + + void load() final { + if (!records_) { + records_ = scope().all(); + } + } + + std::vector> get() final { + return records_ ? *records_ : std::vector>{}; + } + + bool is_loaded() const final { + return (bool)records_; + } }; template - struct HasManyAssociation : PluralAssociation { + struct HasManyAssociation : PluralAssociationBase> { using MemberPointer = HasMany O::*; - explicit HasManyAssociation(std::string key, MemberPointer ptr) : PluralAssociation{std::move(key)}, ptr_(ptr) {} - MemberPointer ptr_; + explicit HasManyAssociation(MemberPointer ptr, std::string name, std::string fkey) : PluralAssociationBase>{ptr, std::move(name), std::move(fkey)} {} - void initialize_in_object(O& object) const final { - (object.*ptr_).association_ = this; + size_t member_offset(O& object) const { + return (size_t)(((char*)&(object.*(this->member_))) - (char*)&object); } - IPluralAssociationField* get_field(O& object) const final { - return &(object.*ptr_); - } + void initialize_in_object(O& object, Context* context) const override { + PluralAssociationBase>::initialize_in_object(object, context); - const IPluralAssociationField* get_field(const O& object) const final { - return &(object.*ptr_); + // This is sinful (and undefined behaviour, and probably slow: + // The aim is to be able to go from pointer to HasMany to pointer to O. + size_t offset = member_offset(object); + this->get(object)->get_id_of_owner_ = [=](const HasMany& anchor) -> PrimaryKey { + const O* obj = reinterpret_cast(reinterpret_cast(&anchor) - offset); + auto t = get_type()->primary_key(); + auto p = dynamic_cast*>(t); + if (p == nullptr) { + throw AssociationError{"Owner of HasMany association has a primary key that isn't a PrimaryKey property."}; + } + return p->get_known(*obj); + }; } }; } diff --git a/persistence/has_one.hpp b/persistence/has_one.hpp index a04f728..49f5bad 100644 --- a/persistence/has_one.hpp +++ b/persistence/has_one.hpp @@ -6,19 +6,13 @@ namespace persistence { template - struct HasOne { - const IAssociationTo* association_ = nullptr; + struct HasOne : SingularAssociationAnchor { }; template - struct HasOneAssociation : SingularAssociation { + struct HasOneAssociation : SingularAssociationBase> { using MemberPointer = HasOne O::*; - explicit HasOneAssociation(std::string key, MemberPointer ptr) : SingularAssociation{std::move(key)}, ptr_(ptr) {} - MemberPointer ptr_; - - void initialize_in_object(O& object) const final { - (object.*ptr_).association_ = this; - } + explicit HasOneAssociation(std::string key, MemberPointer ptr) : SingularAssociationBase>{std::move(key), ptr} {} }; } diff --git a/persistence/insert.cpp b/persistence/insert.cpp new file mode 100644 index 0000000..9718864 --- /dev/null +++ b/persistence/insert.cpp @@ -0,0 +1,127 @@ +#include "persistence/insert.hpp" +#include "persistence/ast.hpp" +#include "persistence/connection_pool.hpp" +#include "persistence/record_type.hpp" +#include "persistence/record.hpp" +#include "persistence/primary_key.hpp" +#include "persistence/data_as_literal.hpp" + +#include + +namespace persistence { + namespace detail { + namespace monad = wayward::monad; + + using wayward::make_error; + using wayward::NothingType; + + Result + make_insert_query(AnyRef record, const IRecordType* record_type, bool set_created_at) { + //static_assert(!boost::is_copy_constructible>::value, "TUPLE IS is_copy_constructible!!!"); + + // Get the primary key. + auto pk = record_type->abstract_primary_key(); + + if (pk) { + Result existing_pk = pk->get(record); + if (existing_pk.good()) { + PrimaryKey& primary_key_value = *existing_pk.get().get(); + if (primary_key_value.is_persisted()) { + return make_error(wayward::format("Trying to insert record that already has a primary key (real type: {0}).", existing_pk.get().type_info().name())); + } + } + } + + // Set created_at if it exists. + if (set_created_at) { + auto created_at_property = record_type->find_abstract_property_by_column_name("created_at"); + if (created_at_property != nullptr) { + auto now = wayward::DateTime::now(); + created_at_property->set(record, now); + } + } + + // Build the INSERT query. + ast::InsertQuery query; + query.relation = record_type->relation(); + size_t num = record_type->num_properties(); + query.columns.reserve(num); + query.values.reserve(num); + if (pk) { + query.returning_columns.push_back(pk->column()); + } + + auto conn = current_connection_provider().acquire_connection_for_data_store(record_type->data_store()); + + for (size_t i = 0; i < record_type->num_properties(); ++i) { + auto p = record_type->abstract_property_at(i); + if (p == pk) continue; + query.columns.push_back(p->column()); + auto value = p->get(record); + if (value) { + DataAsLiteral data_as_literal; + query.values.push_back(data_as_literal.make_literal(value.get(), &p->type())); + } else { + return std::move(std::move(value).error()); + } + } + + return InsertQueryWithConnection{std::move(query), std::move(conn)}; + } + + Result> + execute_insert(const ast::InsertQuery& query, IConnection& conn, const IRecordType* record_type) { + conn.logger()->log(wayward::Severity::Debug, "p", wayward::format("Insert {0}", record_type->name())); + std::unique_ptr results; + try { + results = conn.execute(query); + } + catch (const wayward::Error& error) { + conn.logger()->log(wayward::Severity::Error, "p", wayward::format("Error executing SQL:\n{0}", error.what())); + } + + if (!results) { + return make_error("Backend did not return any results (meaning INSERT probably failed)."); + } + return std::move(results); + } + + Result + set_primary_key_from_results(AnyRef record, const IRecordType* record_type, const IResultSet& results) { + // Set the primary key of the record. + auto pk = record_type->abstract_primary_key(); + if (pk) { + auto id_as_string = results.get(0, pk->column()); + if (id_as_string) { + PrimaryKey pk_value; + std::stringstream ss { *id_as_string }; + ss >> pk_value.id; + pk->set(record, pk_value); + return Nothing; + } else { + return make_error("Backend did not return an ID for the primary key (meaning INSERT probably failed)."); + } + } + return Nothing; + } + + Result + insert(AnyRef record, const IRecordType* record_type, bool set_created_at) { + return monad::fmap( + detail::make_insert_query(record, record_type, set_created_at), + [&](InsertQueryWithConnection query_and_connection) { + return monad::fmap( + detail::execute_insert( + query_and_connection.query, + query_and_connection.conn, + record_type + ), + [&](const std::unique_ptr& results) { + return detail::set_primary_key_from_results(record, record_type, *results); + } + ); + } + ); + } + } +} diff --git a/persistence/insert.hpp b/persistence/insert.hpp new file mode 100644 index 0000000..df78853 --- /dev/null +++ b/persistence/insert.hpp @@ -0,0 +1,49 @@ +#pragma once +#ifndef PERSISTENCE_INSERT_HPP_INCLUDED +#define PERSISTENCE_INSERT_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace persistence { + using wayward::AnyRef; + using wayward::Result; + + struct IConnection; + struct AcquiredConnection; + struct IRecordType; + struct IResultSet; + namespace ast { + struct InsertQuery; + } + + namespace detail { + struct InsertQueryWithConnection { + ast::InsertQuery query; + AcquiredConnection conn; + }; + + Result + make_insert_query(AnyRef record, const IRecordType* record_type, bool set_created_at); + + Result> + execute_insert(const ast::InsertQuery& query, IConnection& connection, const IRecordType* record_type); + + Result + set_primary_key_from_results(AnyRef record, const IRecordType* record_type, const IResultSet& results); + + Result + insert(AnyRef record, const IRecordType* record_type, bool set_created_at); + } + + + template + Result insert(RecordPtr record, bool set_created_at = true) { + return detail::insert(*record, wayward::get_type(), set_created_at); + } +} + +#endif // PERSISTENCE_INSERT_HPP_INCLUDED diff --git a/persistence/p.cpp b/persistence/p.cpp index b124651..b2d66ea 100644 --- a/persistence/p.cpp +++ b/persistence/p.cpp @@ -1,2 +1,2 @@ #include "persistence/p.hpp" -#include +#include diff --git a/persistence/p.hpp b/persistence/p.hpp index 4807172..133eec1 100644 --- a/persistence/p.hpp +++ b/persistence/p.hpp @@ -2,13 +2,11 @@ #ifndef P_HPP_INCLUDED #define P_HPP_INCLUDED -#include #include #include #include #include #include -#include #include #include #include @@ -20,6 +18,9 @@ #include #include #include +#include +#include +#include #if !defined(PERSISTENCE_NO_SHORTHAND_NAMESPACE) namespace p = persistence; diff --git a/persistence/persistence_macro.hpp b/persistence/persistence_macro.hpp index 1e29ba8..d289ede 100644 --- a/persistence/persistence_macro.hpp +++ b/persistence/persistence_macro.hpp @@ -16,7 +16,7 @@ build_(); \ } \ }; \ - inline const ::persistence::RecordType* build_type(const ::persistence::TypeIdentifier* dummy) { \ + inline const ::persistence::RecordType* build_type(const ::wayward::TypeIdentifier* dummy) { \ RecordTypeBuilder_ ## TYPE builder; \ builder.type_ = new ::persistence::RecordType; \ builder.build_with_defaults_(); \ diff --git a/persistence/primary_key.cpp b/persistence/primary_key.cpp index 5d7465e..b9197b1 100644 --- a/persistence/primary_key.cpp +++ b/persistence/primary_key.cpp @@ -1,7 +1,8 @@ -#include +#include "persistence/primary_key.hpp" +#include "persistence/property.hpp" namespace persistence { - const PrimaryKeyType* build_type(const TypeIdentifier*) { + const PrimaryKeyType* build_type(const wayward::TypeIdentifier*) { static const PrimaryKeyType* p = new PrimaryKeyType; return p; } diff --git a/persistence/primary_key.hpp b/persistence/primary_key.hpp index e30703a..e2d02ed 100644 --- a/persistence/primary_key.hpp +++ b/persistence/primary_key.hpp @@ -3,10 +3,13 @@ #define PERSISTENCE_PRIMARY_KEY_HPP_INCLUDED #include -#include +#include #include #include +#include +#include + namespace persistence { using int64 = std::int64_t; @@ -18,11 +21,11 @@ namespace persistence { operator int64() const { return id; } bool is_persisted() const { return id > 0; } - private: + //private: int64 id = -1; }; - struct PrimaryKeyType : IDataTypeFor { + struct PrimaryKeyType : wayward::DataTypeFor { std::string name() const final { return "PrimaryKey"; } bool is_nullable() const final { return false; } @@ -30,19 +33,45 @@ namespace persistence { return value.is_persisted(); } - void extract_from_results(PrimaryKey& value, const IResultSet& r, size_t row, const std::string& col) const final { - std::stringstream ss; - ss.str(r.get(row, col)); - int64_t v; - ss >> v; - value = PrimaryKey{v}; + void visit(PrimaryKey& pk, wayward::DataVisitor& visitor) const final { + if (visitor.can_modify()) { + visitor(pk.id); + } else { + if (has_value(pk)) { + visitor(pk.id); + } else { + visitor.visit_nil(); + } + } } }; - const PrimaryKeyType* build_type(const TypeIdentifier*); + const PrimaryKeyType* build_type(const wayward::TypeIdentifier*); template struct ColumnAbilities : LiteralEqualityAbilities {}; } +namespace wayward { + namespace monad { + template <> struct Join { + using Type = persistence::PrimaryKey; + }; + template <> struct Join> { + using Type = persistence::PrimaryKey; + }; + + template <> + struct Bind { + template + static auto bind(persistence::PrimaryKey k, F f) -> typename Join()))>>::Type { + if (k.is_persisted()) { + return f(k.id); + } + return Nothing; + } + }; + } +} + #endif // PERSISTENCE_PRIMARY_KEY_HPP_INCLUDED diff --git a/persistence/projection.cpp b/persistence/projection.cpp new file mode 100644 index 0000000..d2db71f --- /dev/null +++ b/persistence/projection.cpp @@ -0,0 +1,418 @@ +#include "persistence/projection.hpp" +#include "persistence/data_store.hpp" +#include "persistence/connection_pool.hpp" + +#include + + +namespace persistence { + namespace detail { + struct ProjectionBase::Private + : wayward::Cloneable + { + CloningPtr base_projector; + + // The map of joins is alias=>projector, and it needs to be rebuilt every time we make a copy of this Projection. + // Ownership of the detail::RelationProjector pointer is held by the hierarchy of projectors. + // We keep track of these to find the proper detail::RelationProjector on which to add a join. + std::map join_map; + + // An unnamed relation is a relation that is joined upon another without an alias. + // From the AST perspective, these are what ast::SymbolicRelation refer to. + // We keep track of this because it would be annoyingly verbose to have to refer to the relation alias + // in queries for every referenced column -- it is the rare case that it's ambiguous after all. + std::map first_relations_; + + std::string relation_for_symbol(ast::SymbolicRelation relation) const final { + auto t = reinterpret_cast(relation); + auto it = first_relations_.find(t); + if (it != first_relations_.end()) { + return it->second; + } else { + throw relational_algebra::SymbolicRelationError(wayward::format("Could not find relation for symbol {0}. Internal consistency error.", relation)); + } + } + }; + + ProjectionBase::ProjectionBase(Context& ctx, CloningPtr base) : context_(ctx), private_(new Private) { + private_->join_map[base->relation_alias()] = base.get(); + auto real_table_name = base->record_type()->relation(); + projection_.query->relation = real_table_name; + if (real_table_name != base->relation_alias()) { + projection_.query->relation_alias = base->relation_alias(); + } + private_->first_relations_[base->record_type()] = base->relation_alias(); + private_->base_projector = std::move(base); + } + + ProjectionBase::ProjectionBase(const ProjectionBase& other) + : context_(other.context_) + , projection_(other.projection_) + , results_(nullptr) + , private_(other.private_) + { + // We were copied, so pointer values have changed. + rebuild_join_map(); + } + + ProjectionBase::ProjectionBase(ProjectionBase&& other) + : context_(other.context_) + , projection_(std::move(other.projection_)) + , results_(std::move(other.results_)) + , private_(std::move(other.private_)) + {} + + ProjectionBase::ProjectionBase(ProjectionBase&& other, relational_algebra::Projection new_projection) + : context_(other.context_) + , projection_(std::move(new_projection)) + , results_(nullptr) + , private_(std::move(other.private_)) + {} + + ProjectionBase::~ProjectionBase() + {} + + ProjectionBase& ProjectionBase::operator=(const ProjectionBase& other) { + projection_ = other.projection_; + results_ = nullptr; + private_ = other.private_; + // We were copied, so pointer values have changed. + rebuild_join_map(); + return *this; + } + + ProjectionBase& ProjectionBase::operator=(ProjectionBase&& other) { + projection_ = std::move(other.projection_); + results_ = std::move(other.results_); + private_ = std::move(other.private_); + return *this; + } + + const IRecordType* ProjectionBase::primary_type() const { + return private_->base_projector->record_type(); + } + + RelationProjector* ProjectionBase::primary_projector() const { + return private_->base_projector.get(); + } + + std::string ProjectionBase::alias_for(const IRecordType* type) const { + auto it = private_->first_relations_.find(type); + if (it != private_->first_relations_.end()) { + return it->second; + } + assert(false); // LOGIC ERROR! + } + + std::string ProjectionBase::to_sql() { + update_select_expressions(); + auto conn = current_connection_provider().acquire_connection_for_data_store(primary_type()->data_store()); + return conn.to_sql(*projection_.query, *private_); + } + + size_t ProjectionBase::count() { + if (results_) + return results_->height(); + + auto p_copy = projection_.select({ + {relational_algebra::aggregate("COUNT", relational_algebra::column(private_->base_projector->relation_alias(), private_->base_projector->record_type()->abstract_primary_key()->column())), + "count"} + }); + auto conn = current_connection_provider().acquire_connection_for_data_store(primary_type()->data_store()); + auto results = conn.execute(*p_copy.query, *private_); + uint64_t count = 0; + Maybe count_column = results->get(0, "count"); + std::stringstream ss(*count_column); + ss >> count; + return count; + } + + void ProjectionBase::rebuild_join_map() { + private_->join_map.clear(); + private_->base_projector->rebuild_join_map_recursively(private_->join_map); + } + + void ProjectionBase::execute_query() { + if (results_ == nullptr) { + update_select_expressions(); + auto conn = current_connection_provider().acquire_connection_for_data_store(primary_type()->data_store()); + //conn.logger()->log(wayward::Severity::Debug, "p", wayward::format("Load {0}", get_type()->name())); + results_ = conn.execute(*projection_.query, *private_); + } + } + + void ProjectionBase::update_select_expressions() { + std::vector selects; + private_->base_projector->append_selects(selects); + projection_ = std::move(projection_).select(std::move(selects)); + } + + + void ProjectionBase::build_join(std::string from_alias, const IRecordType* from_type, const IAssociation& assoc, CloningPtr projector, ast::Join::Type type) { + // Look up where to hook up the join, and perform some sanity checks on the way: + auto it = private_->join_map.find(from_alias); + if (it == private_->join_map.end()) { + throw AssociationError(wayward::format("Unknown relation '{0}'.", from_alias)); + } + auto& source_projector = it->second; + + // Hook it up in the projector hierarchy: + auto target_type = projector->record_type(); + auto& to_alias = projector->relation_alias(); + + // Add it to the list of joins: + private_->join_map[to_alias] = projector.get(); + source_projector->add_join(assoc, std::move(projector)); + + // If this is the first join with this relation, update the first_relations list: + if (private_->first_relations_.find(target_type) == private_->first_relations_.end()) { + private_->first_relations_[target_type] = to_alias; + } + + // Prepare information for the JOIN condition: + std::string relation = target_type->relation(); + + // Build the condition with the relational algebra DSL: + auto lhs = relational_algebra::column(from_alias, assoc.foreign_key()); + auto rhs = relational_algebra::column(to_alias, target_type->abstract_primary_key()->column()); + auto cond = (std::move(lhs) == std::move(rhs)); + + switch (type) { + case ast::Join::Inner: projection_ = std::move(projection_).inner_join(std::move(relation), std::move(to_alias), std::move(cond)); break; + case ast::Join::LeftOuter: projection_ = std::move(projection_).left_join(std::move(relation), std::move(to_alias), std::move(cond)); break; + case ast::Join::RightOuter: projection_ = std::move(projection_).right_join(std::move(relation), std::move(to_alias), std::move(cond)); break; + default: assert(false); // NIY + } + } + + + + void throw_association_type_mismatch_error(const IRecordType* expected, const IRecordType* got) { + throw AssociationTypeMismatchError{wayward::format("Could not populate association expecting type {0} with object of type {1}.", expected->name(), got->name())}; + } + + RelationProjector::RelationProjector(std::string relation_alias, const IRecordType* type) + : record_type_(type), relation_alias_(std::move(relation_alias)) + { + // Build column aliases: + auto len = record_type_->num_properties(); + for (size_t i = 0; i < record_type_->num_properties(); ++i) { + auto prop = record_type_->abstract_property_at(i); + auto alias = wayward::format("{0}_{1}", relation_alias_, prop->column()); + column_aliases_[prop->column()] = std::move(alias); + } + } + + void RelationProjector::add_join(const IAssociation& association, CloningPtr other) { + sub_projectors_[&association] = std::move(other); + } + + void RelationProjector::rebuild_join_map_recursively(std::map& out_joins) { + out_joins[relation_alias_] = this; + for (auto& pair: sub_projectors_) { + pair.second->rebuild_join_map_recursively(out_joins); + } + } + + void RelationProjector::append_selects(std::vector& out_selects) const { + // Append our own columns: + for (auto& pair: column_aliases_) { + out_selects.emplace_back(relational_algebra::column(relation_alias_, pair.first), pair.second); + } + + // Append columns for all subprojectors: + for (auto& pair: sub_projectors_) { + pair.second->append_selects(out_selects); + } + } + + namespace { + using wayward::DataVisitor; + using wayward::DateTime; + + struct ColumnProjectionVisitor : DataVisitor { + const IResultSet& results; + size_t row; + const std::string& column_alias; + + ColumnProjectionVisitor(const IResultSet& results, size_t row, const std::string& column_alias) : results(results), row(row), column_alias(column_alias) {} + + void visit_nil() final {} + + void visit_boolean(bool& value) final { + auto v = results.get(row, column_alias); + value = v ? (*v == "t") : false; + } + + void visit_int8(std::int8_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_int16(std::int16_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_int32(std::int32_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_int64(std::int64_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_uint8(std::uint8_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_uint16(std::uint16_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_uint32(std::uint32_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_uint64(std::uint64_t& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_float(float& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_double(double& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + std::stringstream ss{*v}; + ss >> value; + } + + void visit_string(std::string& value) final { + auto v = results.get(row, column_alias); + if (!v) return; + value = *v; + } + + void visit_key_value(const std::string& key, AnyRef data, const IType* type) final { + throw TypeError{"Key-values cannot be decoded from SQL rows."}; + } + + void visit_element(std::int64_t idx, AnyRef data, const IType* type) final { + throw TypeError("Lists cannot be decoded from SQL rows."); + } + + void visit_special(AnyRef data, const IType* type) final { + if (data.is_a()) { + auto v = results.get(row, column_alias); + if (v) { + auto& value = *data.get(); + + std::string string_rep = std::move(*v); + // PostgreSQL timestamp with time zone looks like this: YYYY-mm-dd HH:MM:ss+ZZ + // Unfortunately, POSIX strptime can't deal with the two-digit timezone at the end, so we tinker with the string + // to get it into a parseable state. + std::string local_time_string = string_rep.substr(0, 19); + std::string timezone_string = string_rep.substr(string_rep.size() - 3); + local_time_string += timezone_string; + if (timezone_string.size() == 3) { + local_time_string += "00"; + } + + auto m = DateTime::strptime(local_time_string, "%Y-%m-%d %T%z"); + + if (m) { + value = std::move(*m); + return; + } + throw TypeError(wayward::format("Couldn't parse DateTime from string: '{0}'", string_rep)); + } + } + } + + bool can_modify() const final { + return true; + } + + bool is_nil_at_current() const final { + return results.is_null_at(row, column_alias); + } + }; + + struct RecordProjectionVisitor : DataVisitor { + const RelationProjector::ColumnAliases& aliases; + const IResultSet& results; + size_t row; + + RecordProjectionVisitor(const RelationProjector::ColumnAliases& aliases, const IResultSet& results, size_t row) : aliases(aliases), results(results), row(row) {} + + void unsupported() { throw TypeError{"Unsupported operation."}; } + + void visit_nil() final { unsupported(); } + void visit_boolean(bool&) final { unsupported(); } + void visit_int8(std::int8_t&) final { unsupported(); } + void visit_int16(std::int16_t&) final { unsupported(); } + void visit_int32(std::int32_t&) final { unsupported(); } + void visit_int64(std::int64_t&) final { unsupported(); } + void visit_uint8(std::uint8_t&) final { unsupported(); } + void visit_uint16(std::uint16_t&) final { unsupported(); } + void visit_uint32(std::uint32_t&) final { unsupported(); } + void visit_uint64(std::uint64_t&) final { unsupported(); } + void visit_float(float&) final { unsupported(); } + void visit_double(double&) final { unsupported(); } + void visit_string(std::string&) final { unsupported(); } + void visit_element(std::int64_t idx, AnyRef data, const IType* type) final { unsupported(); } + void visit_special(AnyRef data, const IType* type) final { unsupported(); } + bool can_modify() const final { return true; } + bool is_nil_at_current() const final { return false; } + + void visit_key_value(const std::string& key, AnyRef data, const IType* type) final { + auto it = aliases.find(key); + if (it == aliases.end()) { + return; + } + + // TODO: Get a backend-specific column converter! + ColumnProjectionVisitor visitor { results, row, it->second }; + type->visit_data(data, visitor); + } + }; + } + + void RelationProjector::populate_with_results(Context& ctx, AnyRef record_ref, const IResultSet& results, size_t row) { + RecordProjectionVisitor visitor { column_aliases_, results, row }; + record_type_->visit_data(record_ref, visitor); + + for (auto& pair: sub_projectors_) { + auto& anchor = *pair.first->get_anchor(record_ref); + pair.second->project_and_populate_association(ctx, anchor, results, row); + } + } + } +} diff --git a/persistence/projection.hpp b/persistence/projection.hpp index 01645b7..afd68f6 100644 --- a/persistence/projection.hpp +++ b/persistence/projection.hpp @@ -2,11 +2,12 @@ #ifndef PERSISTENCE_PROJECTION_HPP_INCLUDED #define PERSISTENCE_PROJECTION_HPP_INCLUDED +#include + #include +#include #include #include -#include -#include #include #include #include @@ -14,11 +15,12 @@ #include #include #include -#include #include #include #include +#include +#include #include #include @@ -31,245 +33,132 @@ namespace persistence { using wayward::Nothing; using wayward::CloningPtr; - struct UnregisteredPropertyError : wayward::Error { - UnregisteredPropertyError(std::string type_name) - : wayward::Error(wayward::format("Attempted to use unregistered property on type {0}. Use property(member, column) in the PERSISTENCE block for the type to register the property.", type_name)) - {} - }; - - struct AssociationError : wayward::Error { - AssociationError(std::string msg) : wayward::Error(std::move(msg)) {} + struct AssociationTypeMismatchError : wayward::Error { + AssociationTypeMismatchError(std::string msg) : Error(std::move(msg)) {} }; template struct Joins; template > struct Projection; - template - struct Column : ColumnAbilities, ColumnType> { - std::string column_name; - Maybe explicit_alias; - - Column(ColumnType Type::*member) { - const RecordType* t = get_type(); - Maybe column = t->find_column_by_member_pointer(member); - if (column) { - column_name = *column; - } else { - throw UnregisteredPropertyError(t->name()); - } - } + namespace detail { + struct RelationProjector { + using ColumnAliases = std::map; // original->alias - Column(std::string relation_alias, ColumnType Type::*member) : explicit_alias(std::move(relation_alias)) { - const RecordType* t = get_type(); - Maybe column = t->find_column_by_member_pointer(member); - if (column) { - column_name = *column; - } else { - throw UnregisteredPropertyError(t->name()); - } - } + RelationProjector(std::string relation_alias, const IRecordType* record_type); + const IRecordType* record_type() const { return record_type_; } + const std::string& relation_alias() const { return relation_alias_; } - Column(std::string column_name) : column_name(std::move(column_name)) {} - Column(std::string relation_alias, std::string column_name) : column_name(std::move(column_name)), explicit_alias(std::move(relation_alias)) {} + virtual RelationProjector* clone() const = 0; - relational_algebra::Value value() const& { - if (explicit_alias) { - return relational_algebra::column(*explicit_alias, column_name); - } else { - return relational_algebra::column(reinterpret_cast(get_type()), column_name); - } - } + void add_join(const IAssociation&, CloningPtr); + void rebuild_join_map_recursively(std::map& out_joins); + void append_selects(std::vector& out_selects) const; - relational_algebra::Value value() && { - if (explicit_alias) { - return relational_algebra::column(std::move(*explicit_alias), std::move(column_name)); - } else { - return relational_algebra::column(reinterpret_cast(get_type()), std::move(column_name)); - } - } - }; + void populate_with_results(Context&, AnyRef record_ref, const IResultSet&, size_t row); - template - Column column(ColumnType Type::*member) { - return Column(member); - } - template - Column column(std::string alias, ColumnType Type::*member) { - return Column(std::move(alias), member); - } - template - Column column(std::string column) { - return Column(std::move(column)); - } - template - Column column(std::string alias, std::string column) { - return Column(std::move(alias), std::move(column)); - } - - struct AssociationTypeMismatchError : wayward::Error { - AssociationTypeMismatchError(std::string msg) : Error(std::move(msg)) {} - }; - - struct RelationProjector : wayward::ICloneable { - RelationProjector(std::string relation_alias) : relation_alias_(std::move(relation_alias)) {} - virtual void project_and_populate_association(Context&, ISingularAssociationField&, const IResultSet& result_set, size_t row) = 0; + virtual void project_and_populate_association(Context&, IAssociationAnchor&, const IResultSet& result_set, size_t row) = 0; + private: + const IRecordType* record_type_; + std::string relation_alias_; + std::map> sub_projectors_; + ColumnAliases column_aliases_; + }; - virtual void append_selects(std::vector& out_selects) const = 0; - virtual void rebuild_joins(std::map& out_joins) = 0; + void throw_association_type_mismatch_error(const IRecordType* expected, const IRecordType* got); - const std::string& relation_alias() const { return relation_alias_; } - private: - std::string relation_alias_; - }; + template + struct RelationProjectorFor final : wayward::Cloneable, RelationProjector> { + RelationProjectorFor(std::string relation_alias) + : wayward::Cloneable, RelationProjector>(std::move(relation_alias), wayward::get_type()) + {} + RelationProjectorFor() : RelationProjectorFor(get_type()->relation()) {} - template - struct RelationProjectorFor : wayward::Cloneable, RelationProjector> { - std::map*, CloningPtr> sub_projectors_; - std::vector*, std::string>> column_aliases_; - - RelationProjectorFor(std::string relation_alias) : wayward::Cloneable, RelationProjector>(std::move(relation_alias)) - { - auto record_type = get_type(); - for (auto& property: record_type->properties_) { - auto alias = wayward::format("{0}_{1}", this->relation_alias(), property->column()); - column_aliases_.push_back(std::make_pair(property.get(), std::move(alias))); + RecordPtr project(Context& ctx, const IResultSet& result_set, size_t row) { + auto record = ctx.create(); + this->populate_with_results(ctx, *record, result_set, row); + return std::move(record); } - } - void rebuild_joins(std::map& out_joins) final { - out_joins[this->relation_alias()] = static_cast(this); - for (auto& pair: sub_projectors_) { - pair.second->rebuild_joins(out_joins); + void project_and_populate_association(Context& ctx, IAssociationAnchor& association, const IResultSet& result_set, size_t row) { + auto ptr = project(ctx, result_set, row); + auto typed_association = dynamic_cast*>(&association); + if (typed_association == nullptr) { + throw_association_type_mismatch_error(association.association()->foreign_type(), get_type()); + } + typed_association->populate(std::move(ptr)); } - } + }; - void append_selects(std::vector& out_selects) const final { - for (auto& pair: column_aliases_) { - out_selects.emplace_back(relational_algebra::column(this->relation_alias(), pair.first->column()), pair.second); - } - for (auto& pair: sub_projectors_) { - pair.second->append_selects(out_selects); - } - } + struct ProjectionBase { + ~ProjectionBase(); - RecordPtr project(Context& ctx, const IResultSet& result_set, size_t row) { - auto record = ctx.create(); + const IRecordType* primary_type() const; - // Populate fields - for (auto& pair: column_aliases_) { - auto property = pair.first; - auto& alias = pair.second; - property->set(*record, result_set, row, alias); - } + std::string to_sql(); + size_t count(); + protected: + ProjectionBase(const ProjectionBase&); + ProjectionBase(ProjectionBase&&); + ProjectionBase& operator=(const ProjectionBase&); + ProjectionBase& operator=(ProjectionBase&&); - // Populate child associations - for (auto& pair: sub_projectors_) { - ISingularAssociationField* real_association = pair.first->get_field(*record); - pair.second->project_and_populate_association(ctx, *real_association, result_set, row); - } + ProjectionBase(Context& ctx, CloningPtr base_projector); + ProjectionBase(ProjectionBase&& other, relational_algebra::Projection new_projection); - return std::move(record); - } + Context& context_; + relational_algebra::Projection projection_; + std::unique_ptr results_; - void project_and_populate_association(Context& ctx, ISingularAssociationField& association, const IResultSet& result_set, size_t row) { - auto ptr = project(ctx, result_set, row); - auto typed_association = dynamic_cast*>(&association); - if (typed_association == nullptr) { - throw AssociationTypeMismatchError(wayward::format("Could not populate association expecting type {0} with object of type {1}.", association.foreign_type().name(), get_type()->name())); - } - typed_association->populate(std::move(ptr)); - } - }; + struct Private; + CloningPtr private_; + + void update_select_expressions(); + void execute_query(); + void rebuild_join_map(); + void build_join(std::string from_alias, const IRecordType* from_type, const IAssociation& assoc, CloningPtr projector, ast::Join::Type type); + std::string alias_for(const IRecordType*) const; + RelationProjector* primary_projector() const; + }; + } template - struct Projection> { + struct Projection> : detail::ProjectionBase { // Utility typedefs: + using Base = detail::ProjectionBase; using Self = Projection>; template using SelfJoining = Projection>; using TypesInProjection = meta::TypeList; - // We need to define some constructors, because we're holding a unique_ptr: explicit Projection(Context& ctx) - : context_(ctx) - { - auto relation = get_type()->relation(); - q_.projector_ = make_cloning_ptr(new RelationProjectorFor(relation)); - p_.query->relation = relation; - q_.joins_[relation] = q_.projector_.get(); - q_.first_relations_[get_type()] = relation; - } + : Base(ctx, make_cloning_ptr(new detail::RelationProjectorFor())) + {} - Projection(Context& ctx, std::string t0_alias) - : context_(ctx) - { - q_.projector_ = make_cloning_ptr(new RelationProjectorFor(t0_alias)); - p_.query->relation = get_type()->relation(); - p_.query->relation_alias = t0_alias; - q_.joins_[t0_alias] = q_.projector_.get(); - q_.first_relations_[get_type()] = t0_alias; - } + Projection(Context& ctx, std::string primary_alias) + : Base(ctx, make_cloning_ptr(new detail::RelationProjectorFor(std::move(primary_alias)))) + {} Projection(const Self& other) - : context_(other.context_) - , materialized_(nullptr) - , p_(other.p_) - , q_(other.q_) - { - rebuild_joins(); - } - - Projection(Self&& other) - : context_(other.context_) - , materialized_(std::move(other.materialized_)) - , p_(std::move(other.p_)) - , q_(std::move(other.q_)) + : Base(other) {} Self& operator=(const Self& other) { - q_ = other.q_; - p_ = other.p_; - materialized_ = nullptr; // reset because the query has changed. - rebuild_joins(); + Base::operator=(other); return *this; } Self& operator=(Self&& other) { - materialized_ = std::move(other.materialized_); - p_ = std::move(other.p_); - q_ = std::move(other.q_); + Base::operator=(std::move(other)); return *this; } - // Common operations: - std::string to_sql() { - update_select_expressions(); - auto conn = current_connection_provider().acquire_connection_for_data_store(get_type()->data_store()); - return conn.to_sql(*p_.query, q_); - } - - size_t count() const { - if (materialized_) - return materialized_->height(); - - auto p_copy = p_.select({ - {relational_algebra::aggregate("COUNT", relational_algebra::column(q_.projector_->relation_alias(), get_type()->primary_key()->column())), - "count"} - }); - auto conn = current_connection_provider().acquire_connection_for_data_store(get_type()->data_store()); - auto results = conn.execute(*p_copy.query, q_); - uint64_t count = 0; - get_type()->extract_from_results(count, *results, 0, "count"); - return count; - } - // Type-unsafe operations that always compile, but don't give you any compile-time checks: - Self where(SQL sql) && { return replace_p(std::move(p_).where(std::move(sql))); } - Self order(SQL sql) && { return replace_p(std::move(p_).order(std::move(sql))); } - Self reverse_order() && { return replace_p(std::move(p_).reverse_order()); } - Self limit(size_t n) && { return replace_p(std::move(p_).limit(n)); } - Self offset(size_t n) && { return replace_p(std::move(p_).offset(n)); } + Self where(SQL sql) && { return replace_p(std::move(projection_).where(std::move(sql))); } + Self order(SQL sql) && { return replace_p(std::move(projection_).order(std::move(sql))); } + Self reverse_order() && { return replace_p(std::move(projection_).reverse_order()); } + Self limit(size_t n) && { return replace_p(std::move(projection_).limit(n)); } + Self offset(size_t n) && { return replace_p(std::move(projection_).offset(n)); } // Const versions that return a copy: Self where(SQL sql) const& { return copy().where(std::move(sql)); } @@ -279,9 +168,9 @@ namespace persistence { Self offset(size_t n) const& { return copy().offset(n); } // Joins: - Self inner_join(std::string relation, std::string as, relational_algebra::Condition on) && { return replace_p(std::move(p_).inner_join(std::move(relation), std::move(as), std::move(on))); } - Self left_join(std::string relation, std::string as, relational_algebra::Condition on) && { return replace_p(std::move(p_).left_join(std::move(relation), std::move(as), std::move(on))); } - Self right_join(std::string relation, std::string as, relational_algebra::Condition on) && { return replace_p(std::move(p_).right_join(std::move(relation), std::move(as), std::move(on))); } + Self inner_join(std::string relation, std::string as, relational_algebra::Condition on) && { return replace_p(std::move(projection_).inner_join(std::move(relation), std::move(as), std::move(on))); } + Self left_join(std::string relation, std::string as, relational_algebra::Condition on) && { return replace_p(std::move(projection_).left_join(std::move(relation), std::move(as), std::move(on))); } + Self right_join(std::string relation, std::string as, relational_algebra::Condition on) && { return replace_p(std::move(projection_).right_join(std::move(relation), std::move(as), std::move(on))); } Self inner_join(std::string relation, std::string as, relational_algebra::Condition on) const& { return copy().inner_join(std::move(relation), std::move(as), std::move(on)); } Self left_join(std::string relation, std::string as, relational_algebra::Condition on) const& { return copy().left_join(std::move(relation), std::move(as), std::move(on)); } Self right_join(std::string relation, std::string as, relational_algebra::Condition on) const& { return copy().right_join(std::move(relation), std::move(as), std::move(on)); } @@ -294,8 +183,8 @@ namespace persistence { }); } void each(std::function&)> callback) { - materialize(); - size_t num_rows = materialized_->height(); + execute_query(); + size_t num_rows = results_->height(); for (size_t i = 0; i < num_rows; ++i) { auto ptr = project(i); callback(ptr); @@ -304,8 +193,8 @@ namespace persistence { std::vector> all() { - materialize(); - size_t num_rows = materialized_->height(); + execute_query(); + size_t num_rows = results_->height(); std::vector> records; records.reserve(num_rows); for (size_t i = 0; i < num_rows; ++i) { @@ -330,13 +219,13 @@ namespace persistence { // Type-safe operations for tables representing T: // TODO: Do some type checking on Condition, to check that it only references tables mentioned in the TypeList. - Self where(relational_algebra::Condition cond) && { return replace_p(std::move(p_).where(std::move(cond))); } + Self where(relational_algebra::Condition cond) && { return replace_p(std::move(projection_).where(std::move(cond))); } Self where(relational_algebra::Condition cond) const& { return copy().where(std::move(cond)); } template Self order(Column col) && { static_assert(meta::Contains::Value, "The specified order column belongs to a type that isn't part of this projection."); - return replace_p(std::move(p_).order({col.value()})); + return replace_p(std::move(projection_).order({col.value()})); } template @@ -465,14 +354,8 @@ namespace persistence { // Replace-projection constructor, used by all rvalue member functions through replace_p. template Projection(Projection> && other, relational_algebra::Projection new_projection) - : context_(other.context_) - , materialized_(nullptr) // replace data because the query has changed. - , p_(std::move(new_projection)) - { - q_.projector_ = std::move(other.q_.projector_); - q_.joins_ = std::move(other.q_.joins_); - q_.first_relations_ = std::move(other.q_.first_relations_); - } + : Base(std::move(other), std::move(new_projection)) + {} Self replace_p(relational_algebra::Projection p) { return Self{std::move(*this), std::move(p)}; @@ -490,13 +373,7 @@ namespace persistence { template SelfJoining add_join(BelongsTo Owner::*assoc, std::string to_alias, ast::Join::Type type) { - auto it = q_.first_relations_.find(get_type()); - if (it != q_.first_relations_.end()) { - auto from_alias = it->second; - return add_join(std::move(from_alias), assoc, std::move(to_alias), type); - } else { - assert(false); // Consistency error: first_relations_ has not been properly updated. - } + return add_join(alias_for(get_type()), assoc, std::move(to_alias), type); } template @@ -508,108 +385,20 @@ namespace persistence { auto target_type = get_type(); auto association = source_type->find_singular_association_by_member_pointer(assoc); - // Look up where to hook up the join, and perform some sanity checks on the way: - auto it = q_.joins_.find(from_alias); - if (it == q_.joins_.end()) { - throw AssociationError(wayward::format("Unknown relation '{0}'.", from_alias)); - } - auto projector = it->second; - auto typed_projector = dynamic_cast*>(projector); - if (typed_projector == nullptr) { - throw AssociationError(wayward::format("The relation alias '{0}' does not describe objects of type '{1}'.", from_alias, source_type->name())); - } - - // Hook it up in the projector hierarchy: - auto new_projector = new RelationProjectorFor(to_alias); - typed_projector->sub_projectors_[association] = make_cloning_ptr(new_projector); - - // Add it to the list of joins: - q_.joins_[to_alias] = new_projector; - - // If this is the first join with this relation, update the first_relations list: - if (q_.first_relations_.find(target_type) == q_.first_relations_.end()) { - q_.first_relations_[target_type] = to_alias; - } - - // Prepare information for the JOIN condition: - std::string relation = target_type->relation(); - - // Build the condition with the relational algebra DSL: - auto lhs = relational_algebra::column(from_alias, association->foreign_key()); - auto rhs = relational_algebra::column(to_alias, target_type->primary_key()->column()); - auto cond = (std::move(lhs) == std::move(rhs)); - - // Combine it all to a join: - switch (type) { - case ast::Join::Inner: return SelfJoining(std::move(*this), std::move(p_).inner_join(std::move(relation), std::move(to_alias), std::move(cond))); - case ast::Join::LeftOuter: return SelfJoining(std::move(*this), std::move(p_).left_join(std::move(relation), std::move(to_alias), std::move(cond))); - case ast::Join::RightOuter: return SelfJoining(std::move(*this), std::move(p_).right_join(std::move(relation), std::move(to_alias), std::move(cond))); - default: assert(false); // NIY - } - } - - void rebuild_joins() { - // This is necessary because we're keeping raw pointers in q_.joins_, so when we're a freshly cloned Projection, - // we need to rebuild that lookup table. - - q_.joins_.clear(); - q_.projector_->rebuild_joins(q_.joins_); - } - - void materialize() { - if (materialized_ == nullptr) { - update_select_expressions(); - auto conn = current_connection_provider().acquire_connection_for_data_store(get_type()->data_store()); - //conn.logger()->log(wayward::Severity::Debug, "p", wayward::format("Load {0}", get_type()->name())); - materialized_ = conn.execute(*p_.query, q_); - } + auto new_projection = SelfJoining{std::move(*this), std::move(projection_)}; + auto new_projector = make_cloning_ptr(new detail::RelationProjectorFor(to_alias)); + new_projection.build_join(std::move(from_alias), source_type, *association, std::move(new_projector), type); + return std::move(new_projection); } RecordPtr project(size_t row) { - assert(materialized_ != nullptr); - return q_.projector_->project(context_, *materialized_, row); - } - - void update_select_expressions() { - std::vector selects; - q_.projector_->append_selects(selects); - p_ = std::move(p_).select(std::move(selects)); - } - - // Things that can't be copied: - Context& context_; - std::unique_ptr materialized_; - - // Things that can: - struct QueryInfo : relational_algebra::IResolveSymbolicRelation { - // The root projector. It is kept on the heap to avoid fixing up pointers every time - // we get moved around. - CloningPtr> projector_; - - // The map of joins is alias=>projector, and it needs to be rebuilt every time we make a copy of this Projection. - // Ownership of the RelationProjector pointer is held by the hierarchy of projectors. - // We keep track of these to find the proper RelationProjector on which to add a join. - std::map joins_; - - // An unnamed relation is a relation that is joined upon another without an alias. - // From the AST perspective, these are what ast::SymbolicRelation refer to. - // We keep track of this because it would be annoyingly verbose to have to refer to the relation alias - // in queries for every referenced column -- it is the rare case that it's ambiguous after all. - std::map first_relations_; - - std::string relation_for_symbol(ast::SymbolicRelation relation) const final { - auto t = reinterpret_cast(relation); - auto it = first_relations_.find(t); - if (it != first_relations_.end()) { - return it->second; - } else { - throw relational_algebra::SymbolicRelationError(wayward::format("Could not find relation for symbol {0}. Internal consistency error.", relation)); - } - } - }; + assert(results_ != nullptr); + // It's safe to static cast because we know what ProjectionBase looks like internally. + auto p = static_cast*>(primary_projector()); + return p->project(context_, *results_, row); + } - relational_algebra::Projection p_; - QueryInfo q_; + template friend struct Projection; }; template @@ -621,6 +410,11 @@ namespace persistence { Projection from(Context& ctx, std::string t0_alias) { return Projection(ctx, std::move(t0_alias)); } + + template + RecordPtr find(Context& ctx, PrimaryKey key) { + return from(ctx).where(column(&T::id) == key).first(); + } } #endif // PERSISTENCE_PROJECTION_HPP_INCLUDED diff --git a/persistence/projection_as_structured_data.hpp b/persistence/projection_as_structured_data.hpp index c900ab6..ead8588 100644 --- a/persistence/projection_as_structured_data.hpp +++ b/persistence/projection_as_structured_data.hpp @@ -2,56 +2,58 @@ #ifndef PERSISTENCE_PROJECTION_AS_STRUCTURED_DATA_HPP_INCLUDED #define PERSISTENCE_PROJECTION_AS_STRUCTURED_DATA_HPP_INCLUDED -#include +#include #include -namespace persistence { +namespace wayward { + namespace data_franca { + template + struct Adapter> : AdapterBase> { + using Proj = persistence::Projection; + + Adapter(Proj& proj, Bitflags options) : AdapterBase(proj, options) {} + + // IReader interface: + DataType type() const override { return DataType::List; } + Maybe get_boolean() const final { return Nothing; } + Maybe get_integer() const final { return Nothing; } + Maybe get_real() const final { return Nothing; } + Maybe get_string() const final { return Nothing; } + bool has_key(const String& key) const final { return false; } + ReaderPtr get(const String& key) const final { return nullptr; } + + size_t length() const final { + return this->ref_.count(); + } -} + ReaderPtr at(size_t idx) const final { + load(); + return make_reader(values_->at(idx), this->options_); + } -namespace wayward { - template - struct StructuredDataAdapter> : IStructuredData { - using Proj = persistence::Projection; - - explicit StructuredDataAdapter(Proj proj) : proj_(std::move(proj)) {} - - NodeType type() const final { - return NodeType::List; - } - - size_t length() const final { - load(); - return proj_.count(); - } - - std::vector keys() const final { - return std::vector{}; - } - - StructuredDataConstPtr get(const std::string& str) const final { - return nullptr; - } - - StructuredDataConstPtr get(size_t idx) const final { - load(); - return make_structured_data_adapter(values->at(idx)); - } - - Maybe get_string() const final { return Nothing; } - Maybe get_integer() const final { return Nothing; } - Maybe get_float() const final { return Nothing; } - Maybe get_boolean() const final { return Nothing; } - private: - mutable Proj proj_; - mutable Maybe>> values; - - void load() const { - if (!values) { - values = proj_.all(); + ReaderEnumeratorPtr enumerator() const final { + load(); + return make_reader(*values_, this->options_)->enumerator(); + } + + private: + mutable Maybe>> values_; + + void load() const { + if (!values_) { + values_ = this->ref_.all(); + } + } + }; + + template + struct GetAdapter> { + using Proj = persistence::Projection; + static AdapterPtr get(Proj& proj, Bitflags options) { + return std::static_pointer_cast(std::make_shared>(proj, options)); } - } - }; + }; + } } #endif // PERSISTENCE_PROJECTION_AS_STRUCTURED_DATA_HPP_INCLUDED diff --git a/persistence/property.cpp b/persistence/property.cpp new file mode 100644 index 0000000..b425612 --- /dev/null +++ b/persistence/property.cpp @@ -0,0 +1,17 @@ +#include "persistence/property.hpp" + +#include "wayward/support/format.hpp" + +namespace persistence { + namespace detail { + using wayward::make_error; + + wayward::ErrorPtr make_type_error_for_mismatching_record_type(const IType* expected_type, const TypeInfo& got_type) { + return make_error(wayward::format("Cannot access property of object of type '{0}'. This property belongs to objects of type '{1}'.", got_type.name(), expected_type->name())); + } + + wayward::ErrorPtr make_type_error_for_mismatching_value_type(const IType* record_type, const IType* expected_type, const TypeInfo& got_type) { + return make_error(wayward::format("Cannot assign property of type '{0}' with value of type '{1}'.", expected_type->name(), got_type.name())); + } + } +} diff --git a/persistence/property.hpp b/persistence/property.hpp index 5e24c92..e1e8889 100644 --- a/persistence/property.hpp +++ b/persistence/property.hpp @@ -3,16 +3,34 @@ #define PERSISTENCE_PROPERTY_HPP_INCLUDED #include -#include +#include #include +#include -#include +#include +#include +#include +#include +#include +#include namespace persistence { + using wayward::Result; + using wayward::Any; + using wayward::AnyRef; + using wayward::AnyConstRef; + using wayward::Nothing; + using wayward::IType; + using wayward::TypeInfo; + using wayward::get_type; + struct IProperty { virtual ~IProperty() {} virtual std::string column() const = 0; virtual const IType& type() const = 0; + + virtual Result get(AnyConstRef record) const = 0; + virtual Result set(AnyRef record, AnyConstRef value) const = 0; }; template @@ -23,41 +41,100 @@ namespace persistence { std::string column_; }; + namespace ast { + struct SingleValue; + } + template struct IPropertyOf : IProperty { virtual ~IPropertyOf() {} virtual bool has_value(const T& record) const = 0; - virtual void set(T& record, const IResultSet&, size_t row_num, const std::string& col_name) const = 0; - virtual std::shared_ptr - get_value_as_structured_data(const T& record) const = 0; + virtual wayward::data_franca::ReaderPtr + get_member_reader(const T&, wayward::Bitflags options) const = 0; + + virtual wayward::data_franca::AdapterPtr + get_member_adapter(T&, wayward::Bitflags options) const = 0; + + virtual void + visit(T&, wayward::DataVisitor& visitor) const = 0; + }; + + struct ASTError : wayward::Error { + ASTError(const std::string& msg) : wayward::Error(msg) {} + }; + + struct TypeError : wayward::Error { + TypeError(const std::string& msg) : wayward::Error(msg) {} }; + namespace detail { + wayward::ErrorPtr make_type_error_for_mismatching_record_type(const IType* expected_type, const TypeInfo& got_type); + wayward::ErrorPtr make_type_error_for_mismatching_value_type(const IType* record_type, const IType* expected_type, const TypeInfo& got_type); + } + template - struct PropertyOf : IPropertyOf, Property { + struct PropertyOfBase : IPropertyOf, Property { using MemberPtr = M T::*; MemberPtr ptr_; - explicit PropertyOf(MemberPtr ptr, std::string column) : Property{column}, ptr_(ptr) {} - const IType& type() const { return *get_type(); } + PropertyOfBase(MemberPtr ptr, std::string column) : Property{column}, ptr_(ptr) {} + const IType& type() const { return *wayward::get_type(); } std::string column() const { return this->column_; } + void visit(T& record, wayward::DataVisitor& visitor) const final { + visitor[this->column()](get_known(record)); + } + + Result get(AnyConstRef record) const override { + if (!record.is_a()) { + return detail::make_type_error_for_mismatching_record_type(get_type(), record.type_info()); + } + auto& mref = *record.get(); + return Any{ get_known(mref) }; + } + + Result set(AnyRef record, AnyConstRef value) const override { + if (!record.is_a()) { + return detail::make_type_error_for_mismatching_record_type(get_type(), record.type_info()); + } + if (!value.is_a()) { + return detail::make_type_error_for_mismatching_value_type(get_type(), get_type(), value.type_info()); + } + auto& mref = *record.get(); + auto vref = value.get(); + get_known(mref) = *vref; + return Nothing; + } + + const M& get_known(const T& record) const { + return record.*ptr_; + } + + M& get_known(T& record) const { + return record.*ptr_; + } + bool has_value(const T& record) const override { - auto& value = record.*ptr_; - return get_type()->has_value(value); + return get_type()->has_value(get_known(record)); } - void set(T& record, const IResultSet& results, size_t row_num, const std::string& col_name) const override { - M* value_ptr = &(record.*ptr_); - get_type()->extract_from_results(*value_ptr, results, row_num, col_name); - }; + wayward::data_franca::ReaderPtr + get_member_reader(const T& object, wayward::Bitflags options) const override { + return wayward::data_franca::make_reader(get_known(object), options); + } - std::shared_ptr - get_value_as_structured_data(const T& record) const override { - const M* value_ptr = &(record.*ptr_); - return wayward::make_structured_data_adapter(*value_ptr); + wayward::data_franca::AdapterPtr + get_member_adapter(T& object, wayward::Bitflags options) const override { + return wayward::data_franca::make_adapter(get_known(object), options); } }; + + template + struct PropertyOf : PropertyOfBase { + using MemberPtr = typename PropertyOfBase::MemberPtr; + PropertyOf(MemberPtr ptr, std::string col) : PropertyOfBase(ptr, std::move(col)) {} + }; } #endif // PERSISTENCE_PROPERTY_HPP_INCLUDED diff --git a/persistence/record.hpp b/persistence/record.hpp index a8172c6..428d13e 100644 --- a/persistence/record.hpp +++ b/persistence/record.hpp @@ -3,44 +3,62 @@ #define PERSISTENCE_RECORD_HPP_INCLUDED #include +#include +#include +#include +#include + +#include +#include +#include namespace persistence { + struct PrimaryKeyError : wayward::Error { + PrimaryKeyError(const std::string& msg) : wayward::Error(msg) {} + }; + + struct PersistError : wayward::Error { + PersistError(const std::string& msg) : wayward::Error(msg) {} + }; + + using wayward::make_error; + template - bool is_persisted(const T& record) { - auto pk_raw = get_type()->primary_key(); - auto pk = dynamic_cast*>(pk_raw); + bool is_persisted(const RecordPtr& record) { + auto pk = get_type()->primary_key(); if (pk == nullptr) { - // TODO: Should we warn about this here? - return false; + throw PrimaryKeyError{"This record class does not seem to have a primary key column defined."}; } - return pk->has_value(record); + return pk->has_value(*record); } template - bool is_new_record(const T& record) { + bool is_new_record(const RecordPtr& record) { return !is_persisted(record); } template - bool update(const T& record) { + Result update(const RecordPtr& record) { if (!is_persisted(record)) { - // We can't update a record with no primary key. - return false; + return make_error("Cannot UPDATE because the record is new and doesn't have a primary key."); } + return make_error("Update NIY"); } template - bool insert(T& record) { - // TODO: NIY - return false; + Result save(RecordPtr& record) { + if (is_persisted(record)) { + return update(record); + } else { + return insert(record); + } } template - bool save(T& record) { - if (is_persisted(record)) { - update(record); - } else { - insert(record); + void save_or_throw(RecordPtr& record) { + auto r = save(record); + if (!r) { + throw *r.error(); } } } diff --git a/persistence/record_as_structured_data.hpp b/persistence/record_as_structured_data.hpp index b738333..b8978b0 100644 --- a/persistence/record_as_structured_data.hpp +++ b/persistence/record_as_structured_data.hpp @@ -3,108 +3,302 @@ #define PERSISTENCE_RECORD_AS_STRUCTURED_DATA #include -#include -#include -#include +#include #include +#include namespace persistence { } namespace wayward { + namespace data_franca { + using ::persistence::get_type; + using ::persistence::RecordPtr; - /// Adapter: PrimaryKey - template - struct StructuredDataAdapter< - T, - typename std::enable_if::Type, ::persistence::PrimaryKey>::value>::type - > - : StructuredDataIntegerAdapter { - StructuredDataAdapter(::persistence::PrimaryKey key) : StructuredDataIntegerAdapter(key) {} - }; - - /// Adapter: DateTime - // TODO: Move this to a wayward-library, since DateTime is in Wayward Support. - template - struct StructuredDataAdapter< - T, - typename std::enable_if::Type, ::wayward::DateTime>::value>::type - > - : StructuredDataValue { - DateTime dt_; - StructuredDataAdapter(const DateTime& dt) : dt_(dt) {} - NodeType type() const override { return NodeType::String; } - Maybe get_string() const override { return dt_.iso8601(); } - }; - - /// Adapter: BelongsTo - template - struct StructuredDataAdapter<::persistence::BelongsTo> - : StructuredDataAdapter<::persistence::RecordPtr> { - StructuredDataAdapter(const ::persistence::BelongsTo& assoc) : StructuredDataAdapter<::persistence::RecordPtr>(assoc.ptr_) {} - }; - template - struct StructuredDataAdapter&> - : StructuredDataAdapter<::persistence::RecordPtr> { - StructuredDataAdapter(const ::persistence::BelongsTo& assoc) : StructuredDataAdapter<::persistence::RecordPtr>(assoc.ptr_) {} - }; - template - struct StructuredDataAdapter<::persistence::BelongsTo&> - : StructuredDataAdapter<::persistence::RecordPtr> { - StructuredDataAdapter(const ::persistence::BelongsTo& assoc) : StructuredDataAdapter<::persistence::RecordPtr>(assoc.ptr_) {} - }; - - /// Adapter: RecordPtr - template - struct StructuredDataAdapter<::persistence::RecordPtr> : IStructuredData { - ::persistence::RecordPtr ptr_; - - explicit StructuredDataAdapter(::persistence::RecordPtr ptr) : ptr_(std::move(ptr)) {} - - NodeType type() const final { - return ptr_ ? NodeType::Dictionary : NodeType::Nil; - } - - size_t length() const final { - return ::persistence::get_type()->num_properties(); - } - - std::vector keys() const final { - std::vector k; - auto t = ::persistence::get_type(); - size_t n = t->num_properties(); - k.reserve(n); - for (size_t i = 0; i < n; ++i) { - auto& property = t->property_at(i); - k.push_back(property.column()); - } - return k; - } - - StructuredDataConstPtr get(const std::string& str) const final { - if (ptr_ == nullptr) return nullptr; - - auto t = ::persistence::get_type(); - auto property = t->find_property_by_column_name(str); - if (property) { - auto property_of = dynamic_cast*>(property); - if (property_of) { - return property_of->get_value_as_structured_data(*ptr_); - } - } - return nullptr; - } - - StructuredDataConstPtr get(size_t idx) const final { - return nullptr; - } - - Maybe get_string() const final { return wayward::Nothing; } - Maybe get_integer() const final { return wayward::Nothing; } - Maybe get_float() const final { return wayward::Nothing; } - Maybe get_boolean() const final { return wayward::Nothing; } - }; + template + struct RecordAdapter : AdapterBase> { + RecordAdapter(RecordPtr& ref, Bitflags options) : AdapterBase>(ref, options) {} + + DataType type() const final { + return this->ref_ ? DataType::Dictionary : DataType::Nothing; + } + + bool has_property(const String& key) const { + return get_type()->find_property_by_column_name(key) != nullptr; + } + + bool has_association(const String& key) const { + return get_type()->find_association_by_name(key) != nullptr; + } + + bool has_key(const String& key) const final { + return has_property(key) || has_association(key); + } + + ReaderPtr get(const String& key) const final { + auto association = get_type()->find_association_by_name(key); + if (association) { + return association->get_member_reader(*this->ref_, this->options_); + } + + auto property = get_type()->find_property_by_column_name(key); + if (property) { + return property->get_member_reader(*this->ref_, this->options_); + } + return nullptr; + } + + size_t length() const final { + return get_type()->num_properties(); + } + + AdapterPtr reference_at_key(const String& key) final { + auto association = get_type()->find_association_by_name(key); + if (association) { + return association->get_member_adapter(*this->ref_, this->options_); + } + + auto property = get_type()->find_property_by_column_name(key); + if (property) { + return property->get_member_adapter(*this->ref_, this->options_); + } + return nullptr; + } + + bool set_nothing() final { + this->ref_ = nullptr; + return true; + } + + bool erase(const String& key) final { + auto ptr = reference_at_key(key); + if (ptr) { + return ptr->set_nothing(); + } + return false; + } + + struct PropertyEnumerator : Cloneable { + persistence::RecordPtr record; + const persistence::RecordType* t; + size_t i = 0; + Bitflags options_; + PropertyEnumerator(const PropertyEnumerator&) = default; + PropertyEnumerator(PropertyEnumerator&&) = default; + PropertyEnumerator(persistence::RecordPtr record, Bitflags o) : record(std::move(record)), t(::persistence::get_type()), options_(o) {} + + ReaderPtr current_value() const final { + auto prop = t->property_at(i); + return prop->get_member_reader(*record, options_); + } + + Maybe current_key() const final { + auto prop = t->property_at(i); + return prop->column(); + } + + bool at_end() const final { return i >= t->num_properties(); } + void move_next() final { + if (i < t->num_properties()) { + ++i; + } + } + }; + + ReaderEnumeratorPtr enumerator() const final { + return ReaderEnumeratorPtr{new PropertyEnumerator{this->ref_, this->options_}}; + } + }; + + template + struct Adapter> : RecordAdapter { + Adapter(RecordPtr& ref, Bitflags options) : RecordAdapter(ref, options) {} + }; + + template <> + struct Adapter : Adapter { + Adapter(persistence::PrimaryKey& key, Bitflags o = Options::None) : Adapter(key.id) {} + }; + + template <> + struct Adapter : AdapterBase { + Adapter(DateTime& ref, Bitflags = Options::None) : AdapterBase(ref, Options::None) {} + + // TODO: + DataType type() const final { return DataType::String; } + Maybe get_string() const final { return this->ref_.iso8601(); } + bool set_string(String str) final { + auto dt = DateTime::strptime("%Y-%m-%d %H:%M:%S %z", str); + if (dt) { + this->ref_ = *dt; + return true; + } + return false; + } + }; + + template + struct AssociationAdapter : AdapterBase { + AssociationAdapter(T& ref, Bitflags o) : AdapterBase(ref, o) {} + + bool can_load() const { + return this->options_ & Options::AllowLoad; + } + + bool is_loaded() const { + return this->ref_.is_loaded(); + } + + void load() const { + const_cast(this->ref_).load(); + } + }; + + template + struct SingularAssociationAdapter : AssociationAdapter { + SingularAssociationAdapter(T& ref, Bitflags o) : AssociationAdapter(ref, o) {} + + bool has_value() const { + return this->ref_.is_set(); + } + + DataType type() const override { + if (this->can_load() || this->is_loaded()) { + return DataType::Dictionary; + } else if (has_value()) { + return DataType::Integer; + } else { + return DataType::Nothing; + } + } + + Maybe get_integer() const override { + return monad::fmap(this->ref_.id(), [&](const persistence::PrimaryKey& key) { return key.id; }); + } + + bool has_key(const String& key) const override { + if (type() == DataType::Dictionary) { + auto t = this->ref_.association()->foreign_type(); + return t->find_abstract_property_by_column_name(key) != nullptr || t->find_abstract_association_by_name(key) != nullptr; + } + return false; + } + + size_t length() const override { + if (type() == DataType::Dictionary) { + return this->ref_.association()->foreign_type()->num_properties(); + } + return 0; + } + + ReaderPtr value_reader() const { + if (type() == DataType::Dictionary) { + if (this->can_load() && !this->is_loaded()) { + this->load(); + } + auto ptr = this->ref_.get(); + return make_owning_reader(std::move(ptr), this->options_); + } else if (has_value()) { + return make_owning_reader(this->ref_.id()); + } + return nullptr; + } + + AdapterPtr value_adapter() const { + if (type() == DataType::Dictionary) { + if (this->can_load() && !this->is_loaded()) { + this->load(); + } + auto ptr = this->ref_.get(); + return make_owning_adapter(std::move(ptr), this->options_); + } else if (has_value()) { + return make_owning_adapter(*this->ref_.id_ptr(), this->options_); + } + return nullptr; + } + + ReaderPtr get(const String& key) const override { + return value_reader()->get(key); + } + + ReaderEnumeratorPtr enumerator() const override { + return value_reader()->enumerator(); + } + + bool set_integer(Integer n) override { + this->ref_.value_ = persistence::PrimaryKey{n}; + return true; + } + + AdapterPtr reference_at_key(const String& key) override { + return value_adapter()->reference_at_key(key); + } + + bool erase(const String& key) override { + return value_adapter()->erase(key); + } + }; + + template + struct PluralAssociationAdapter : AssociationAdapter { + PluralAssociationAdapter(T& ref, Bitflags o) : AssociationAdapter(ref, o) {} + mutable Maybe reader_; + + DataType type() const { + if (this->can_load() || this->is_loaded()) { + return DataType::List; + } + return DataType::Nothing; + } + + ReaderPtr value_reader() const { + if (!reader_ && type() == DataType::List) { + if (this->can_load() && !this->is_loaded()) { + this->load(); + } + reader_ = make_owning_reader(this->ref_.get(), this->options_); + } + return *reader_; + } + + size_t length() const override { + return value_reader()->length(); + } + + ReaderPtr at(size_t idx) const override { + return value_reader()->at(idx); + } + + ReaderEnumeratorPtr enumerator() const override { + return value_reader()->enumerator(); + } + + AdapterPtr reference_at_index(size_t idx) override { + return nullptr; + } + }; + + template + struct Adapter> : SingularAssociationAdapter> { + Adapter(persistence::BelongsTo& ref, Bitflags o) : SingularAssociationAdapter>(ref, o) {} + }; + + template + struct Adapter> : SingularAssociationAdapter> { + Adapter(persistence::HasOne& ref, Bitflags o) : SingularAssociationAdapter>(ref, o) {} + }; + + template + struct Adapter> : PluralAssociationAdapter> { + Adapter(persistence::HasMany& ref, Bitflags o) : PluralAssociationAdapter>(ref, o) {} + }; + + // template + // struct Adapter> : Adapter> { + // Adapter(persistence::HasOne& ref) : Adapter>(ref.ptr_) {} + // }; + } } #endif // PERSISTENCE_RECORD_AS_STRUCTURED_DATA diff --git a/persistence/record_type.hpp b/persistence/record_type.hpp index d7b60bf..e8ef929 100644 --- a/persistence/record_type.hpp +++ b/persistence/record_type.hpp @@ -2,7 +2,8 @@ #ifndef PERSISTENCE_RECORD_TYPE_INCLUDED #define PERSISTENCE_RECORD_TYPE_INCLUDED -#include +#include + #include #include #include @@ -11,23 +12,26 @@ #include namespace persistence { + using wayward::IType; + struct IRecordType : IType { virtual std::string relation() const = 0; virtual std::string data_store() const = 0; - virtual void initialize_associations_in_object(void*) const = 0; + virtual void initialize_associations_in_object(void*, Context*) const = 0; - virtual const IProperty* find_property_by_column_name(const std::string& name) const = 0; + virtual const IProperty* find_abstract_property_by_column_name(const std::string& name) const = 0; + virtual const IAssociation* find_abstract_association_by_name(const std::string& name) const = 0; - virtual const IProperty* primary_key() const = 0; + virtual const IProperty* abstract_primary_key() const = 0; virtual size_t num_properties() const = 0; - virtual const IProperty& property_at(size_t idx) const = 0; - virtual size_t num_associations() const = 0; - virtual const IAssociation& association_at(size_t idx) const = 0; + virtual const IProperty* abstract_property_at(size_t idx) const = 0; + virtual const IAssociation* abstract_association_at(size_t idx) const = 0; }; - struct RecordTypeBase : IRecordType { + template + struct RecordTypeBase : Base { // IType std::string name() const final { return name_; } bool is_nullable() const final { return false; } @@ -42,29 +46,43 @@ namespace persistence { std::string data_store_ = "default"; }; - template - struct RecordType : RecordTypeBase { + template + struct RecordType : RecordTypeBase> { // TODO: Constructors, destructors, etc. std::vector>> properties_; std::vector>> associations_; const IPropertyOf* primary_key_ = nullptr; - void initialize_associations_in_object(void* obj) const final { + bool has_value(const RT&) const final { return true; } + + void visit(RT& value, wayward::DataVisitor& visitor) const final { + for (auto& prop: properties_) { + prop->visit(value, visitor); + } + } + + void initialize_associations_in_object(void* obj, Context* ctx) const final { RT& object = *reinterpret_cast(obj); for (auto& p: associations_) { - p->initialize_in_object(object); + p->initialize_in_object(object, ctx); } } - const IProperty* primary_key() const final { return primary_key_; } - + // IRecordType interface: + const IProperty* find_abstract_property_by_column_name(const std::string& name) const final { return find_property_by_column_name(name); } + const IAssociation* find_abstract_association_by_name(const std::string& name) const final { return find_association_by_name(name); } + const IProperty* abstract_primary_key() const final { return primary_key(); } size_t num_properties() const final { return properties_.size(); } - const IProperty& property_at(size_t idx) const final { return *properties_.at(idx); } - size_t num_associations() const final { return associations_.size(); } - const IAssociation& association_at(size_t idx) const final { return *associations_.at(idx); } + const IProperty* abstract_property_at(size_t idx) const final { return property_at(idx); } + const IAssociation* abstract_association_at(size_t idx) const final { return association_at(idx); } + + // RecordType interface: + const IPropertyOf* primary_key() const { return primary_key_; } + const IPropertyOf* property_at(size_t idx) const { return properties_.at(idx).get(); } + const IAssociationFrom* association_at(size_t idx) const { return associations_.at(idx).get(); } - const IProperty* find_property_by_column_name(const std::string& name) const { + const IPropertyOf* find_property_by_column_name(const std::string& name) const { for (auto& prop: properties_) { if (prop->column() == name) { return prop.get(); @@ -73,6 +91,15 @@ namespace persistence { return nullptr; } + const IAssociationFrom* find_association_by_name(const std::string& name) const { + for (auto& assoc: associations_) { + if (assoc->name() == name) { + return assoc.get(); + } + } + return nullptr; + } + template const PropertyOf* find_property_by_member_pointer(M RT::*member) const { @@ -84,11 +111,11 @@ namespace persistence { } template - const SingularAssociation* - find_singular_association_by_member_pointer(M RT::*member) const { + auto find_singular_association_by_member_pointer(M RT::*member) const -> const SingularAssociationBase* { + using Assoc = SingularAssociationBase; for (auto& assoc: associations_) { - auto p = dynamic_cast*>(assoc.get()); - if (p) return p; + auto p = dynamic_cast(assoc.get()); + if (p && p->member_ptr() == member) return p; } return nullptr; } @@ -106,8 +133,8 @@ namespace persistence { }; template - void initialize_associations(T& object) { - get_type()->initialize_associations_in_object(&object); + void initialize_associations(T& object, Context& ctx) { + get_type()->initialize_associations_in_object(&object, &ctx); } } diff --git a/persistence/record_type_builder.hpp b/persistence/record_type_builder.hpp index b34a949..4fa60cd 100644 --- a/persistence/record_type_builder.hpp +++ b/persistence/record_type_builder.hpp @@ -9,7 +9,12 @@ #include #include +#include + namespace persistence { + using wayward::Maybe; + using wayward::Nothing; + template struct RecordTypeBuilder { RecordType* type_; @@ -21,9 +26,16 @@ namespace persistence { template BelongsToAssociation& - belongs_to(BelongsTo RT::*member, std::string key_column) { - auto p = new BelongsToAssociation { key_column, member }; - auto prop = new PropertyOf> { member, std::move(key_column) }; + belongs_to(BelongsTo RT::*member, std::string name, Maybe foreign_key = Nothing) { + std::string fkey; + if (foreign_key) { + fkey = *foreign_key; + } else { + fkey = name + "_id"; + } + + auto p = new BelongsToAssociation { member, name, fkey }; + auto prop = new PropertyOf> { member, fkey }; type_->associations_.push_back(std::unique_ptr>(p)); type_->properties_.push_back(std::unique_ptr>(prop)); return *p; @@ -31,16 +43,16 @@ namespace persistence { template HasManyAssociation& - has_many(HasMany RT::*member, std::string foreign_key) { - auto p = new HasManyAssociation { std::move(foreign_key), member }; + has_many(HasMany RT::*member, std::string name, std::string foreign_key) { + auto p = new HasManyAssociation { member, name, foreign_key }; type_->associations_.push_back(std::unique_ptr>(p)); return *p; } template PropertyOf>& - has_one(HasOne RT::*member, std::string foreign_key) { - auto p = new HasOneAssociation { std::move(foreign_key), member }; + has_one(HasOne RT::*member, std::string name, std::string foreign_key) { + auto p = new HasOneAssociation { member, name, foreign_key }; type_->associations_.push_back(std::unique_ptr>(p)); return *p; } diff --git a/persistence/relational_algebra.cpp b/persistence/relational_algebra.cpp index dd59dd2..bc4a205 100644 --- a/persistence/relational_algebra.cpp +++ b/persistence/relational_algebra.cpp @@ -1,4 +1,5 @@ -#include +#include "persistence/relational_algebra.hpp" +#include "persistence/data_as_literal.hpp" namespace persistence { namespace relational_algebra { @@ -44,10 +45,9 @@ namespace persistence { }; } - Value literal(double n) { - return Value { - make_cloning_ptr(new ast::NumericLiteral{n}) - }; + Value literal(AnyRef data, const IType* type) { + DataAsLiteral data_as_literal; + return Value { data_as_literal.make_literal(data, type) }; } SQL sql(std::string sql) { diff --git a/persistence/relational_algebra.hpp b/persistence/relational_algebra.hpp index b9b58a9..7f31089 100644 --- a/persistence/relational_algebra.hpp +++ b/persistence/relational_algebra.hpp @@ -3,11 +3,14 @@ #define PERSISTENCE_RELATIONAL_ALGEBRA_HPP_INCLUDED #include +#include #include namespace persistence { namespace relational_algebra { using wayward::CloningPtr; + using wayward::AnyRef; + using wayward::IType; struct SQL { std::string sql; @@ -101,6 +104,7 @@ namespace persistence { explicit Projection(std::string relation); Projection(const Projection&) = default; Projection(Projection&&) = default; + Projection& operator=(const Projection&) = default; Projection& operator=(Projection&&) = default; Projection where(Condition) const&; @@ -129,39 +133,15 @@ namespace persistence { Value column(std::string relation, std::string column); Value column(ast::SymbolicRelation, std::string column); Value aggregate_impl(std::string func, Value* args, size_t num_args); - Value literal(std::string str); - Value literal(double number); + Value literal(AnyRef, const IType*); Condition negate(Condition&& cond); SQL sql(std::string sql); - template struct RepresentAsLiteral; template - struct RepresentAsLiteral::value && !std::is_same::value) - || std::is_floating_point::value - >::type> { - static Value literal(T number) { - return Value { - // TODO: Fix this casting to double. - make_cloning_ptr(new ast::NumericLiteral{(double)number}) - }; - } - }; - - template <> - struct RepresentAsLiteral { - static Value literal(std::string str) { - return Value { - make_cloning_ptr(new ast::StringLiteral{std::move(str)}) - }; - } - }; - - - template - Value literal(T lit) { - return RepresentAsLiteral::literal(std::forward(lit)); + Value literal(T&& lit) { + using Type = typename wayward::meta::RemoveConstRef::Type; + return literal(AnyRef{ lit }, wayward::get_type()); } template diff --git a/persistence/result_set.hpp b/persistence/result_set.hpp index 3336bc4..ef28da8 100644 --- a/persistence/result_set.hpp +++ b/persistence/result_set.hpp @@ -5,7 +5,11 @@ #include #include +#include + namespace persistence { + using wayward::Maybe; + struct IResultSet { virtual ~IResultSet() {} @@ -13,7 +17,7 @@ namespace persistence { virtual size_t height() const = 0; // Number of rows virtual std::vector columns() const = 0; virtual bool is_null_at(size_t idx, const std::string& col) const = 0; - virtual std::string get(size_t idx, const std::string& col) const = 0; + virtual Maybe get(size_t idx, const std::string& col) const = 0; }; } diff --git a/persistence/type.hpp b/persistence/type.hpp deleted file mode 100644 index 2a0b389..0000000 --- a/persistence/type.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef PERSISTENCE_TYPE_HPP_INCLUDED -#define PERSISTENCE_TYPE_HPP_INCLUDED - -#include - -namespace persistence { - struct IType { - virtual std::string name() const = 0; - virtual bool is_nullable() const = 0; - }; - - struct IResultSet; - - template - struct IDataTypeFor : IType { - virtual void extract_from_results(T& value, const IResultSet&, size_t row, const std::string& col_name) const = 0; - virtual bool has_value(const T& value) const = 0; - }; - - template - struct TypeIdentifier { TypeIdentifier() {} }; - - // Implement this: - // const IType* build_type(const TypeIdentifier* null); - - template - auto get_type() -> decltype(build_type(std::declval*>())) { - static const TypeIdentifier type_id; - static const auto t = build_type(&type_id); - return t; - } -} - -#endif // PERSISTENCE_TYPE_HPP_INCLUDED diff --git a/persistence/types.hpp b/persistence/types.hpp deleted file mode 100644 index bdcccd9..0000000 --- a/persistence/types.hpp +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once -#ifndef PERSISTENCE_TYPES_HPP_INCLUDED -#define PERSISTENCE_TYPES_HPP_INCLUDED - -#include -#include -#include - -#include -#include -#include - -namespace persistence { - struct StringType : IDataTypeFor { - bool is_nullable() const final { return false; } - std::string name() const final { return "std::string"; } - - bool has_value(const std::string& value) const final { - return true; - } - - void extract_from_results(std::string& value, const IResultSet& results, size_t row, const std::string& col) const final { - value = results.get(row, col); - } - }; - - const StringType* build_type(const TypeIdentifier*); - - template - struct NumericType : IDataTypeFor { - NumericType(std::string name) : name_(std::move(name)) {} - bool is_nullable() const final { return false; } - std::string name() const final { return name_; } - size_t bits() const { return sizeof(T)*8; } - bool is_signed() const { return std::is_signed::value; } - bool is_float() const { return std::is_floating_point::value; } - - bool has_value(const T& value) const final { return true; } - - void extract_from_results(T& value, const IResultSet& results, size_t row, const std::string& col) const final { - std::stringstream ss; - ss.str(results.get(row, col)); - ss >> value; // TODO: Check failbits. - } - private: - std::string name_; - }; - - const NumericType* build_type(const TypeIdentifier*); - const NumericType* build_type(const TypeIdentifier*); - const NumericType* build_type(const TypeIdentifier*); - const NumericType* build_type(const TypeIdentifier*); - const NumericType* build_type(const TypeIdentifier*); - const NumericType* build_type(const TypeIdentifier*); - - namespace detail { - std::string maybe_type_name(const IType* inner); - } - - template - struct MaybeType : IDataTypeFor> { - MaybeType(const IDataTypeFor* inner_type) : inner_type_(inner_type) {} - std::string name() const final { return detail::maybe_type_name(inner_type_); } - bool is_nullable() const { return true; } - - bool has_value(const wayward::Maybe& value) const final { - return static_cast(value); - } - - void extract_from_results(wayward::Maybe& value, const IResultSet& results, size_t row, const std::string& col) const final { - if (results.is_null_at(row, col)) { - value = wayward::Nothing; - } else { - T tmp; - inner_type_->extract_from_results(tmp, results, row, col); - value = std::move(tmp); - } - } - private: - const IDataTypeFor* inner_type_ = nullptr; - }; - - template - const MaybeType* build_type(const TypeIdentifier>*) { - static const MaybeType* p = new MaybeType{get_type()}; - return p; - } -} - -#endif // PERSISTENCE_TYPES_HPP_INCLUDED diff --git a/persistence/validation_errors.hpp b/persistence/validation_errors.hpp new file mode 100644 index 0000000..6230ce7 --- /dev/null +++ b/persistence/validation_errors.hpp @@ -0,0 +1,23 @@ +#pragma once +#ifndef PERSISTENCE_VALIDATION_ERRORS_HPP_INCLUDED +#define PERSISTENCE_VALIDATION_ERRORS_HPP_INCLUDED + +#include + +namespace persistence { + struct ValidationErrors : wayward::Error { + ValidationErrors(const std::string& msg) : wayward::Error(msg) {} + // TODO + }; +} + +namespace wayward { + namespace data_franca { + template <> + struct Adapter : AdapterBase { + Adapter(persistence::ValidationErrors& ref, Bitflags opts) : AdapterBase(ref, opts) {} + }; + } +} + +#endif // PERSISTENCE_VALIDATION_ERRORS_HPP_INCLUDED diff --git a/tests/SConscript b/tests/SConscript index 4dcc1f6..ddc480c 100644 --- a/tests/SConscript +++ b/tests/SConscript @@ -1,7 +1,7 @@ import glob import os -Import('env', 'wayward_support', 'wayward', 'persistence', 'wayward_testing', 'WaywardProgram') +Import('env', 'wayward_support', 'wayward', 'persistence', 'wayward_testing', 'WaywardInternalProgram') env = env.Clone() env.Append(CPPPATH = Split('googletest/include googletest ..')) @@ -15,7 +15,7 @@ case_env.Append(LIBS = [test_main, gtest, wayward_testing]) test_targets = [] for f in glob.glob('wayward/support/*.cpp'): - target = WaywardProgram(case_env, f.replace('.cpp', '', 1), source = f, rpaths = Split('../../.. ../..')) + target = WaywardInternalProgram(case_env, f.replace('.cpp', '', 1), source = f, rpaths = Split('../../.. ../..')) #target = case_env.Program(f.replace('.cpp', '', 1), source = [f], LINKFLAGS = Split('-Wl,-rpath -Wl,@executable_path/../../../ -Wl,-rpath -Wl,@executable_path/../../')) test_targets.append(target) @@ -23,7 +23,7 @@ persistence_case_env = env.Clone() persistence_case_env.Append(LIBS = [test_main, gtest, persistence, wayward_testing]) for f in glob.glob('persistence/*.cpp'): - target = WaywardProgram(persistence_case_env, f.replace('.cpp', '', 1), source = f, rpaths = Split('../.. ..')) + target = WaywardInternalProgram(persistence_case_env, f.replace('.cpp', '', 1), source = f, rpaths = Split('../.. ..')) #target = persistence_case_env.Program(f.replace('.cpp', '', 1), source = [f], LINKFLAGS = Split('-Wl,-rpath -Wl,@executable_path/../../ -rpath @executable_path/../')) test_targets.append(target) diff --git a/tests/persistence/connection_mock.hpp b/tests/persistence/connection_mock.hpp index 999fd2a..c0708ad 100644 --- a/tests/persistence/connection_mock.hpp +++ b/tests/persistence/connection_mock.hpp @@ -3,7 +3,8 @@ #define PERSISTENCE_TEST_CONNECTION_MOCK_HPP_INCLUDED #include -#include +#include +#include #include diff --git a/tests/persistence/insert_test.cpp b/tests/persistence/insert_test.cpp new file mode 100644 index 0000000..9be9df8 --- /dev/null +++ b/tests/persistence/insert_test.cpp @@ -0,0 +1,63 @@ +#include + +#include +#include +#include +#include +#include + +#include "connection_mock.hpp" +#include "adapter_mock.hpp" + +namespace { + using persistence::PrimaryKey; + using persistence::Context; + using wayward::DateTime; + using wayward::Maybe; + using wayward::Nothing; + + using namespace persistence::test; + + struct Foo { + PrimaryKey id; + DateTime created_at; + Maybe updated_at; + int integer_value = -1; + std::string string_value; + Maybe nullable_string_value; + }; + + PERSISTENCE(Foo) { + property(&Foo::id, "id"); + property(&Foo::created_at, "created_at"); + property(&Foo::updated_at, "updated_at"); + property(&Foo::integer_value, "integer_value"); + property(&Foo::string_value, "string_value"); + property(&Foo::nullable_string_value, "nullable_string_value"); + } + + struct InsertionTest : ::testing::Test { + persistence::AdapterRegistrar adapter_registrar_ = "test"; + Context context; + + persistence::test::ResultSetMock& results() { + return *adapter_registrar_.adapter_.result_set_; + } + + void SetUp() override { + persistence::setup("test://test"); + } + }; + + TEST_F(InsertionTest, generates_sql_to_insert) { + Foo foo; + auto tuple = persistence::detail::make_insert_query(foo, persistence::get_type(), false); + if (!tuple.good()) { + throw *tuple.error(); + } + EXPECT_EQ(true, tuple.good()); + auto q = std::move(tuple.get().query); + auto sql = tuple.get().conn.to_sql(q); + EXPECT_EQ(0, sql.find("INSERT INTO foos (")); + } +} diff --git a/tests/persistence/projection_test.cpp b/tests/persistence/projection_test.cpp index 39c40cf..d924d03 100644 --- a/tests/persistence/projection_test.cpp +++ b/tests/persistence/projection_test.cpp @@ -3,8 +3,8 @@ #include #include #include -#include #include +#include #include "connection_mock.hpp" #include "adapter_mock.hpp" @@ -94,7 +94,7 @@ namespace { auto q = from(context); size_t counter = 0; q.each([&](const Foo& foo) { - EXPECT_EQ((bool)foo.nullable_string_value, (bool)results().rows_.at(counter).at(2)); + EXPECT_EQ((bool)results().rows_.at(counter).at(2), (bool)foo.nullable_string_value); ++counter; }); EXPECT_NE(0, counter); @@ -108,7 +108,7 @@ namespace { ss.str(*results().rows_.at(counter).at(3)); int32_t n; ss >> n; - EXPECT_EQ(foo.int32_value, n); + EXPECT_EQ(n, foo.int32_value); ++counter; }); EXPECT_NE(0, counter); @@ -122,7 +122,7 @@ namespace { ss.str(*results().rows_.at(counter).at(4)); double n; ss >> n; - EXPECT_EQ(foo.double_value, n); + EXPECT_EQ(n, foo.double_value); ++counter; }); EXPECT_NE(0, counter); @@ -151,14 +151,14 @@ namespace { property(&Article::id, "id"); property(&Article::title, "title"); property(&Article::text, "text"); - belongs_to(&Article::author, "author_id"); + belongs_to(&Article::author, "author"); } PERSISTENCE(User) { property(&User::id, "id"); property(&User::name, "name"); - has_many(&User::articles, "author_id"); - belongs_to(&User::supervisor, "supervisor_id"); + has_many(&User::articles, "articles", "author_id"); + belongs_to(&User::supervisor, "supervisor"); } namespace w = wayward; diff --git a/tests/persistence/result_set_mock.hpp b/tests/persistence/result_set_mock.hpp index 6b1e41e..4d736fa 100644 --- a/tests/persistence/result_set_mock.hpp +++ b/tests/persistence/result_set_mock.hpp @@ -17,7 +17,7 @@ namespace persistence { size_t height() const { return rows_.size(); } std::vector columns() const { return columns_; } bool is_null_at(size_t idx, const std::string& col) const; - std::string get(size_t idx, const std::string& col) const; + Maybe get(size_t idx, const std::string& col) const; std::vector columns_; std::vector>> rows_; @@ -40,13 +40,8 @@ namespace persistence { return !m; } - inline std::string ResultSetMock::get(size_t idx, const std::string& col) const { - auto m = value_at(idx, col); - if (m) { - return *m; - } else { - return ""; - } + inline Maybe ResultSetMock::get(size_t idx, const std::string& col) const { + return value_at(idx, col); } } } diff --git a/tests/wayward/support/any_test.cpp b/tests/wayward/support/any_test.cpp new file mode 100644 index 0000000..7cba533 --- /dev/null +++ b/tests/wayward/support/any_test.cpp @@ -0,0 +1,39 @@ +#include + +#include + +namespace { + using wayward::Any; + using wayward::AnyRef; + using wayward::AnyConstRef; + + TEST(Any, stores_integer) { + Any any { (int)123 }; + EXPECT_EQ(true, any.is_a()); + auto m = any.get(); + EXPECT_EQ(123, *m); + } + + TEST(Any, accesses_contents_by_reference) { + Any any { (int)123 }; + auto m = any.get(); + *m = 456; + EXPECT_EQ(456, *any.get()); + } + + TEST(AnyRef, stores_integer_ref) { + int n = 123; + AnyRef any { n }; + EXPECT_EQ(true, any.is_a()); + auto m = any.get(); + EXPECT_EQ(n, *m); + } + + TEST(AnyRef, accesses_contents_by_reference) { + int n = 123; + AnyRef any { n }; + auto m = any.get(); + *m = 456; + EXPECT_EQ(456, n); + } +} diff --git a/tests/wayward/support/benchmark_test.cpp b/tests/wayward/support/benchmark_test.cpp new file mode 100644 index 0000000..9c859b7 --- /dev/null +++ b/tests/wayward/support/benchmark_test.cpp @@ -0,0 +1,68 @@ +#include +#include + +namespace wayward { + void PrintTo(const wayward::DateTime& dt, std::ostream* os) { + *os << dt.iso8601(); + } + + template + void PrintTo(DateTimeDuration duration, std::ostream* os) { + *os << duration.repr_.count() << " " << GetTimeUnitName>::Value; + } +} + +namespace { + using wayward::DateTime; + using wayward::Benchmark; + using wayward::BenchmarkScope; + using namespace wayward::units; + + TEST(Benchmark, measures_kernel_time) { + Benchmark bm; + ::usleep(100); + auto t = bm.finish(); + EXPECT_GE(t, 100_us); + EXPECT_NE(t, 0_us); + } + + TEST(Benchmark, measures_userspace_time) { + Benchmark bm; + auto begin = DateTime::now(); + while (DateTime::now() - begin < 100_us) { + // Do nothing, but try to avoid having the loop optimized out. + ::usleep(0); + } + auto t = bm.finish(); + EXPECT_GE(t, 100_us); + } + + TEST(BenchmarkScope, creates_a_subscope) { + Benchmark bm; + ::usleep(50); + { + BenchmarkScope BS { bm, "Subscope" }; + ::usleep(50); + } + auto total = bm.finish(); + EXPECT_GE(total, 100_us); + auto scoped = bm.scopes()["Subscope"]; + EXPECT_GE(scoped, 50_us); + EXPECT_NE(scoped, 0_us); + } + + TEST(BenchmarkScope, accumulates_subscopes_in_depth) { + Benchmark bm; + { + BenchmarkScope scope1 { bm, "Subscope" }; + ::usleep(50); + { + BenchmarkScope scope2 { bm, "Subscope" }; + ::usleep(50); + } + } + auto total = bm.finish(); + EXPECT_EQ(1, bm.scopes().size()); + EXPECT_GE(bm.scopes()["Subscope"], 100_us); + } +} diff --git a/tests/wayward/support/data_franca_test.cpp b/tests/wayward/support/data_franca_test.cpp new file mode 100644 index 0000000..4639924 --- /dev/null +++ b/tests/wayward/support/data_franca_test.cpp @@ -0,0 +1,185 @@ +#include +#include + +namespace { + using namespace wayward::data_franca; + + TEST(Spectator, inspects_ints) { + int n = 123; + Spectator s { n }; + EXPECT_EQ(DataType::Integer, s.type()); + Integer m; + bool result = s >> m; + EXPECT_EQ(true, result); + EXPECT_EQ(123, m); + } + + TEST(Spectator, inspects_strings) { + std::string a = "Hello, World!"; + Spectator s { a }; + EXPECT_EQ(DataType::String, s.type()); + String m; + bool result = s >> m; + EXPECT_EQ(true, result); + EXPECT_EQ("Hello, World!", m); + } + + TEST(Spectator, inspects_vectors_of_ints) { + std::vector v {1, 2, 3, 4}; + Spectator s { v }; + EXPECT_EQ(DataType::List, s.type()); + Integer i = 1; + for (auto it: s) { + EXPECT_EQ(DataType::Integer, it.type()); + Integer n; + EXPECT_EQ(true, it >> n); + EXPECT_EQ(i, n); + ++i; + } + } + + TEST(Spectator, inspects_maps_of_ints) { + std::map m { {"a", 123}, {"b", 567} }; + Spectator s { m }; + EXPECT_EQ(DataType::Dictionary, s.type()); + std::string keys[] = {{"a"}, {"b"}}; + size_t i = 0; + for (auto it = s.begin(); it != s.end(); ++it) { + EXPECT_EQ(*it.key(), keys[i]); + auto ss = *it; + Integer n; + EXPECT_EQ(true, ss >> n); + EXPECT_EQ(n, m[*it.key()]); + ++i; + } + } + + TEST(Mutator, inspects_ints) { + int n = 123; + Mutator s { n }; + EXPECT_EQ(DataType::Integer, s.type()); + Integer m; + bool result = s >> m; + EXPECT_EQ(true, result); + EXPECT_EQ(123, m); + } + + TEST(Mutator, inspects_strings) { + std::string a = "Hello, World!"; + Mutator s { a }; + EXPECT_EQ(DataType::String, s.type()); + String m; + bool result = s >> m; + EXPECT_EQ(true, result); + EXPECT_EQ("Hello, World!", m); + } + + TEST(Mutator, inspects_vectors_of_ints) { + std::vector v {1, 2, 3, 4}; + Mutator s { v }; + EXPECT_EQ(DataType::List, s.type()); + Integer i = 1; + for (auto it: s) { + EXPECT_EQ(DataType::Integer, it.type()); + Integer n; + EXPECT_EQ(true, it >> n); + EXPECT_EQ(i, n); + ++i; + } + } + + TEST(Mutator, inspects_maps_of_ints) { + std::map m { {"a", 123}, {"b", 567} }; + Mutator s { m }; + EXPECT_EQ(DataType::Dictionary, s.type()); + std::string keys[] = {{"a"}, {"b"}}; + size_t i = 0; + for (auto it = s.begin(); it != s.end(); ++it) { + EXPECT_EQ(*it.key(), keys[i]); + auto ss = *it; + Integer n; + EXPECT_EQ(true, ss >> n); + EXPECT_EQ(n, m[*it.key()]); + ++i; + } + } + + TEST(Mutator, mutates_integer) { + int n = 123; + Mutator m { n }; + m << 456; + EXPECT_EQ(456, n); + } + + TEST(Mutator, mutates_string) { + std::string s = "Hello"; + Mutator m { s }; + m << "World"; + EXPECT_EQ("World", s); + } + + TEST(Mutator, mutates_vector_of_ints) { + std::vector v = { 1, 2, 3 }; + Mutator m { v }; + m.push_back(4); + EXPECT_EQ(4, v.size()); + EXPECT_EQ(4, v[3]); + } + + TEST(Mutator, mutates_dictionary_of_ints) { + std::map map; + map["a"] = 1; + map["b"] = 2; + Mutator m { map }; + m["a"] << 3; + EXPECT_EQ(3, map["a"]); + } + + + TEST(Object, builds_int) { + Object o { 123 }; + EXPECT_EQ(DataType::Integer, o.type()); + Integer m; + EXPECT_EQ(true, o >> m); + EXPECT_EQ(123, m); + } + + TEST(Object, builds_string) { + Object o { "Hello, World!" }; + EXPECT_EQ(DataType::String, o.type()); + String s; + EXPECT_EQ(true, o >> s); + EXPECT_EQ("Hello, World!", s); + } + + TEST(Object, builds_vector_of_ints) { + Object o; + int numbers[3] = {123, 456, 789}; + for (size_t i = 0; i < 3; ++i) { + o.push_back(numbers[i]); + } + EXPECT_EQ(DataType::List, o.type()); + EXPECT_EQ(3, o.length()); + for (size_t i = 0; i < 3; ++i) { + Integer n; + EXPECT_EQ(true, o[i] >> n); + EXPECT_EQ(numbers[i], n); + } + } + + TEST(Object, builds_dictionary_of_ints) { + Object o; + std::string keys[] = {"a", "b", "c"}; + int numbers[] = {1, 2, 3}; + for (size_t i = 0; i < 3; ++i) { + o[keys[i]] = numbers[i]; + } + EXPECT_EQ(DataType::Dictionary, o.type()); + EXPECT_EQ(3, o.length()); + for (size_t i = 0; i < 3; ++i) { + Integer n; + EXPECT_EQ(true, o[keys[i]] >> n); + EXPECT_EQ(numbers[i], n); + } + } +} diff --git a/tests/wayward/support/datetime_test.cpp b/tests/wayward/support/datetime_test.cpp index 6b51246..e3d359c 100644 --- a/tests/wayward/support/datetime_test.cpp +++ b/tests/wayward/support/datetime_test.cpp @@ -15,6 +15,7 @@ namespace wayward { namespace { using wayward::DateTime; + using wayward::Timezone; using wayward::DateTimeDuration; using wayward::DateTimeInterval; using wayward::Nanoseconds; @@ -35,21 +36,21 @@ namespace { TEST(DateTime, at_creates_a_date_at_nanosecond_precision) { auto date = DateTime::at(2012, 3, 21, 12, 21, 43, 100, 200, 300); - EXPECT_EQ(2012, date.year()); - EXPECT_EQ(3, date.month()); - EXPECT_EQ(21, date.day()); - EXPECT_EQ(12, date.hour()); - EXPECT_EQ(21, date.minute()); - EXPECT_EQ(43, date.second()); - EXPECT_EQ(100, date.millisecond()); - EXPECT_EQ(200, date.microsecond()); - EXPECT_EQ(300, date.nanosecond()); + EXPECT_EQ(2012_years, date.year()); + EXPECT_EQ(3_months, date.month()); + EXPECT_EQ(21_days, date.day()); + EXPECT_EQ(12_hours, date.hour()); + EXPECT_EQ(21_minutes, date.minute()); + EXPECT_EQ(43_seconds, date.second()); + EXPECT_EQ(100_milliseconds, date.millisecond()); + EXPECT_EQ(200_microseconds, date.microsecond()); + EXPECT_EQ(300_nanoseconds, date.nanosecond()); } TEST(DateTime, strftime_formats_iso8601) { auto date = DateTime::at(2012, 3, 21, 12, 21, 43); - auto formatted = date.strftime("%Y-%m-%d %H:%M:%S %Z"); - EXPECT_EQ("2012-03-21 12:21:43 UTC", formatted); + auto formatted = date.strftime("%Y-%m-%d %H:%M:%S"); + EXPECT_EQ("2012-03-21 12:21:43", formatted); } TEST(DateTime, adds_nanoseconds) { @@ -164,19 +165,19 @@ namespace { TEST(DateTime, treats_2000_as_leap_year) { auto date = DateTime::at(2000, 2, 29, 0, 0, 0); - EXPECT_EQ(date.month(), 2); + EXPECT_EQ(date.month(), 2_months); auto date2 = DateTime::at(2000, 2, 30, 0, 0, 0); - EXPECT_EQ(date2.month(), 3); + EXPECT_EQ(date2.month(), 3_months); } TEST(DateTime, treats_2001_as_common_year) { auto date = DateTime::at(2001, 2, 29, 0, 0, 0); - EXPECT_EQ(date.month(), 3); + EXPECT_EQ(date.month(), 3_months); } TEST(DateTime, treats_1980_as_leap_year) { auto date = DateTime::at(1980, 2, 29, 0, 0, 0); - EXPECT_EQ(date.month(), 2); + EXPECT_EQ(date.month(), 2_months); } TEST(DateTimeInterval, adds_seconds_and_minutes) { @@ -186,4 +187,22 @@ namespace { auto expected = 90_seconds; EXPECT_EQ(x, expected); } + + TEST(DateTime, DISABLED_strptime_regression_1) { + // This is disabled because strftime/strptime relate to the current time zone, but + // strptime without time zone seems to assume that the input is UTC, which makes it + // impossible to test with string comparison. + + std::string repr = "2014-06-15 12:07:00"; + auto m = DateTime::strptime(repr, "%Y-%m-%d %T"); + EXPECT_EQ(true, (bool)m); + EXPECT_EQ(2014_years, m->year()); + EXPECT_EQ(6_months, m->month()); + EXPECT_EQ(15_days, m->day()); + EXPECT_EQ(12_hours, m->hour()); + EXPECT_EQ(7_minutes, m->minute()); + EXPECT_EQ(0_seconds, m->second()); + printf("TIME WITH TZ: %s\n", m->strftime("%Y-%m-%d %T %Z").c_str()); + EXPECT_EQ(repr, m->strftime("%Y-%m-%d %T")); + } } diff --git a/tests/wayward/support/maybe_test.cpp b/tests/wayward/support/maybe_test.cpp new file mode 100644 index 0000000..c2bc636 --- /dev/null +++ b/tests/wayward/support/maybe_test.cpp @@ -0,0 +1,24 @@ +#include + +#include + +namespace { + using wayward::Maybe; + using wayward::NothingType; + using wayward::Nothing; + + TEST(Maybe, encapsulates_int) { + Maybe m = 123; + EXPECT_EQ(123, *m); + } + + TEST(Maybe, encapsulates_int_ref) { + int n = 123; + Maybe m { n }; + EXPECT_EQ(123, *m); + when_maybe(m, [&](int& x) { + x = 456; + }); + EXPECT_EQ(456, n); + } +} diff --git a/tests/wayward/support/mutable_node_test.cpp b/tests/wayward/support/mutable_node_test.cpp deleted file mode 100644 index a114f68..0000000 --- a/tests/wayward/support/mutable_node_test.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -namespace { - using wayward::MutableNode; - using wayward::NodeType; - - TEST(MutableNode, initializes_with_string_constant) { - MutableNode str = "Hello, World!"; - EXPECT_EQ(NodeType::String, str.type()); - } - - TEST(MutableNode, initializes_with_integer_constant) { - MutableNode n = 123; - EXPECT_EQ(NodeType::Integer, n.type()); - } - - TEST(MutableNode, initialized_with_boolean_constant) { - MutableNode b = true; - EXPECT_EQ(NodeType::Boolean, b.type()); - } - - TEST(MutableNode, sets_member_of_dictionary_to_integer_constant) { - MutableNode dict = MutableNode::dictionary(); - dict["hello"] = 123; - EXPECT_EQ(NodeType::Integer, dict["hello"].type()); - } - - TEST(MutableNode, sets_member_of_dictionary_to_other_node) { - MutableNode dict = MutableNode::dictionary(); - MutableNode n = 123; - dict["hello"] = n; - EXPECT_EQ(NodeType::Integer, dict["hello"].type()); - } - - TEST(MutableNode, appends_to_list) { - MutableNode list = MutableNode::list(); - list.push_back(123); - EXPECT_EQ(NodeType::Integer, list[(size_t)0].type()); - } -} diff --git a/tests/wayward/support/string_test.cpp b/tests/wayward/support/string_test.cpp new file mode 100644 index 0000000..33308f4 --- /dev/null +++ b/tests/wayward/support/string_test.cpp @@ -0,0 +1,150 @@ +#include +#include +#include + +namespace wayward { + void PrintTo(const wayward::String& str, std::ostream* os) { + *os << str; + } + + void PrintTo(const wayward::Char& ch, std::ostream* os) { + *os << "0x" << std::hex << ch.codepoint(); + } +} + +namespace { + using wayward::String; + using wayward::Char; + using wayward::trim; + + TEST(String, trim_returns_identity) { + std::string a = "aaa"; + EXPECT_EQ("aaa", trim(a)); + } + + TEST(String, trim_strips_leading_spaces) { + std::string a = " aa"; + EXPECT_EQ("aa", trim(a)); + } + + TEST(String, trim_strips_trailing_spaces) { + std::string a = "aa "; + EXPECT_EQ("aa", trim(a)); + } + + TEST(String, trim_does_not_remove_whitespace_from_the_middle) { + std::string a = " a a "; + EXPECT_EQ("a a", trim(a)); + } + + + TEST(String, recognizes_ascii_character) { + const char input[] = "A"; + bool ascii = wayward::utf8::is_7bit_ascii(input[0]); + EXPECT_EQ(true, ascii); + } + + TEST(String, recognizes_utf8_character) { + const char input[] = "Ø"; + bool ascii = wayward::utf8::is_7bit_ascii(input[0]); + EXPECT_EQ(false, ascii); + } + + TEST(String, finds_length_of_utf8_character) { + const char input[] = "Ø"; + size_t len = wayward::utf8::char_length(input, sizeof(input)); + EXPECT_EQ(2, len); + } + + TEST(String, counts_utf8_characters) { + String str { "æøå" }; + EXPECT_EQ(3, str.length()); + EXPECT_EQ(6, str.size()); + + String str2 { "abcæøåxyz" }; + EXPECT_EQ(9, str2.length()); + EXPECT_EQ(12, str2.size()); + } + + TEST(String, access_characters_by_index) { + String str { "æøå" }; + EXPECT_EQ(Char{"ø"}, str[1]); + EXPECT_NE(Char{"a"}, str[1]); + } + + TEST(String, iterates_over_chars) { + String str { "abcæøåxyz" }; + std::vector chars; + for (auto ch: str) { + chars.push_back(ch); + } + EXPECT_EQ(str[0], chars[0]); + EXPECT_EQ(str[1], chars[1]); + EXPECT_EQ(str[2], chars[2]); + EXPECT_EQ(str[3], chars[3]); + EXPECT_EQ(str[4], chars[4]); + EXPECT_EQ(str[5], chars[5]); + EXPECT_EQ(str[6], chars[6]); + EXPECT_EQ(str[7], chars[7]); + EXPECT_EQ(str[8], chars[8]); + } + + TEST(String, concatenates) { + String a { "Hello, " }; + String b { "World!" }; + EXPECT_EQ("Hello, World!", a + b); + } + + TEST(String, streams_to_output) { + std::stringstream ss; + ss << String{"abc"}; + EXPECT_EQ("abc", ss.str()); + } + + TEST(Char, encodes_NUL) { + EXPECT_EQ(Char{'\0'}.codepoint(), 0); + } + + TEST(Char, encodes_ASCII) { + EXPECT_EQ(Char{'\x42'}.codepoint(), 0x42); + } + + TEST(Char, encodes_2byte) { + Char ch {"ø"}; + EXPECT_EQ(ch.string(), String{"ø"}); + EXPECT_EQ(ch.codepoint(), 0xf8); + } + + TEST(Char, decodes_2byte) { + Char ch { 0xf8 }; + EXPECT_EQ("ø", ch.string()); + EXPECT_EQ(2, ch.string().size()); + EXPECT_EQ(1, ch.string().length()); + } + + TEST(Char, encodes_3byte) { + Char ch {"わ"}; + EXPECT_EQ(ch.string(), String{"わ"}); + EXPECT_EQ(ch.codepoint(), 0x308f); + } + + TEST(Char, decodes_3byte) { + Char ch { 0x20ac }; + EXPECT_EQ("€", ch.string()); + EXPECT_EQ(3, ch.string().size()); + EXPECT_EQ(1, ch.string().length()); + } + + TEST(Char, encodes_4byte) { + Char ch {"𐤀"}; + EXPECT_EQ(ch.string(), String{"𐤀"}); + EXPECT_EQ(ch.codepoint(), 0x10900); + } + + TEST(Char, decodes_4byte) { + Char ch { 0x10900 }; + EXPECT_EQ("𐤀", ch.string()); + EXPECT_EQ(4, ch.string().size()); + EXPECT_EQ(1, ch.string().length()); + } +} diff --git a/w_util/init.cpp b/w_util/init.cpp new file mode 100644 index 0000000..f593527 --- /dev/null +++ b/w_util/init.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace { + using namespace wayward; + + void report_error(std::string err) { + ConsoleStreamLogger::get()->log(Severity::Error, "new", err); + } + + int run_command(std::string cmd, bool log = true) { + if (log) { + ConsoleStreamLogger::get()->log(Severity::Information, "new", format("=> {0}", cmd)); + } + return ::system(cmd.c_str()); + } + + static bool path_exists(const std::string& path) { + struct stat s; + return ::stat(path.c_str(), &s) == 0; + } + + static bool path_is_directory(const std::string& path) { + struct stat st; + int r = ::stat(path.c_str(), &st); + return r == 0 && S_ISDIR(st.st_mode); + } + + + static const char app_template[] = + "#include \n" + "\n" + "int main(int argc, char** argv) {\n" + " w::App app { argc, argv };\n" + " \n" + " app.get(\"/\", [&](w::Request&) { return w::render_text(\"Hello, World!\"); });\n" + " \n" + " return app.run();\n" + "}\n" + ; + + static const char sconstruct_template[] = + "import os\n" + "\n" + "WAYWARD_PATH = '{0}/' # Final slash important, otherwise rpath doesn't work.\n" + "\n" + "SConscript(os.path.join(WAYWARD_PATH, \"SConstruct\"))\n" + "\n" + "Import('env', 'wayward', 'persistence', 'wayward_support')\n" + "\n" + "env.Append(CPPPATH = WAYWARD_PATH)\n" + "\n" + "blog = env.Program('{1}', ['{2}'], LIBS = [wayward, persistence, wayward_support], LINKFLAGS = ['-rpath', WAYWARD_PATH])\n" + ; +} + +namespace w_dev { + using namespace wayward; + + int init(int argc, char const* const* argv) { + bool verbose = false; + + CommandLineOptions cmd; + cmd.description("Print commands as they are executed."); + cmd.option("--verbose", "-v", [&]() { + verbose = true; + }); + cmd.usage(); + auto names = cmd.parse(argc, argv); + if (names.size() == 0) { + cmd.display_usage_and_exit(); + } + auto directory = names[0]; + auto path_components = split(directory, "/"); + auto appname = path_components.back(); + auto mainfile = format("{0}.cpp", appname); + auto buildfile = "SConstruct"; + auto w_dir = ".w"; + + ConsoleStreamLogger::get()->log(Severity::Information, "new", format("Creating app '{0}' in directory '{1}'...", appname, directory)); + + if (run_command(format("mkdir -p {0}", directory), verbose) != 0) { + report_error("mkdir failed, aborting."); + return 1; + } + + if (::chdir(directory.c_str()) != 0) { + report_error("cd failed, aborting."); + return 1; + } + + ConsoleStreamLogger::get()->log(Severity::Information, "new", "Initializing Git repository..."); + + if (run_command("git init .", verbose) != 0) { + report_error("git init failed, aborting."); + return 1; + } + + if (path_exists(w_dir)) { + ConsoleStreamLogger::get()->log(Severity::Information, "new", format("Submodule '{0}' already exists, skipping clone phase.", w_dir)); + } else { + ConsoleStreamLogger::get()->log(Severity::Information, "new", "Adding Wayward submodules..."); + + if (run_command(format("git submodule add https://github.com/simonask/w.git {0}", w_dir), verbose) != 0) { + report_error("git submodule add failed, aborting."); + return 1; + } + + if (run_command("git submodule update --init --recursive", verbose) != 0) { + report_error("git submodule update failed, aborting."); + return 1; + } + } + + if (path_exists(mainfile)) { + ConsoleStreamLogger::get()->log(Severity::Information, "new", format("Main file '{0}' already exists, skipping installation.", mainfile)); + } else { + ConsoleStreamLogger::get()->log(Severity::Information, "new", format("Installing '{0}'...", mainfile)); + + std::ofstream of { mainfile }; + of << std::string(app_template); + of.close(); + } + + if (path_exists(buildfile)) { + ConsoleStreamLogger::get()->log(Severity::Information, "new", format("Build file '{0}' already exists, skipping installation.", buildfile)); + } else { + ConsoleStreamLogger::get()->log(Severity::Information, "new", format("Installing '{0}'...", buildfile)); + + std::ofstream of { buildfile }; + of << format(sconstruct_template, w_dir, appname, mainfile); + of.close(); + } + + return 0; + } +} diff --git a/w_util/main.cpp b/w_util/main.cpp index 8b46150..637d6a0 100644 --- a/w_util/main.cpp +++ b/w_util/main.cpp @@ -30,8 +30,7 @@ int main(int argc, char const *argv[]) return w_dev::server(argc - 1, argv + 1); } else if (cmd == "init" || cmd == "new") { - // TODO! - //return w_dev::init(argc - 1, argv + 1); + return w_dev::init(argc - 1, argv + 1); w_dev::usage(argv[0]); } else if (cmd == "help" || cmd == "--help" || cmd == "-h") { diff --git a/wayward/app.cpp b/wayward/app.cpp index 6edbf9b..0733358 100644 --- a/wayward/app.cpp +++ b/wayward/app.cpp @@ -58,7 +58,7 @@ namespace wayward { handler.path = std::move(path); static const std::regex find_placeholder {"/:([\\w\\d]+)(/?)", std::regex::ECMAScript}; - static const std::string match_placeholder = "/(.+)"; + static const std::string match_placeholder = "/([^/.]+)"; std::stringstream rs; size_t group_counter = 1; regex_replace_stream(rs, handler.path, find_placeholder, [&](std::ostream& os, const MatchResults& match) { @@ -69,6 +69,11 @@ namespace wayward { } }); + // Trailing ".:format": + rs << "(\\.([\\w\\d]+))?"; + group_counter++; + handler.regex_group_names[group_counter++] = "format"; + handler.human_readable_regex = rs.str(); handler.regex = std::regex(handler.human_readable_regex); handler.handler = std::move(callback); @@ -151,7 +156,7 @@ namespace wayward { if (h) { try { if (app->config.log_requests) { - log::debug("w", wayward::format("Parameters: {0}", req.params.to_string())); + log::debug("w", wayward::format("Parameters: {0}", as_json(req.params, JSONMode::Compact))); } return h->handler(req); } diff --git a/wayward/content_type.cpp b/wayward/content_type.cpp new file mode 100644 index 0000000..ed5395b --- /dev/null +++ b/wayward/content_type.cpp @@ -0,0 +1,43 @@ +#include "wayward/content_type.hpp" + +#include + +namespace wayward { + constexpr char HTML::MimeType[]; + constexpr char JSON::MimeType[]; + constexpr char XML::MimeType[]; + + namespace { + struct ContentTypeRegistry { + std::map content_types; + }; + + ContentTypeRegistry& content_type_registry() { + static ContentTypeRegistry registry; + return registry; + } + + struct RegisterDefaultContentTypeExtensions { + RegisterDefaultContentTypeExtensions() { + register_content_type_extension("html"); + register_content_type_extension("htm"); + register_content_type_extension("xml"); + register_content_type_extension("json"); + } + }; + + static const RegisterDefaultContentTypeExtensions _g_register_default_content_type_extensions = RegisterDefaultContentTypeExtensions{}; + } + + void register_content_type_extension(std::string ext, std::string content_type) { + content_type_registry().content_types[std::move(ext)] = std::move(content_type); + } + + Maybe content_type_for_extension(std::string ext) { + auto it = content_type_registry().content_types.find(ext); + if (it != content_type_registry().content_types.end()) { + return it->second; + } + return Nothing; + } +} diff --git a/wayward/content_type.hpp b/wayward/content_type.hpp new file mode 100644 index 0000000..ec356dc --- /dev/null +++ b/wayward/content_type.hpp @@ -0,0 +1,29 @@ +#pragma once +#ifndef WAYWARD_CONTENT_TYPE_HPP_INCLUDED +#define WAYWARD_CONTENT_TYPE_HPP_INCLUDED + +#include + +namespace wayward { + struct HTML { + static const constexpr char MimeType[] = "text/html"; + }; + + struct JSON { + static const constexpr char MimeType[] = "application/json"; + }; + + struct XML { + static const constexpr char MimeType[] = "application/xml"; + }; + + Maybe content_type_for_extension(std::string ext); + void register_content_type_extension(std::string ext, std::string content_type); + + template + void register_content_type_extension(std::string ext) { + register_content_type_extension(std::move(ext), ContentType::MimeType); + } +} + +#endif // WAYWARD_CONTENT_TYPE_HPP_INCLUDED diff --git a/wayward/render.cpp b/wayward/render.cpp index 7f2f0b1..850eea0 100644 --- a/wayward/render.cpp +++ b/wayward/render.cpp @@ -32,7 +32,7 @@ namespace wayward { } } - Response render(const std::string& template_name, Dict params, HTTPStatusCode code) { + Response render(const std::string& template_name, Options params, HTTPStatusCode code) { auto engine = current_template_engine(); Response response; response.code = code; diff --git a/wayward/respond_to.cpp b/wayward/respond_to.cpp new file mode 100644 index 0000000..e7039af --- /dev/null +++ b/wayward/respond_to.cpp @@ -0,0 +1,75 @@ +#include "wayward/respond_to.hpp" +#include "wayward/support/string.hpp" + +namespace wayward { + namespace { + std::set get_content_types(const Request& request) { + std::set result; + std::string param_format; + if (request.params["format"] >> param_format) { + auto maybe_content_type = content_type_for_extension(param_format); + monad::fmap(std::move(maybe_content_type), [&](auto& content_type) { + return result = {std::move(content_type)}; + }); + } + + auto it = request.headers.find("Accept"); + if (it != request.headers.end()) { + auto types = split(it->second, ";"); + for (auto& t: types) { + auto trimmed = trim(t); + if (t == "*/*") return std::move(result); + result.insert(std::move(trimmed)); + } + } + return std::move(result); + } + + Response not_acceptable() { + Response response; + response.code = HTTPStatusCode::NotAcceptable; + response.headers["Content-Type"] = "text/plain"; + response.body = "Not Acceptable"; + return std::move(response); + } + } + + Responder::Responder(const Request& req) + : request_(req) + , accepted_content_types_(get_content_types(req)) + {} + + Responder::Responder(const Request& req, std::string default_content_type) + : request_(req) + , accepted_content_types_(get_content_types(req)) + , default_content_type_(std::move(default_content_type)) + {} + + Response Responder::to_response() const& { + if (generated_response_) { + return *generated_response_; + } else { + return not_acceptable(); + } + } + + Response Responder::to_response() && { + if (generated_response_) { + return std::move(*generated_response_); + } else { + return not_acceptable(); + } + } + + bool Responder::should_accept_content_type(const std::string& content_type) const { + if (!generated_response_) { + if (default_content_type_ && *default_content_type_ == content_type) + return true; + if (accepted_content_types_.size() == 0) + return true; + if (accepted_content_types_.count(content_type) == 1) + return true; + } + return false; + } +} diff --git a/wayward/respond_to.hpp b/wayward/respond_to.hpp new file mode 100644 index 0000000..21df1ff --- /dev/null +++ b/wayward/respond_to.hpp @@ -0,0 +1,92 @@ +#pragma once +#ifndef WAYWARD_RESPOND_TO_HPP_INCLUDED +#define WAYWARD_RESPOND_TO_HPP_INCLUDED + +#include +#include +#include + +#include + +namespace wayward { + template + Response render_json(T&& object) { + Response response; + response.headers["Content-Type"] = "application/json; charset=utf-8"; + response.body = as_json(std::forward(object)); + return std::move(response); + } + + struct Responder { + explicit Responder(const Request&); + Responder(const Request&, std::string default_content_type); + + operator Response() const; + Response to_response() const&; + Response to_response() &&; + + template + Responder& when(F&& f); + + template + Responder& when(std::string format, F&& f); + + template + Responder& when_accepting(std::string content_type, F&& f); + + template + Responder& otherwise(F&& f); + + template + Responder& with(T&& object); + + private: + std::set accepted_content_types_; + Maybe default_content_type_; + Maybe generated_response_; + const Request& request_; + + bool should_accept_content_type(const std::string& content_type) const; + }; + + template + Responder& Responder::when(F&& f) { + return when_accepting(ContentType::MimeType, std::forward(f)); + } + + template + Responder& Responder::when(std::string ext, F&& f) { + return when_accepting(*content_type_for_extension(ext), std::forward(f)); + } + + template + Responder& Responder::when_accepting(std::string content_type, F&& f) { + if (should_accept_content_type(content_type)) { + generated_response_ = f(); + } + return *this; + } + + template + Responder& Responder::otherwise(F&& f) { + if (!generated_response_) { + generated_response_ = f(); + } + return *this; + } + + inline Responder::operator Response() const { + return to_response(); + } + + inline Responder respond_to(Request& req) { + return Responder{req}; + } + + template + inline Responder respond_to(Request& req) { + return Responder{req, DefaultContentType::MimeType}; + } +} + +#endif // WAYWARD_RESPOND_TO_HPP_INCLUDED diff --git a/wayward/routes.hpp b/wayward/routes.hpp index 36249ba..3398170 100644 --- a/wayward/routes.hpp +++ b/wayward/routes.hpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include namespace wayward { @@ -14,12 +16,24 @@ namespace wayward { virtual void after(Request&) {} virtual Response around(Request& req, std::function yield) { return yield(req); } + Session session; + persistence::Context persistence_context; template persistence::Projection from() { return persistence::from(persistence_context); } + + template + persistence::RecordPtr create(const data_franca::Spectator& data) { + return persistence::create(persistence_context, data); + } + + template + bool destroy(persistence::RecordPtr& ptr) { + return persistence::destroy(persistence_context, ptr); + } }; } diff --git a/wayward/session.hpp b/wayward/session.hpp new file mode 100644 index 0000000..f54d404 --- /dev/null +++ b/wayward/session.hpp @@ -0,0 +1,17 @@ +#pragma once +#ifndef WAYWARD_SESSION_HPP_INCLUDED +#define WAYWARD_SESSION_HPP_INCLUDED + +#include + +namespace wayward { + struct Session { + std::map data_; + std::map flash_; + + Maybe flash(const std::string& key) const; + void flash(std::string key, Any value); + }; +} + +#endif // WAYWARD_SESSION_HPP_INCLUDED diff --git a/wayward/support/any.cpp b/wayward/support/any.cpp new file mode 100644 index 0000000..d2366d9 --- /dev/null +++ b/wayward/support/any.cpp @@ -0,0 +1,72 @@ +#include "wayward/support/any.hpp" + +namespace wayward { + Any::Any(const Any& other) : type_info_(other.type_info_) { + ensure_allocation(); + type_info_->copy_construct(memory(), other.memory()); + } + + Any::Any(Any&& other) : type_info_(other.type_info_) { + ensure_allocation(); + type_info_->move_construct(memory(), other.memory()); + } + + Any& Any::operator=(const Any& other) { + if (type_info_ == other.type_info_) { + type_info_->copy_assign(memory(), other.memory()); + } else { + destruct(); + type_info_ = other.type_info_; + ensure_allocation(); + type_info_->copy_construct(memory(), other.memory()); + } + return *this; + } + + Any& Any::operator=(Any&& other) { + if (type_info_ == other.type_info_) { + type_info_->move_assign(memory(), other.memory()); + } else { + destruct(); + type_info_ = other.type_info_; + ensure_allocation(); + type_info_->move_construct(memory(), other.memory()); + } + return *this; + } + + bool Any::is_small_object() const { + return type_info_->size <= SmallObjectStorageSize && type_info_->alignment <= SmallObjectStorageAlignment; + } + + void Any::ensure_allocation() { + if (heap_storage_ == nullptr && !is_small_object()) { + // TODO: Handle alignment! + heap_storage_ = reinterpret_cast(new char[type_info_->size]); + } + } + + void Any::destruct() { + auto mem = memory(); + type_info_->destruct(mem); + if (mem && !is_small_object()) { + delete[] reinterpret_cast(mem); + } + } + + void* Any::memory() { + if (is_small_object()) { + return &inline_storage_; + } else { + return heap_storage_; + } + } + + const void* Any::memory() const { + if (is_small_object()) { + return &inline_storage_; + } else { + return heap_storage_; + } + } +} diff --git a/wayward/support/any.hpp b/wayward/support/any.hpp new file mode 100644 index 0000000..212d0ee --- /dev/null +++ b/wayward/support/any.hpp @@ -0,0 +1,222 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_ANY_HPP_INCLUDED +#define WAYWARD_SUPPORT_ANY_HPP_INCLUDED + +#include +#include +#include + +namespace wayward { + struct Any { + template + Any(T&& object); + template + Any(const T& object); + + Any() {} + Any(NothingType) {} + + Any(const Any& other); + Any(Any&& other); + ~Any() { destruct(); } + Any& operator=(const Any&); + Any& operator=(Any&&); + + const TypeInfo& type_info() const { return *type_info_; } + + template bool is_a() const; + template Maybe get(); + template Maybe get() const; + template auto when(F&& f) -> typename monad::Join()))>::Type; + template auto when(F&& f) const -> typename monad::Join()))>::Type; + + private: + const TypeInfo* type_info_ = &GetTypeInfo::Value; + static const size_t SmallObjectStorageSize = sizeof(void*) * 3; + static const size_t SmallObjectStorageAlignment = sizeof(void*); + using SmallObjectStorage = std::aligned_storage::type; + + union { + SmallObjectStorage inline_storage_; + void* heap_storage_ = nullptr; + }; + + bool is_small_object() const; + void ensure_allocation(); + void destruct(); + void* memory(); + const void* memory() const; + + friend struct AnyRef; + friend struct AnyConstRef; + }; + + struct AnyConstRef; + + struct AnyRef { + template + AnyRef(T& object, + typename std::enable_if::value>::type* dummy = nullptr + ) : type_info_(&GetTypeInfo::Value), ref_(reinterpret_cast(&object)) { + static_assert(!std::is_const::value, "Cannot make a reference to a const type."); + static_assert(!std::is_same::value, "Cannot construct AnyRef to Any."); + static_assert(!std::is_same::value, "Cannot construct AnyRef to AnyRef."); + static_assert(!std::is_same::value, "Cannot construct AnyRef to AnyConstRef."); + } + + AnyRef() {} + AnyRef(const AnyRef& other) = default; + AnyRef(Any& any) : type_info_(&any.type_info()), ref_(any.memory()) {} + AnyRef& operator=(const AnyRef&) = default; + + const TypeInfo& type_info() const { return *type_info_; } + + template bool is_a() const; + template Maybe::Type &> get(); + template Maybe::Type &> get() const; + template auto when(F&& f) -> typename monad::Join()))>::Type; + template auto when(F&& f) const -> typename monad::Join()))>::Type; + private: + const TypeInfo* type_info_ = &GetTypeInfo::Value; + void* ref_ = nullptr; + friend struct AnyConstRef; + }; + + struct AnyConstRef { + template + AnyConstRef(const T& object) : type_info_(&GetTypeInfo::Value), ref_(reinterpret_cast(&object)) { + static_assert(!std::is_same::value, "Cannot construct AnyConstRef to Any."); + static_assert(!std::is_same::value, "Cannot construct AnyConstRef to AnyRef."); + static_assert(!std::is_same::value, "Cannot construct AnyConstRef to AnyConstRef."); + } + + AnyConstRef() {} + AnyConstRef(const AnyConstRef& other) = default; + AnyConstRef(const Any& any) : type_info_(&any.type_info()), ref_(any.memory()) {} + AnyConstRef(const AnyRef& ref) : type_info_(&ref.type_info()), ref_(ref.ref_) {} + AnyConstRef& operator=(const AnyConstRef&) = default; + + const TypeInfo& type_info() const { return *type_info_; } + + template bool is_a() const; + template Maybe::Type&> get() const; + template auto when(F&& f) const -> typename monad::Join()))>::Type; + private: + const TypeInfo* type_info_ = &GetTypeInfo::Value; + const void* ref_ = nullptr; + }; + + template + Any::Any(T&& object) : type_info_(&GetTypeInfo::Type>::Value) { + static_assert(!std::is_same::value, "Cannot construct Any containing Any."); + static_assert(!std::is_same::value, "Cannot construct Any containing AnyConstRef."); + static_assert(!std::is_same::value, "Cannot construct Any containing AnyRef."); + ensure_allocation(); + type_info_->move_construct(memory(), reinterpret_cast(&object)); + } + + template + Any::Any(const T& object) : type_info_(&GetTypeInfo::Type>::Value) { + static_assert(!std::is_same::value, "Cannot construct Any containing AnyConstRef."); + static_assert(!std::is_same::value, "Cannot construct Any containing AnyRef."); + ensure_allocation(); + type_info_->copy_construct(memory(), reinterpret_cast(&object)); + } + + template + bool Any::is_a() const { + return type_info_ == &GetTypeInfo::Value; + } + + template + Maybe Any::get() const { + // This supports getting the internals as a reference-Maybe with get() + using Type = typename meta::RemoveConstRef::Type; + if (is_a()) { + const Type& ref = *reinterpret_cast(memory()); + return Maybe(ref); + } + return Nothing; + } + + template + Maybe Any::get() { + // This supports getting the internals as a reference-Maybe with get() + using Type = typename meta::RemoveConstRef::Type; + if (is_a()) { + Type& ref = *reinterpret_cast(memory()); + return Maybe(ref); + } + return Nothing; + } + + template + auto Any::when(F&& f) -> typename monad::Join()))>::Type { + return monad::fmap(get(), std::forward(f)); + } + + template + auto Any::when(F&& f) const -> typename monad::Join()))>::Type { + return monad::fmap(get(), std::forward(f)); + } + + template + bool AnyRef::is_a() const { + return type_info_ == &GetTypeInfo::Value; + } + + template + Maybe::Type &> AnyRef::get() const { + // This supports getting the internals as a reference-Maybe with get() + using Type = typename meta::RemoveConstRef::Type; + if (is_a()) { + const Type& ref = *reinterpret_cast(ref_); + return Maybe(ref); + } + return Nothing; + } + + template + Maybe::Type &> AnyRef::get() { + // This supports getting the internals as a reference-Maybe with get() + using Type = typename meta::RemoveConstRef::Type; + if (is_a()) { + Type& ref = *reinterpret_cast(ref_); + return Maybe(ref); + } + return Nothing; + } + + template + auto AnyRef::when(F&& f) -> typename monad::Join()))>::Type { + return monad::fmap(get(), std::forward(f)); + } + + template + auto AnyRef::when(F&& f) const -> typename monad::Join()))>::Type { + return monad::fmap(get(), std::forward(f)); + } + + template + bool AnyConstRef::is_a() const { + return type_info_ == &GetTypeInfo::Value; + } + + template + Maybe::Type &> AnyConstRef::get() const { + using Type = typename meta::RemoveConstRef::Type; + if (is_a()) { + const Type& ref = *reinterpret_cast(ref_); + return Maybe(ref); + } + return Nothing; + } + + template + auto AnyConstRef::when(F&& f) const -> typename monad::Join()))>::Type { + return monad::fmap(get(), std::forward(f)); + } + +} + +#endif // WAYWARD_SUPPORT_ANY_HPP_INCLUDED diff --git a/wayward/support/benchmark.cpp b/wayward/support/benchmark.cpp new file mode 100644 index 0000000..c39674f --- /dev/null +++ b/wayward/support/benchmark.cpp @@ -0,0 +1,87 @@ +#include "wayward/support/benchmark.hpp" + +#include + +namespace wayward { + DateTime PerformanceClock::now() const { + struct rusage u; + ::getrusage(RUSAGE_SELF, &u); + Seconds s { u.ru_utime.tv_sec + u.ru_stime.tv_sec }; + Microseconds us { u.ru_utime.tv_usec + u.ru_stime.tv_usec }; + Nanoseconds ns = us + s; + return DateTime{DateTime::Repr{ns}}; + } + + Timezone PerformanceClock::timezone() const { + // Dummy. + return Timezone{}; + } + + PerformanceClock& PerformanceClock::get() { + static PerformanceClock clock; + return clock; + } + + Benchmark::Benchmark() { + start(); + } + + Benchmark::~Benchmark() { + finish(); + } + + void Benchmark::start() { + total_.activations = 1; + total_.begin = PerformanceClock::get().now(); + total_.accum = Microseconds{0}; + } + + DateTimeInterval Benchmark::finish() { + if (total_.activations) { + auto end = PerformanceClock::get().now(); + total_.accum += end - total_.begin; + --total_.activations; + } + return total(); + } + + DateTimeInterval Benchmark::total() const { + return total_.accum; + } + + DateTimeInterval Benchmark::measure(std::function f) { + Benchmark bm; + f(); + return bm.finish(); + } + + std::map Benchmark::scopes() const { + std::map results; + for (auto& pair: scopes_) { + results[pair.first] = pair.second.accum; + } + return std::move(results); + } + + void Benchmark::enter_scope(std::string name) { + auto it = scopes_.find(name); + if (it == scopes_.end()) { + it = scopes_.insert(std::make_pair(name, Measurement{})).first; + } + if (it->second.activations == 0) { + it->second.begin = PerformanceClock::get().now(); + } + ++it->second.activations; + } + + void Benchmark::exit_scope(std::string name) { + auto it = scopes_.find(name); + if (it != scopes_.end()) { + --it->second.activations; + if (it->second.activations == 0) { + auto now = PerformanceClock::get().now(); + it->second.accum += now - it->second.begin; + } + } + } +} diff --git a/wayward/support/benchmark.hpp b/wayward/support/benchmark.hpp new file mode 100644 index 0000000..49dbb0d --- /dev/null +++ b/wayward/support/benchmark.hpp @@ -0,0 +1,58 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_BENCHMARK_HPP_INCLUDED +#define WAYWARD_SUPPORT_BENCHMARK_HPP_INCLUDED + +#include +#include +#include +#include + +namespace wayward { + struct PerformanceClock : IClock { + DateTime now() const final; + Timezone timezone() const final; + + static PerformanceClock& get(); + private: + PerformanceClock() {} + ~PerformanceClock() {} + }; + + struct Benchmark { + Benchmark(); + ~Benchmark(); + + void start(); + DateTimeInterval finish(); + + static DateTimeInterval measure(std::function); + + DateTimeInterval total() const; + std::map scopes() const; + + void enter_scope(std::string name); + void exit_scope(std::string name); + private: + struct Measurement { + DateTimeInterval accum; + DateTime begin; + size_t activations = 0; + }; + + Measurement total_; + std::map scopes_; + }; + + struct BenchmarkScope { + Benchmark& bm_; + std::string name_; + BenchmarkScope(Benchmark& bm, std::string name) : bm_(bm), name_(std::move(name)) { + bm_.enter_scope(name_); + } + ~BenchmarkScope() { + bm_.exit_scope(name_); + } + }; +} + +#endif // WAYWARD_SUPPORT_BENCHMARK_HPP_INCLUDED diff --git a/wayward/support/bitflags.hpp b/wayward/support/bitflags.hpp new file mode 100644 index 0000000..81eb8f3 --- /dev/null +++ b/wayward/support/bitflags.hpp @@ -0,0 +1,35 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_BITFLAGS_HPP_INCLUDED +#define WAYWARD_SUPPORT_BITFLAGS_HPP_INCLUDED + +#include + +namespace wayward { + template + struct Bitflags { + Bitflags() : value_(0) {} + Bitflags(Enum value) : value_(static_cast(value)) {} + Bitflags(const Bitflags&) = default; + Bitflags& operator=(const Bitflags&) = default; + + bool operator==(const Bitflags& other) const { return value_ == other.value_; } + bool operator!=(const Bitflags& other) const { return value_ != other.value_; } + + Bitflags operator|(Bitflags other) const { return value_ | other.value_; } + Bitflags operator&(Bitflags other) const { return value_ & other.value_; } + Bitflags operator^(Bitflags other) const { return value_ ^ other.value_; } + Bitflags operator~() const { return ~value_; } + + Bitflags& operator|=(Bitflags other) { value_ |= other.value_; return *this; } + Bitflags& operator&=(Bitflags other) { value_ &= other.value_; return *this; } + Bitflags& operator^=(Bitflags other) { value_ ^= other.value_; return *this; } + + operator bool() const { return (bool)value_; } + private: + using Raw = typename std::underlying_type::type; + Bitflags(Raw value) : value_(value) {} + Raw value_; + }; +} + +#endif // WAYWARD_SUPPORT_BITFLAGS_HPP_INCLUDED diff --git a/wayward/support/cloning_ptr.hpp b/wayward/support/cloning_ptr.hpp index f3bd95e..24016e6 100644 --- a/wayward/support/cloning_ptr.hpp +++ b/wayward/support/cloning_ptr.hpp @@ -3,6 +3,7 @@ #define WAYWARD_SUPPORT_CLONING_PTR_HPP_INCLUDED #include +#include namespace wayward { struct ICloneable { @@ -70,6 +71,7 @@ namespace wayward { void reset(pointer ptr = pointer()) { ptr.reset(ptr); } void swap(CloningPtr& other) { ptr.swap(other.ptr); } pointer get() { return ptr.get(); } + const pointer get() const { return ptr.get(); } explicit operator bool() const { return ptr.operator bool(); } T& operator*() const { return *ptr; } pointer operator->() const { return ptr.operator->(); } @@ -87,10 +89,24 @@ namespace wayward { return lhs.get() != rhs.get(); } + template + bool operator==(const CloningPtr& lhs, std::nullptr_t) { + return lhs.get() == nullptr; + } + + template + bool operator==(std::nullptr_t, const CloningPtr& rhs) { + return rhs.get() == nullptr; + } + template CloningPtr make_cloning_ptr(T* ptr) { return CloningPtr(ptr); } + + namespace meta { + template struct IsPointerLike> : TrueType {}; + } } namespace std { diff --git a/wayward/support/data_franca.hpp b/wayward/support/data_franca.hpp index 609bb26..8ef9281 100644 --- a/wayward/support/data_franca.hpp +++ b/wayward/support/data_franca.hpp @@ -3,6 +3,8 @@ #define WAYWARD_SUPPORT_DATA_FRANCA_HPP_INCLUDED #include -#include +#include +#include +#include #endif // WAYWARD_SUPPORT_DATA_FRANCA_HPP_INCLUDED diff --git a/wayward/support/data_franca/adapter.hpp b/wayward/support/data_franca/adapter.hpp new file mode 100644 index 0000000..9cbe633 --- /dev/null +++ b/wayward/support/data_franca/adapter.hpp @@ -0,0 +1,99 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_DATA_FRANCA_ADAPTER_HPP_INCLUDED +#define WAYWARD_SUPPORT_DATA_FRANCA_ADAPTER_HPP_INCLUDED + +#include +#include + +#include +#include + +namespace wayward { + namespace data_franca { + enum class Options { + None = 0, + + // A flag that indicates that readers/adapters are allowed to make potentially slow + // requests to fetch all requested data. + AllowLoad = 1, + }; + + struct IAdapter : IReader, IWriter { + virtual ~IAdapter() {} + }; + + template + struct AdapterBase : IAdapter { + AdapterBase(T& ref, Bitflags opts) : ref_(ref), options_(opts) {} + + // IReader interface: + DataType type() const override { return DataType::Nothing; } + Maybe get_boolean() const override { return Nothing; } + Maybe get_integer() const override { return Nothing; } + Maybe get_real() const override { return Nothing; } + Maybe get_string() const override { return Nothing; } + bool has_key(const String& key) const override { return false; } + ReaderPtr get(const String& key) const override { return nullptr; } + size_t length() const override { return 0; } + ReaderPtr at(size_t idx) const override { return nullptr; } + ReaderEnumeratorPtr enumerator() const override { return nullptr; } + + // IWriter interface: + bool set_nothing() override { return false; } + bool set_boolean(Boolean b) override { return false; } + bool set_integer(Integer n) override { return false; } + bool set_real(Real r) override { return false; } + bool set_string(String str) override { return false; } + AdapterPtr reference_at_index(size_t idx) override { return nullptr; } + AdapterPtr push_back() override { return nullptr; } + AdapterPtr reference_at_key(const String& key) override { return nullptr; } + bool erase(const String& key) override { return false; } + + T& ref_; + Bitflags options_; + }; + + template struct Adapter; + + template struct GetAdapter; + + template + auto make_adapter(T&& object, Bitflags options) { + return GetAdapter::Type>::get(std::forward(object), options); + } + + template + ReaderPtr make_reader(T&& object, Bitflags options = Options::None) { + return GetAdapter::Type>::get(std::forward(object), options); + } + + template + struct GetAdapter { + static AdapterPtr get(T& object, Bitflags options) { + return std::static_pointer_cast(std::make_shared>(object, options)); + } + static ReaderPtr get(const T& object, Bitflags options) { + // It's OK to const_cast here, because we immediately upcast to IReader, which is a const-only interface. + return std::static_pointer_cast(std::make_shared>(const_cast(object), options)); + } + }; + + template + struct OwningAdapter : Adapter { + T owned_; + OwningAdapter(T object, Bitflags options) : Adapter(owned_, options), owned_(std::move(object)) {} + }; + + template + AdapterPtr make_owning_adapter(T&& object, Bitflags options = Options::None) { + return AdapterPtr{ new OwningAdapter::Type>{ std::forward(object), options } }; + } + + template + ReaderPtr make_owning_reader(T&& object, Bitflags options = Options::None) { + return ReaderPtr{ new OwningAdapter::Type>{ std::forward(object), options } }; + } + } +} + +#endif // WAYWARD_SUPPORT_DATA_FRANCA_ADAPTER_HPP_INCLUDED diff --git a/wayward/support/data_franca/adapters.hpp b/wayward/support/data_franca/adapters.hpp index 85f8ee1..a53fb66 100644 --- a/wayward/support/data_franca/adapters.hpp +++ b/wayward/support/data_franca/adapters.hpp @@ -2,16 +2,222 @@ #ifndef WAYWARD_SUPPORT_DATA_FRANCA_ADAPTERS_HPP_INCLUDED #define WAYWARD_SUPPORT_DATA_FRANCA_ADAPTERS_HPP_INCLUDED +#include +#include + namespace wayward { namespace data_franca { template - struct StandardReader : IReader { + struct Adapter> : IAdapter { + Maybe& ref_; + Adapter adapter_; + Adapter(Maybe& ref, Bitflags options) : ref_(ref), adapter_(*ref_.unsafe_get(), options) {} + + // IReader interface: + DataType type() const final { return ref_ ? adapter_.type() : DataType::Nothing; } + Maybe get_boolean() const final { return ref_ ? adapter_.get_boolean() : Nothing; } + Maybe get_integer() const final { return ref_ ? adapter_.get_integer() : Nothing; } + Maybe get_real() const final { return ref_ ? adapter_.get_real() : Nothing; } + Maybe get_string() const final { return ref_ ? adapter_.get_string() : Nothing; } + bool has_key(const String& key) const final { return ref_ ? adapter_.has_key(key) : false; } + ReaderPtr get(const String& key) const final { return ref_ ? adapter_.get(key) : nullptr; } + size_t length() const final { return ref_ ? adapter_.length() : 0; } + ReaderPtr at(size_t idx) const final { return ref_ ? adapter_.at(idx) : nullptr; } + ReaderEnumeratorPtr enumerator() const final { return ref_ ? adapter_.enumerator() : nullptr; } + + // IWriter interface: + bool set_nothing() final { ref_ = Nothing; return true; } + bool set_boolean(Boolean b) final { return ref_ ? adapter_.set_boolean(b) : false; } + bool set_integer(Integer n) final { return ref_ ? adapter_.set_integer(n) : false; } + bool set_real(Real r) final { return ref_ ? adapter_.set_real(r) : false; } + bool set_string(String str) final { return ref_ ? adapter_.set_string(std::move(str)) : false; } + AdapterPtr reference_at_index(size_t idx) final { return ref_ ? adapter_.reference_at_index(idx) : nullptr; } + AdapterPtr push_back() final { return ref_ ? adapter_.push_back() : nullptr; } + AdapterPtr reference_at_key(const String& key) final { return ref_ ? adapter_.reference_at_key(key) : nullptr; } + bool erase(const String& key) final { return ref_ ? adapter_.erase(key) : false; } + }; + + template <> + struct Adapter : AdapterBase { + Adapter(NothingType& ref, Bitflags opts) : AdapterBase(ref, opts) {} + }; + + template <> + struct Adapter : AdapterBase { + Adapter(bool& ref, Bitflags o) : AdapterBase(ref, o) {} + + DataType type() const final { return DataType::Boolean; } + Maybe get_boolean() const final { return this->ref_; } + bool set_boolean(Boolean b) final { this->ref_ = b; return true; } + }; + + template struct IsSignedIntegerValue; + template <> struct IsSignedIntegerValue { static const bool Value = true; }; + template <> struct IsSignedIntegerValue { static const bool Value = true; }; + template <> struct IsSignedIntegerValue { static const bool Value = true; }; + template struct IsSignedIntegerValue { static const bool Value = false; }; + + template + struct Adapter::Value>::type> : AdapterBase { + Adapter(T& ref, Bitflags o = Options::None) : AdapterBase(ref, o) {} + + DataType type() const final { return DataType::Integer; } + Maybe get_integer() const final { return static_cast(this->ref_); } + bool set_integer(Integer n) final { this->ref_ = static_cast(n); return true; } + }; + + template + struct Adapter::value>::type> : AdapterBase { + Adapter(T& ref, Bitflags o = Options::None) : AdapterBase(ref, o) {} + + DataType type() const final { return DataType::Real; } + Maybe get_real() const final { return static_cast(this->ref_); } + bool set_real(Real r) final { this->ref_ = static_cast(r); return true; } + }; + + template <> + struct Adapter : AdapterBase { + Adapter(String& ref, Bitflags o) : AdapterBase(ref, o) {} + + DataType type() const final { return DataType::String; } + Maybe get_string() const final { return this->ref_; } + bool set_string(String str) final { this->ref_ = std::move(str); return true; } + }; + + template + struct Adapter> : AdapterBase> { + Adapter(std::vector& ref, Bitflags o) : AdapterBase>(ref, o) {} + + DataType type() const final { return DataType::List; } + size_t length() const final { return this->ref_.size(); } + + ReaderPtr at(size_t idx) const { + if (idx < this->ref_.size()) { + return make_reader(this->ref_.at(idx), this->options_); + } + return nullptr; + } + + struct Enumerator : Cloneable { + using Iterator = typename std::vector::const_iterator; + Enumerator(Iterator it, Iterator end, Bitflags o) : it_(it), end_(end), options_(o) {} + Iterator it_; + Iterator end_; + Bitflags options_; + + ReaderPtr current_value() const final { + if (it_ != end_) { + return make_reader(*it_, options_); + } + return nullptr; + } + + Maybe current_key() const final { return Nothing; } + + bool at_end() const final { return it_ == end_; } + + void move_next() final { + if (!at_end()) { + ++it_; + } + } + }; + + ReaderEnumeratorPtr enumerator() const final { + return ReaderEnumeratorPtr(new Enumerator{this->ref_.begin(), this->ref_.end(), this->options_}); + } + + AdapterPtr reference_at_index(size_t idx) final { + if (idx < this->ref_.size()) { + return make_adapter(this->ref_.at(idx), this->options_); + } + return nullptr; + } + + AdapterPtr push_back() final { + this->ref_.push_back(T{}); + return make_adapter(this->ref_.back(), this->options_); + } + }; + + template + struct Adapter> : AdapterBase> { + Adapter(std::map& ref, Bitflags o) : AdapterBase>(ref, o) {} + + DataType type() const final { return DataType::Dictionary; } + size_t length() const final { return this->ref_.size(); } + + bool has_key(const String& key) const final { return this->ref_.find(key) != this->ref_.end(); } + + ReaderPtr get(const String& key) const final { + auto it = this->ref_.find(key); + if (it != this->ref_.end()) { + return make_reader(it->second); + } + return nullptr; + } + + struct Enumerator : Cloneable { + using Iterator = typename std::map::const_iterator; + Iterator it_; + Iterator end_; + Bitflags options_; + Enumerator(Iterator it, Iterator end, Bitflags o) : it_(it), end_(end), options_(o) {} + + ReaderPtr current_value() const final { + if (it_ != end_) { + return make_reader(it_->second, options_); + } + return nullptr; + } + + Maybe current_key() const final { + if (it_ != end_) { + return it_->first; + } + return Nothing; + } + + bool at_end() const final { return it_ == end_; } + + void move_next() final { + if (!at_end()) { + ++it_; + } + } + }; + + ReaderEnumeratorPtr enumerator() const final { + return ReaderEnumeratorPtr{new Enumerator{this->ref_.begin(), this->ref_.end(), this->options_}}; + } + + AdapterPtr reference_at_key(const String& key) final { + auto it = this->ref_.find(key); + if (it == this->ref_.end()) { + it = this->ref_.insert(std::make_pair(key, T{})).first; + } + return make_adapter(it->second, this->options_); + } + bool erase(const String& key) final { + auto it = this->ref_.find(key); + if (it != this->ref_.end()) { + this->ref_.erase(it); + return true; + } + return false; + } }; template - struct StandardAdapter : IWriter { + using OwningMapAdapter = OwningAdapter>; + using OwningStringAdapter = OwningAdapter; + template + struct GetAdapter { + static ReaderPtr get(const char* str, Bitflags o) { + return ReaderPtr{new OwningStringAdapter{String{str, N-1}, o}}; + } }; } } diff --git a/wayward/support/data_franca/get_adapter.hpp b/wayward/support/data_franca/get_adapter.hpp deleted file mode 100644 index 185910e..0000000 --- a/wayward/support/data_franca/get_adapter.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#ifndef WAYWARD_SUPPORT_DATA_FRANCA_GET_ADAPTER_HPP_INCLUDED -#define WAYWARD_SUPPORT_DATA_FRANCA_GET_ADAPTER_HPP_INCLUDED - -namespace wayward { - namespace data_franca { - template struct GetAdapter; - - struct IReader; - struct IWriter; - using ReaderPtr = std::shared_ptr; - using AdapterPtr = std::shared_ptr; - - template - auto make_adapter(T&& object) -> - // C++14 now please... - decltype( - GetAdapter::Type>::get(std::forward(object)) - ) { - return GetAdapter::Type>::get(std::forward(object)); - } - - template - ReaderPtr make_reader(T&& object) { - return std::static_pointer_cast(make_adapter(std::forward(object))); - } - - template - struct GetAdapter { - AdapterPtr get(T& object) { - return std::static_pointer_cast(std::make_shared>(object)); - } - ReaderPtr get(const T& object) { - return std::static_pointer_cast(std::make_shared>(object)); - } - }; - } -} - -#endif // WAYWARD_SUPPORT_DATA_FRANCA_GET_ADAPTER_HPP_INCLUDED diff --git a/wayward/support/data_franca/mutator.cpp b/wayward/support/data_franca/mutator.cpp new file mode 100644 index 0000000..268854b --- /dev/null +++ b/wayward/support/data_franca/mutator.cpp @@ -0,0 +1,7 @@ +#include "wayward/support/data_franca/mutator.hpp" + +namespace wayward { + namespace data_franca { + const NullReader Mutator::g_null_reader; + } +} diff --git a/wayward/support/data_franca/mutator.hpp b/wayward/support/data_franca/mutator.hpp new file mode 100644 index 0000000..9170074 --- /dev/null +++ b/wayward/support/data_franca/mutator.hpp @@ -0,0 +1,95 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_DATA_FRANCA_MUTATOR_HPP_INCLUDED +#define WAYWARD_SUPPORT_DATA_FRANCA_MUTATOR_HPP_INCLUDED + +#include +#include + +namespace wayward { + namespace data_franca { + struct Mutator final : ReaderInterface, WriterInterface { + Mutator() {} + Mutator(AdapterPtr ptr) : q_{std::move(ptr)} {} + Mutator(const Mutator&) = default; + Mutator(Mutator&&) = default; + Mutator& operator=(const Mutator&) = default; + Mutator& operator=(Mutator&&) = default; + + template + Mutator(T&& object); + + Spectator operator[](size_t idx) const { return this->reader_subscript(idx); } + Spectator operator[](const String& key) const { return this->reader_subscript(key); } + Spectator operator[](const char* key) const { return this->reader_subscript(key); } + Mutator operator[](size_t idx) { return this->writer_subscript(idx); } + Mutator operator[](const String& key) { return this->writer_subscript(key); } + Mutator operator[](const char* key) { return this->writer_subscript(key); } + + DataType type() const; + + const IReader& reader_iface() const; + IWriter& writer_iface(); + IAdapter& adapter_iface(); + private: + friend struct ReaderInterface; + friend struct WriterInterface; + friend struct GetAdapter; + + AdapterPtr q_; + + static const NullReader g_null_reader; + }; + + template + Mutator::Mutator(T&& object) : q_(make_adapter(std::forward(object), Options::None)) {} + + inline DataType Mutator::type() const { + return reader_iface().type(); + } + + inline const IReader& Mutator::reader_iface() const { + return q_ ? static_cast(*q_) : g_null_reader; + } + + struct MutationError : Error { + MutationError(const std::string& message) : Error(message) {} + }; + + inline IWriter& Mutator::writer_iface() { + if (q_ == nullptr) { + throw MutationError{"Attempted to modify an empty mutator."}; + } + return *q_; + } + + inline IAdapter& Mutator::adapter_iface() { + if (q_ == nullptr) { + throw MutationError{"Attempted to modify an empty mutator."}; + } + return *q_; + } + + template <> + struct GetAdapter { + static ReaderPtr get(const Mutator& m, Options options) { return std::static_pointer_cast(m.q_); } + static AdapterPtr get(Mutator& m, Options options) { return m.q_; } + }; + + struct ScalarMutator final : ReaderInterface, WriterInterface { + ScalarMutator(const ScalarMutator&) = default; + ScalarMutator(Mutator& mutator) : adapter_(mutator.adapter_iface()) {} + ScalarMutator(IAdapter& adapter) : adapter_(adapter) {} + + explicit operator bool() const { return type() != DataType::Nothing; } + DataType type() const { return adapter_.type(); } + + const IReader& reader_iface() const { return adapter_; } + IWriter& writer_iface() { return adapter_; } + IAdapter& adapter_iface() { return adapter_; } + private: + IAdapter& adapter_; + }; + } +} + +#endif // WAYWARD_SUPPORT_DATA_FRANCA_MUTATOR_HPP_INCLUDED diff --git a/wayward/support/data_franca/node.hpp b/wayward/support/data_franca/node.hpp deleted file mode 100644 index f9590ca..0000000 --- a/wayward/support/data_franca/node.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once -#ifndef WAYWARD_SUPPORT_DATA_FRANCA_NODE_HPP_INCLUDED -#define WAYWARD_SUPPORT_DATA_FRANCA_NODE_HPP_INCLUDED - -#include - -#include -#include - -namespace wayward { - namespace data_franca { - struct Node { - Node(const Node& other) = default; - Node(Node&& other) = default; - Node(NothingType = Nothing) : data_(Nothing) {} - Node(Boolean b) : data_(b) {} - Node(Integer n) : data_(n) {} - Node(Real r) : data_(r) {} - Node(String str) : data_(std::move(str)) {} - - Node clone() const { return *this; } - - // Immutable interface: - DataType type() const; - bool is_nothing() const; - operator bool() const; - bool operator>>(Boolean& b) const; - bool operator>>(Integer& n) const; - bool operator>>(Real& r) const; - bool operator>>(String& str) const; - size_t length() const; - const Node& operator[](size_t idx) const; - const Node& operator[](const String& key) const; - struct iterator; - iterator begin() const; - iterator end() const; - - // Mutable interface -- changes the type of the node: - Node& operator=(const Node& other) = default; - Node& operator=(Node&& other) = default; - Node& operator[](size_t idx); - Node& operator[](const String& key); - void push_back(Node value); - - private: - using List = std::vector>; - using Dictionary = std::map>; - - Either< - NothingType, - Boolean, - Integer, - Real, - String, - List, - Dictionary - > data_; - - static const Node forever_empty_; - }; - } -} - -#endif // WAYWARD_SUPPORT_DATA_FRANCA_NODE_HPP_INCLUDED diff --git a/wayward/support/data_franca/object.cpp b/wayward/support/data_franca/object.cpp new file mode 100644 index 0000000..72ce912 --- /dev/null +++ b/wayward/support/data_franca/object.cpp @@ -0,0 +1,152 @@ +#include "wayward/support/data_franca/object.hpp" + +namespace wayward { + namespace data_franca { + const Object Object::g_null_object; + + struct Object::ListEnumerator : Cloneable { + using Iterator = Object::List::const_iterator; + Iterator it_; + Iterator end_; + ListEnumerator(Iterator it, Iterator end) : it_(it), end_(end) {} + ReaderPtr current_value() const final { return make_reader(**it_); } + Maybe current_key() const final { return Nothing; } + bool at_end() const final { return it_ == end_; } + void move_next() final { ++it_; } + }; + + struct Object::DictEnumerator : Cloneable { + using Iterator = Object::Dictionary::const_iterator; + Iterator it_; + Iterator end_; + DictEnumerator(Iterator it, Iterator end) : it_(it), end_(end) {} + ReaderPtr current_value() const final { return make_reader(*it_->second); } + Maybe current_key() const final { return it_->first; } + bool at_end() const final { return it_ == end_; } + void move_next() final { ++it_; } + }; + + DataType Object::type() const { + // XXX: Slightly fragile, but never change the order of the Either. + switch (data_.which()) { + case 0: return DataType::Nothing; + case 1: return DataType::Boolean; + case 2: return DataType::Integer; + case 3: return DataType::Real; + case 4: return DataType::String; + case 5: return DataType::List; + case 6: return DataType::Dictionary; + default: return DataType::Nothing; + } + } + + ReaderEnumeratorPtr Object::enumerator() const { + ReaderEnumeratorPtr ptr; + data_.template when([&](const List& list) { + ptr = ReaderEnumeratorPtr{new ListEnumerator{list.begin(), list.end()}}; + }); + data_.template when([&](const Dictionary& dict) { + ptr = ReaderEnumeratorPtr{new DictEnumerator{dict.begin(), dict.end()}}; + }); + return std::move(ptr); + } + + Object& Object::reference_at_index(size_t idx) { + if (type() != DataType::List) { + throw ObjectIndexOutOfBounds{"Object is not a list."}; + } + Object* ptr = nullptr; + data_.template when([&](List& list) { + if (idx < list.size()) { + ptr = list[idx].get(); + } + }); + if (ptr == nullptr) { + throw ObjectIndexOutOfBounds{"Index out of bounds."}; + } + return *ptr; + } + + const Object& Object::at(size_t idx) const { + if (type() != DataType::List) { + throw ObjectIndexOutOfBounds{"Object is not a list."}; + } + const Object* ptr = nullptr; + data_.template when([&](const List& list) { + if (idx < list.size()) { + ptr = list[idx].get(); + } + }); + if (ptr == nullptr) { + throw ObjectIndexOutOfBounds{"Index out of bounds."}; + } + return *ptr; + } + + Object& Object::reference_at_key(const String& key) { + if (type() != DataType::Dictionary) { + data_ = Dictionary{}; + } + Object* ptr = nullptr; + data_.template when([&](Dictionary& dict) { + auto it = dict.find(key); + if (it == dict.end()) { + it = dict.insert(std::make_pair(key, CloningPtr{new Object})).first; + } + ptr = it->second.get(); + }); + assert(ptr != nullptr); + return *ptr; + } + + bool Object::push_back(Object other) { + if (type() != DataType::List) { + data_ = List{}; + } + data_.template when([&](List& list) { + list.emplace_back(new Object(std::move(other))); + }); + return true; + } + + size_t Object::length() const { + size_t len = 0; + data_.template when([&](const List& list) { + len = list.size(); + }); + data_.template when([&](const Dictionary& dict) { + len = dict.size(); + }); + return len; + } + + bool Object::erase(const String& key) { + bool b = false; + data_.template when([&](Dictionary& dict) { + dict.erase(key); + b = true; + }); + return b; + } + + const Object& Object::get(const String& key) const { + const Object* ptr = &g_null_object; + data_.template when([&](const Dictionary& dict) { + auto it = dict.find(key); + if (it != dict.end()) { + ptr = it->second.get(); + } + }); + return *ptr; + } + + bool Object::has_key(const String& key) const { + bool has = false; + data_.template when([&](const Dictionary& dict) { + auto it = dict.find(key); + has = it != dict.end(); + }); + return has; + } + } +} diff --git a/wayward/support/data_franca/object.hpp b/wayward/support/data_franca/object.hpp index 0832c09..77eecba 100644 --- a/wayward/support/data_franca/object.hpp +++ b/wayward/support/data_franca/object.hpp @@ -2,8 +2,12 @@ #ifndef WAYWARD_SUPPORT_DATA_FRANCA_OBJECT_HPP_INCLUDED #define WAYWARD_SUPPORT_DATA_FRANCA_OBJECT_HPP_INCLUDED +#include +#include + #include #include +#include #include @@ -23,7 +27,30 @@ namespace wayward { Object(Real r) : data_(r) {} Object(String s) : data_(std::move(s)) {} + Object(const Object&) = default; + Object(Object&&) = default; + Object& operator=(Object&&) = default; + Object& operator=(const Object&) = default; + + + // Convenience: + Object(int n) : data_((Integer)n) {} + Object(const char* str) : data_(std::string{str}) {} + static Object dictionary() { Object o; o.data_ = Dictionary{}; return std::move(o); } + static Object list() { Object o; o.data_ = List{}; return std::move(o); } + + Object* clone() const { return new Object(*this); } + DataType type() const; + Object& operator[](size_t idx) { return this->writer_subscript(idx); } + const Object& operator[](size_t idx) const { return this->reader_subscript(idx); } + Object& operator[](const String& key) { return this->writer_subscript(key); } + const Object& operator[](const String& key) const { return this->reader_subscript(key); } + Object& operator[](const char* key) { return this->writer_subscript(key); } + const Object& operator[](const char* key) const { return this->reader_subscript(key); } + + // This sets the type to 'List': + void reserve(size_t idx); // ReaderInterface required interface: const Object& reader_iface() const { return *this; } @@ -36,8 +63,13 @@ namespace wayward { size_t length() const; const Object& at(size_t idx) const; + struct ListEnumerator; + struct DictEnumerator; + ReaderEnumeratorPtr enumerator() const; + // WriterInterface required interface: Object& writer_iface() { return *this; } + bool set_nothing() { data_ = Nothing; return true; } bool set_boolean(Boolean b) { data_ = b; return true; } bool set_integer(Integer n) { data_ = n; return true; } bool set_real(Real r) { data_ = r; return true; } @@ -62,6 +94,51 @@ namespace wayward { > data_; static const Object g_null_object; + + friend struct Enumerator; + friend struct Adapter; + }; + + struct ObjectIndexOutOfBounds : Error { + ObjectIndexOutOfBounds(const std::string& msg) : Error{msg} {} + }; + + template <> + struct Adapter : IAdapter { + Adapter(Object& ref, Bitflags options) : ref_(ref), options_(options) {} + + // IReader interface: + DataType type() const final { return ref_.type(); } + Maybe get_boolean() const final { return ref_.get_boolean(); } + Maybe get_integer() const final { return ref_.get_integer(); } + Maybe get_real() const final { return ref_.get_real(); } + Maybe get_string() const final { return ref_.get_string(); } + bool has_key(const String& key) const final { return ref_.has_key(key); } + ReaderPtr get(const String& key) const final { return make_reader(ref_.get(key), options_); } + size_t length() const final { return ref_.length(); } + ReaderPtr at(size_t idx) const final { return make_reader(ref_.at(idx), options_); } + ReaderEnumeratorPtr enumerator() const final { return ref_.enumerator(); } + + // IWriter interface: + bool set_nothing() final { return ref_.set_nothing(); } + bool set_boolean(Boolean b) final { return ref_.set_boolean(b); } + bool set_integer(Integer n) final { return ref_.set_integer(n); } + bool set_real(Real r) final { return ref_.set_real(r); } + bool set_string(String str) final { return ref_.set_string(std::move(str)); } + AdapterPtr reference_at_index(size_t idx) final { return make_adapter(ref_.reference_at_index(idx), options_); } + AdapterPtr push_back() final { + ref_.push_back(Object{}); + AdapterPtr ptr; + ref_.data_.template when([&](Object::List& list) { + ptr = make_adapter(*list.back(), options_); + }); + return std::move(ptr); + } + AdapterPtr reference_at_key(const String& key) final { return make_adapter(ref_.reference_at_key(key), options_); } + bool erase(const String& key) final { return ref_.erase(key); } + + Object& ref_; + Bitflags options_; }; } } diff --git a/wayward/support/data_franca/reader.hpp b/wayward/support/data_franca/reader.hpp index b4d0028..da44c08 100644 --- a/wayward/support/data_franca/reader.hpp +++ b/wayward/support/data_franca/reader.hpp @@ -3,9 +3,9 @@ #define WAYWARD_SUPPORT_DATA_FRANCA_READER_HPP_INCLUDED #include -#include #include +#include #include #include @@ -13,7 +13,7 @@ namespace wayward { namespace data_franca { struct IReaderEnumerator; - using ReaderEnumeratorPtr = std::unique_ptr; + using ReaderEnumeratorPtr = CloningPtr; /* A Reader traverses data and inspects it as it passes over it. @@ -44,9 +44,11 @@ namespace wayward { virtual Maybe current_key() const = 0; virtual bool at_end() const = 0; virtual void move_next() = 0; + virtual IReaderEnumerator* clone() const = 0; }; - struct NullReader : IReader { + struct NullReader final : IReader { + NullReader() {} DataType type() const final { return DataType::Nothing; } Maybe get_boolean() const final { return Nothing; } Maybe get_integer() const final { return Nothing; } @@ -60,11 +62,11 @@ namespace wayward { ReaderEnumeratorPtr enumerator() const { return nullptr; } }; - struct ReaderEnumeratorAtEnd : IReaderEnumerator { + struct ReaderEnumeratorAtEnd : Cloneable { ReaderPtr current_value() const final { return nullptr; } Maybe current_key() const final { return Nothing; } bool at_end() const final { return true; } - void move_next() const final {} + void move_next() final {} }; template @@ -75,8 +77,9 @@ namespace wayward { bool operator>>(Integer& n) const; bool operator>>(Real& r) const; bool operator>>(String& str) const; - Subscript operator[](size_t idx) const; - Subscript operator[](const String& key) const; + Subscript reader_subscript(size_t idx) const; + Subscript reader_subscript(const String& key) const; + bool has_key(const String& key) const; size_t length() const; struct iterator; iterator begin() const; @@ -86,32 +89,53 @@ namespace wayward { ReaderInterface() {} private: // Could be an IReader, could be something else... - auto reader() const -> decltype(std::declval().reader_iface()) { - return static_cast(this)->reader_iface(); + template + auto reader() const -> decltype(std::declval().reader_iface()) { + return static_cast(this)->reader_iface(); } }; template struct ReaderInterface::iterator { + iterator(const iterator&) = default; + iterator(iterator&&) = default; + bool operator==(const iterator& other) const { - return (enumerator_ == nullptr && other.enumerator_ == nullptr) || (enumerator_->at_end() == other.enumerator_->at_end()); + if (enumerator_ == nullptr) { + if (other.enumerator_ == nullptr) { + return true; + } else { + return other.enumerator_->at_end(); + } + } else { + if (other.enumerator_ == nullptr) { + return enumerator_->at_end(); + } else { + return enumerator_->at_end() == other.enumerator_->at_end(); + } + } } - bool operator!=(const iterator& other) const { !(*this == other); } - const Self& operator*() const { return current_; } - const Self* operator->() const { return ¤t_; } - iterator& operator++() { enumerator_->move_next(); current_ = Self{enumerator_->current_value}; return *this; } + bool operator!=(const iterator& other) const { return !(*this == other); } + const Subscript& operator*() const { return current_; } + const Subscript* operator->() const { return ¤t_; } + + Maybe key() const { return enumerator_->current_key(); } + + iterator& operator++() { enumerator_->move_next(); update_ptr(); return *this; } private: - iterator(ReaderEnumeratorPtr e) : enumerator_{std::move(e)}, current_{enumerator_->current_value()} {} + iterator(ReaderEnumeratorPtr e) : enumerator_{std::move(e)} { update_ptr(); } ReaderEnumeratorPtr enumerator_; - const Self current_; + Subscript current_; + friend struct ReaderInterface; + void update_ptr() { + if (enumerator_ && !enumerator_->at_end()) { + auto c = enumerator_->current_value(); + current_ = std::move(c); + } + } }; - template - bool ReaderInterface::is_null() const { - return !q_; - } - template bool ReaderInterface::is_nothing() const { return reader().type() == DataType::Nothing; @@ -124,7 +148,7 @@ namespace wayward { template bool ReaderInterface::operator>>(Boolean& b) const { - if (type() == DataType::Boolean) { + if (reader().type() == DataType::Boolean) { b = *reader().get_boolean(); return true; } @@ -133,29 +157,83 @@ namespace wayward { template bool ReaderInterface::operator>>(Integer& n) const { - if (type() == DataType::Integer) { - n = *reader().get_integer(); - return true; + auto type = reader().type(); + switch (type) { + case DataType::Integer: { + n = *reader().get_integer(); + return true; + } + case DataType::Real: { + Real r = *reader().get_real(); + n = static_cast(r); + return true; + } + case DataType::String: { + std::stringstream ss(*reader().get_string()); + return (ss >> n).eof(); + } + default: + return false; } - return false; } template bool ReaderInterface::operator>>(Real& r) const { - if (type() == DataType::Real) { - r = *reader().get_real(); - return true; + auto type = reader().type(); + switch (type) { + case DataType::Integer: { + Integer n = *reader().get_integer(); + r = static_cast(n); + return true; + } + case DataType::Real: { + r = *reader().get_real(); + return true; + } + case DataType::String: { + std::stringstream ss(*reader().get_string()); + return (ss >> r).eof(); + } + default: + return false; } - return false; } template bool ReaderInterface::operator>>(String& str) const { - if (type() == DataType::String) { - str = *reader().get_string(); - return true; + auto type = reader().type(); + switch (type) { + case DataType::Integer: { + std::stringstream ss; + ss << *reader().get_integer(); + str = ss.str(); + return true; + } + case DataType::Real: { + std::stringstream ss; + ss << *reader().get_real(); + str = ss.str(); + return true; + } + case DataType::String: { + str = *reader().get_string(); + return true; + } + default: + return false; } - return false; + } + + template + typename ReaderInterface::iterator + ReaderInterface::begin() const { + return iterator{reader().enumerator()}; + } + + template + typename ReaderInterface::iterator + ReaderInterface::end() const { + return iterator{ReaderEnumeratorPtr{new ReaderEnumeratorAtEnd}}; } template @@ -164,14 +242,19 @@ namespace wayward { } template - Subscript ReaderInterface::operator[](size_t idx) const { + Subscript ReaderInterface::reader_subscript(size_t idx) const { return reader().at(idx); } template - Subscript ReaderInterface::operator[](const String& key) const { + Subscript ReaderInterface::reader_subscript(const String& key) const { return reader().get(key); } + + template + bool ReaderInterface::has_key(const String& key) const { + return reader().has_key(key); + } } } diff --git a/wayward/support/data_franca/spectator.cpp b/wayward/support/data_franca/spectator.cpp new file mode 100644 index 0000000..0205d56 --- /dev/null +++ b/wayward/support/data_franca/spectator.cpp @@ -0,0 +1,7 @@ +#include + +namespace wayward { + namespace data_franca { + const NullReader Spectator::g_null_reader; + } +} diff --git a/wayward/support/data_franca/spectator.hpp b/wayward/support/data_franca/spectator.hpp new file mode 100644 index 0000000..1b088bb --- /dev/null +++ b/wayward/support/data_franca/spectator.hpp @@ -0,0 +1,82 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_DATA_FRANCA_SPELUNKER_HPP_INCLUDED +#define WAYWARD_SUPPORT_DATA_FRANCA_SPELUNKER_HPP_INCLUDED + +#include +#include + +namespace wayward { + namespace data_franca { + /* + A Spectator can spelunk through data structures of any type that defines a + suitable adapter. + */ + struct Spectator final : ReaderInterface { + Spectator() {} + Spectator(ReaderPtr ptr) : q_{std::move(ptr)} {} + Spectator(const Spectator&) = default; + Spectator(Spectator&&) = default; + template + Spectator(T&& object); + template + Spectator(const T& object); + template + Spectator(T& object); + + Spectator& operator=(const Spectator&) = default; + Spectator& operator=(Spectator&&) = default; + + explicit operator bool() const { return type() != DataType::Nothing; } + + Spectator operator[](size_t idx) const { return this->reader_subscript(idx); } + Spectator operator[](const String& key) const { return this->reader_subscript(key); } + Spectator operator[](const char* key) const { return this->reader_subscript(key); } + + DataType type() const { return q_ ? q_->type() : DataType::Nothing; } + + const IReader& reader_iface() const { return q_ ? *q_ : g_null_reader; } + private: + ReaderPtr q_; + friend struct ReaderInterface; + friend struct GetAdapter; + + static const NullReader g_null_reader; + }; + + template + Spectator::Spectator(const T& object) : q_(make_reader(object, Options::AllowLoad)) {} + + template + Spectator::Spectator(T& object) : q_(make_reader(object, Options::AllowLoad)) {} + + template + Spectator::Spectator(T&& object) : q_(make_owning_reader(std::move(object), Options::AllowLoad)) {} + + template <> + struct GetAdapter { + static ReaderPtr get(const Spectator& s, Bitflags o) { return s.q_; } + }; + + /* + The ScalarSpectator has identical semantics to the Spectator, except it can only + represent scalar values (i.e., not lists and dictionaries). It's an optimization + to allow some types of deserialization without allocation. + */ + struct ScalarSpectator final : ReaderInterface { + ScalarSpectator(const ScalarSpectator&) = default; + ScalarSpectator(const Spectator& spectator) : reader_(spectator.reader_iface()) { } + ScalarSpectator(const IReader& reader) : reader_(reader) {} + + explicit operator bool() const { return type() != DataType::Nothing; } + + DataType type() const { return reader_.type(); } + + const IReader& reader_iface() const { return reader_; } + private: + friend struct ReaderInterface; + const IReader& reader_; + }; + } +} + +#endif // WAYWARD_SUPPORT_DATA_FRANCA_SPELUNKER_HPP_INCLUDED diff --git a/wayward/support/data_franca/spelunker.hpp b/wayward/support/data_franca/spelunker.hpp deleted file mode 100644 index cf8fe93..0000000 --- a/wayward/support/data_franca/spelunker.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#ifndef WAYWARD_SUPPORT_DATA_FRANCA_SPELUNKER_HPP_INCLUDED -#define WAYWARD_SUPPORT_DATA_FRANCA_SPELUNKER_HPP_INCLUDED - -#include -#include - -namespace wayward { - namespace data_franca { - /* - A Spelunker can spelunk through data structures of any type that defines a - suitable adapter. - */ - struct Spelunker final : ReaderInterface { - Spelunker() {} - Spelunker(const Spelunker&) = default; - Spelunker(Spelunker&&) = default; - - Spelunker& operator=(const Spelunker&) = default; - Spelunker& operator=(Spelunker&&) = default; - - template - Spelunker(T&& object); - - DataType type() const { return q_ ? q_->type() : DataType::NothingType; } - - const IReader& reader_iface() const { return q_ ? *q_ : g_null_reader; } - private: - ReaderPtr q_; - friend struct ReaderInterface; - friend struct iterator; - Spelunker(ReaderPtr ptr) : q_{std::move(ptr)} {} - - static const NullReader g_null_reader; - }; - - template - Spelunker::Spelunker(T&& object) : q_(make_reader(std::forward(object))) {} - } -} - -#endif // WAYWARD_SUPPORT_DATA_FRANCA_SPELUNKER_HPP_INCLUDED diff --git a/wayward/support/data_franca/types.hpp b/wayward/support/data_franca/types.hpp index d8ff82c..238ceeb 100644 --- a/wayward/support/data_franca/types.hpp +++ b/wayward/support/data_franca/types.hpp @@ -21,6 +21,13 @@ namespace wayward { List, Dictionary, }; + + struct IReader; + struct IWriter; + struct IAdapter; + using ReaderPtr = std::shared_ptr; + using WriterPtr = std::shared_ptr; + using AdapterPtr = std::shared_ptr; } } diff --git a/wayward/support/data_franca/writer.hpp b/wayward/support/data_franca/writer.hpp index 54ec8ac..e140065 100644 --- a/wayward/support/data_franca/writer.hpp +++ b/wayward/support/data_franca/writer.hpp @@ -3,87 +3,104 @@ #define WAYWARD_SUPPORT_DATA_FRANCA_WRITER_HPP_INCLUDED #include -#include +#include namespace wayward { namespace data_franca { - struct IWriter : IReader { + struct Spectator; + + struct IWriter { virtual ~IWriter() {} + virtual bool set_nothing() = 0; virtual bool set_boolean(Boolean b) = 0; virtual bool set_integer(Integer n) = 0; virtual bool set_real(Real r) = 0; virtual bool set_string(String str) = 0; - virtual WriterPtr reference_at_index(size_t idx) = 0; - virtual bool push_back(const Spelunker& value) = 0; + virtual AdapterPtr reference_at_index(size_t idx) = 0; + virtual AdapterPtr push_back() = 0; - virtual WriterPtr reference_at_key(const String& key) = 0; + virtual AdapterPtr reference_at_key(const String& key) = 0; virtual bool erase(const String& key) = 0; }; template struct WriterInterface { + bool operator<<(NothingType); bool operator<<(Boolean b); bool operator<<(Integer n); bool operator<<(Real r); bool operator<<(String str); - Subscript operator[](size_t idx); - Subscript operator[](const String& key); + // Convenience: + bool operator<<(int n) { return *this << (Integer)n; } + bool operator<<(const char* str) { return *this << std::string{str}; } + + Subscript writer_subscript(size_t idx); + Subscript writer_subscript(const String& key); bool erase(const String& key); - bool push_back(const Self& value); + template + bool push_back(const T& value); protected: WriterInterface() {} private: // Could be an IWriter, could be something else... - auto writer() -> decltype(std::declval().writer_iface()) { - return static_cast(this)->writer_iface(); + template + auto writer() -> decltype(std::declval().writer_iface()) { + return static_cast(this)->writer_iface(); } }; template - bool WriterInterface::operator<<(Boolean b) { - return writer().set_boolean(b): + bool WriterInterface::operator<<(NothingType) { + return writer().set_nothing(); + } + + template + bool WriterInterface::operator<<(Boolean b) { + return writer().set_boolean(b); } template - bool WriterInterface::operator<<(Integer n) { - return writer().set_integer(n): + bool WriterInterface::operator<<(Integer n) { + return writer().set_integer(n); } template - bool WriterInterface::operator<<(Real r) { - return writer().set_read(r): + bool WriterInterface::operator<<(Real r) { + return writer().set_real(r); } template - bool WriterInterface::operator<<(String str) { - return writer().set_string(std::move(str)): + bool WriterInterface::operator<<(String str) { + return writer().set_string(std::move(str)); } template - Subscript WriterInterface::operator[](size_t idx) { + Subscript WriterInterface::writer_subscript(size_t idx) { return writer().reference_at_index(idx); } template - Subscript WriterInterface::operator[](const String& key) { + Subscript WriterInterface::writer_subscript(const String& key) { return writer().reference_at_key(key); } template - bool WriterInterface::erase(const String& key) { + bool WriterInterface::erase(const String& key) { return writer().erase(key); } template - bool WriterInterface::push_back(const Self& value) { - return writer().push_back(value); + template + bool WriterInterface::push_back(const T& value) { + auto pushed = writer().push_back(); + return Self{pushed} << value; } } diff --git a/wayward/support/data_visitor.hpp b/wayward/support/data_visitor.hpp new file mode 100644 index 0000000..af298b6 --- /dev/null +++ b/wayward/support/data_visitor.hpp @@ -0,0 +1,114 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_DATA_VISITOR_HPP_INCLUDED +#define WAYWARD_SUPPORT_DATA_VISITOR_HPP_INCLUDED + +#include + +namespace wayward { + struct DataVisitor { + // Implement this interface: + + virtual void visit_nil() = 0; + virtual void visit_boolean(bool&) = 0; + virtual void visit_int8(std::int8_t&) = 0; + virtual void visit_int16(std::int16_t&) = 0; + virtual void visit_int32(std::int32_t&) = 0; + virtual void visit_int64(std::int64_t&) = 0; + virtual void visit_uint8(std::uint8_t&) = 0; + virtual void visit_uint16(std::uint16_t&) = 0; + virtual void visit_uint32(std::uint32_t&) = 0; + virtual void visit_uint64(std::uint64_t&) = 0; + virtual void visit_float(float&) = 0; + virtual void visit_double(double&) = 0; + virtual void visit_string(std::string&) = 0; + virtual void visit_key_value(const std::string& key, AnyRef data, const IType* type) = 0; + virtual void visit_element(std::int64_t idx, AnyRef data, const IType* type) = 0; + virtual void visit_special(AnyRef data, const IType* type) = 0; + virtual bool can_modify() const = 0; + virtual bool is_nil_at_current() const = 0; + + + // Specialize this if your data type can be visited as one of the normal methods listed above. + // If it isn't specialized, visit_special will be called. + template struct VisitCaller; + + // Call this interface from types: + + template + void visit(T& value) { + using Type = typename meta::RemoveConstRef::Type; + VisitCaller::visit(*this, value); + } + + template + void operator()(T& value) { + visit(value); + } + + template + void visit(const std::string& key, T& value) { + using Type = typename meta::RemoveConstRef::Type; + this->visit_key_value(key, AnyRef{value}, get_type()); + } + + struct KeyValueVisitor { + DataVisitor& visitor; + std::string key; + KeyValueVisitor(DataVisitor& visitor, std::string key) : visitor(visitor), key(std::move(key)) {} + template + void operator()(T& value) { + visitor.visit(key, value); + } + }; + + KeyValueVisitor operator[](const std::string& key) { + return KeyValueVisitor{*this, key}; + } + KeyValueVisitor operator[](const char* key) { + return KeyValueVisitor{*this, key}; + } + + template + void visit(std::int64_t idx, T& value) { + using Type = typename meta::RemoveConstRef::Type; + this->visit_element(idx, AnyRef{value}, get_type()); + } + + struct ElementVisitor { + DataVisitor& visitor; + std::int64_t index; + ElementVisitor(DataVisitor& visitor, std::int64_t index) : visitor(visitor), index(index) {} + template + void operator()(T& value) { + visitor.visit(index, value); + } + }; + + ElementVisitor operator[](std::int64_t index) { + return ElementVisitor{*this, index}; + } + }; + + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, NothingType) { visitor.visit_nil(); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, bool& value) { visitor.visit_boolean(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::int8_t& value) { visitor.visit_int8(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::int16_t& value) { visitor.visit_int16(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::int32_t& value) { visitor.visit_int32(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::int64_t& value) { visitor.visit_int64(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::uint8_t& value) { visitor.visit_uint8(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::uint16_t& value) { visitor.visit_uint16(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::uint32_t& value) { visitor.visit_uint32(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::uint64_t& value) { visitor.visit_uint64(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, float& value) { visitor.visit_float(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, double& value) { visitor.visit_double(value); } }; + template <> struct DataVisitor::VisitCaller { static void visit(DataVisitor& visitor, std::string& value) { visitor.visit_string(value); } }; + + template struct DataVisitor::VisitCaller { + using Type = typename meta::RemoveConstRef::Type; + static void visit(DataVisitor& visitor, T& value) { + visitor.visit_special(value, get_type()); + } + }; +} + +#endif // WAYWARD_SUPPORT_DATA_VISITOR_HPP_INCLUDED diff --git a/wayward/support/datetime/clock.cpp b/wayward/support/datetime/clock.cpp index 2d37a84..f3464cf 100644 --- a/wayward/support/datetime/clock.cpp +++ b/wayward/support/datetime/clock.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace wayward { namespace { @@ -28,17 +28,11 @@ namespace wayward { } DateTime SystemClock::now() const { - // Note: Not using std::chrono::system_clock::now(), because it doesn't understand timezones. - struct timeval tv; - struct timezone tz; - ::gettimeofday(&tv, &tz); - - Timezone timezone; - timezone.utc_offset = Seconds{-tz.tz_minuteswest * 60}; - timezone.is_dst = tz.tz_dsttime != 0; - - auto ns = std::chrono::seconds{tv.tv_sec} + std::chrono::microseconds{tv.tv_usec}; + auto n = std::chrono::system_clock::now(); + return DateTime{DateTime::Repr{n}}; + } - return DateTime{DateTime::Repr{ns}, timezone}; + Timezone SystemClock::timezone() const { + return Timezone{::tzname[0]}; } } diff --git a/wayward/support/datetime/clock.hpp b/wayward/support/datetime/clock.hpp index a1ffa94..b67c5a4 100644 --- a/wayward/support/datetime/clock.hpp +++ b/wayward/support/datetime/clock.hpp @@ -4,16 +4,19 @@ namespace wayward { struct DateTime; + struct Timezone; struct IClock { virtual ~IClock() {} virtual DateTime now() const = 0; + virtual Timezone timezone() const = 0; }; IClock& clock(); struct SystemClock : IClock { DateTime now() const final; + Timezone timezone() const final; static SystemClock& get(); private: SystemClock(); diff --git a/wayward/support/datetime/datetime.cpp b/wayward/support/datetime/datetime.cpp index fd48027..135b444 100644 --- a/wayward/support/datetime/datetime.cpp +++ b/wayward/support/datetime/datetime.cpp @@ -1,10 +1,13 @@ #include +#include #include #include #include +#include + namespace wayward { using namespace units; @@ -21,18 +24,6 @@ namespace wayward { constexpr char GetTimeUnitName::Value[]; constexpr char GetTimeUnitName::Value[]; - DateTime::Repr DateTime::r_utc() const { - auto adjusted = repr_ + timezone_.utc_offset.repr_; - if (timezone_.is_dst) { - adjusted -= Hours{1}.repr_; - } - return adjusted; - } - - DateTime DateTime::utc() const { - return DateTime{r_utc(), Timezone::UTC}; - } - DateTime DateTime::operator+(const DateTimeInterval& interval) const { DateTime copy = *this; copy += interval; @@ -61,22 +52,23 @@ namespace wayward { namespace { bool - is_leap_year(int64_t year) { + is_leap_year(Years year) { // It's leap year every 4 years, except every 100 years, but then again every 400 years. // Source: http://en.wikipedia.org/wiki/Leap_year - return ((year % 4) == 0) && (((year % 100) != 0) || ((year % 400) == 0)); + int y = year.count(); + return ((y % 4) == 0) && (((y % 100) != 0) || ((y % 400) == 0)); } - uint32_t - days_in_month(int64_t year, int64_t month) { + Days + days_in_month(Years year, Months month) { int64_t month_sign = month < 0 ? -1 : 1; month *= month_sign; // abs - year += month_sign * ((month - 1) / 12); - month = ((month - 1) % 12) + 1; + year += month_sign * ((month - 1_month) / 12); + month = ((month - 1_month) % 12) + 1_month; month *= month_sign; // undo abs - switch (month) { + switch (month.count()) { case 1: return 31; case 2: return is_leap_year(year) ? 29 : 28; case 3: return 31; @@ -96,52 +88,47 @@ namespace wayward { struct tm calendar_values_to_tm(const DateTime::CalendarValues& cal) { struct tm t = {0}; - t.tm_year = cal.year - 1900; + t.tm_year = (cal.year - 1900_years).count(); // TODO: timegm gives an error when wrapping old dates, for some weird reason - t.tm_mon = cal.month - 1; - t.tm_mday = cal.day; - - t.tm_hour = cal.hour; - t.tm_min = cal.minute; - t.tm_sec = cal.second; + t.tm_mon = (cal.month - 1_month).count(); + t.tm_mday = cal.day.count(); + t.tm_hour = cal.hour.count(); + t.tm_min = cal.minute.count(); + t.tm_sec = cal.second.count(); + t.tm_zone = const_cast(cal.timezone.zone.data()); return t; } DateTime::CalendarValues tm_to_calendar_values(const struct tm& t) { DateTime::CalendarValues cal; - cal.year = t.tm_year + 1900; - cal.month = t.tm_mon + 1; - cal.day = t.tm_mday; - cal.hour = t.tm_hour; - cal.minute = t.tm_min; - cal.second = t.tm_sec; + cal.year = Years{t.tm_year + 1900}; + cal.month = Months{t.tm_mon + 1}; + cal.day = Days{t.tm_mday}; + cal.hour = Hours{t.tm_hour}; + cal.minute = Minutes{t.tm_min}; + cal.second = Seconds{t.tm_sec}; + cal.timezone.zone = t.tm_zone; return cal; } - Nanoseconds calendar_values_to_nanoseconds_from_epoch(const DateTime::CalendarValues& cal) { - struct tm t = calendar_values_to_tm(cal); + Nanoseconds local_tm_to_utc_epoch(struct tm t) { + auto time_us = std::chrono::system_clock::from_time_t(::mktime(&t)); + return Nanoseconds{time_us.time_since_epoch()}; + } - int64_t microseconds_from_nanoseconds = cal.nanosecond / 1000; - int64_t nanoseconds = cal.nanosecond % 1000; - int64_t microseconds = cal.microsecond + microseconds_from_nanoseconds; - int64_t milliseconds_from_microseconds = microseconds / 1000; - microseconds %= 1000; - int64_t milliseconds = cal.millisecond + milliseconds_from_microseconds; - int64_t seconds_from_milliseconds = milliseconds / 1000; - t.tm_sec += seconds_from_milliseconds; - milliseconds %= 1000; - - auto time_us = std::chrono::system_clock::from_time_t(::timegm(&t)); - auto from_epoch_ns = std::chrono::nanoseconds{time_us.time_since_epoch().count() * 1000}; - from_epoch_ns += std::chrono::nanoseconds{nanoseconds}; - from_epoch_ns += std::chrono::microseconds{microseconds}; - from_epoch_ns += std::chrono::milliseconds{milliseconds}; - return from_epoch_ns; + Nanoseconds local_calendar_values_to_utc_epoch(const DateTime::CalendarValues& cal) { + // TODO: The time zone must be the local timezone, otherwise it won't work :( + struct tm t = calendar_values_to_tm(cal); + Nanoseconds ns = local_tm_to_utc_epoch(t); + ns += cal.nanosecond; + ns += cal.microsecond; + ns += cal.millisecond; + return ns; } - struct tm nanoseconds_to_tm(Nanoseconds ns) { + struct tm utc_epoch_to_tm_utc(Nanoseconds ns) { // Standard std::chrono::time_point only understands microsecond precision. auto us = ns.repr_.count() / 1000; std::chrono::time_point us_repr {std::chrono::microseconds(us)}; @@ -151,24 +138,35 @@ namespace wayward { return t; } - DateTime::CalendarValues - nanoseconds_from_epoch_to_calendar_values(Nanoseconds ns) { - DateTime::CalendarValues cal = tm_to_calendar_values(nanoseconds_to_tm(ns)); - auto ns_rem = ns.repr_.count() % 1000; + struct tm utc_epoch_to_tm_local(Nanoseconds ns) { + // Standard std::chrono::time_point only understands microsecond precision. auto us = ns.repr_.count() / 1000; - cal.millisecond = (us / 1000) % 1000; - cal.microsecond = us % 1000; - cal.nanosecond = ns_rem; + std::chrono::time_point us_repr {std::chrono::microseconds(us)}; + auto from_epoch = std::chrono::system_clock::to_time_t(us_repr); + struct tm t; + ::localtime_r(&from_epoch, &t); + return t; + } + + DateTime::CalendarValues + utc_epoch_to_local_calendar_values(Nanoseconds ns) { + struct tm t = utc_epoch_to_tm_local(ns); + auto cal = tm_to_calendar_values(t); + + cal.nanosecond = ns % 1000000000; + cal.microsecond = cal.nanosecond.count() / 1000; + cal.nanosecond %= 1000; + cal.millisecond = cal.microsecond.count() / 1000; + cal.microsecond %= 1000; return cal; } } DateTime::CalendarValues DateTime::as_calendar_values() const { - auto ns = repr_.time_since_epoch(); - return nanoseconds_from_epoch_to_calendar_values(ns); + return utc_epoch_to_local_calendar_values(repr_.time_since_epoch()); } - DateTime DateTime::at(int32_t year, int32_t month, int32_t d, int32_t h, int32_t m, int32_t s, int32_t ms, int32_t us, int32_t ns) { + DateTime DateTime::at(Timezone tz, Years year, Months month, Days d, Hours h, Minutes m, Seconds s, Milliseconds ms, Microseconds us, Nanoseconds ns) { CalendarValues cal; cal.year = year; cal.month = month; @@ -179,90 +177,87 @@ namespace wayward { cal.millisecond = ms; cal.microsecond = us; cal.nanosecond = ns; + cal.timezone = tz; return at(cal); } - DateTime DateTime::at(const CalendarValues& cal) { - return DateTime{DateTime::Repr{calendar_values_to_nanoseconds_from_epoch(cal)}}; + DateTime DateTime::at(Years year, Months month, Days d, Hours h, Minutes m, Seconds s, Milliseconds ms, Microseconds us, Nanoseconds ns) { + return at(clock().timezone(), year, month, d, h, m, s, ms, us, ns); } - DateTime DateTime::at(Timezone tz, const CalendarValues& cal) { - return DateTime{DateTime::Repr{calendar_values_to_nanoseconds_from_epoch(cal)}, tz}; + DateTime DateTime::at(const CalendarValues& cal) { + return DateTime{DateTime::Repr{local_calendar_values_to_utc_epoch(cal)}}; } Seconds DateTime::unix_timestamp() const { - auto ns = repr_.time_since_epoch().count(); - return Seconds{ns / 1000000000}; + return Seconds{repr_.time_since_epoch().count() / 1000000}; } - int32_t DateTime::year() const { + Years DateTime::year() const { auto cal = as_calendar_values(); return cal.year; } - int32_t DateTime::month() const { + Months DateTime::month() const { auto cal = as_calendar_values(); return cal.month; } - int32_t DateTime::day() const { + Days DateTime::day() const { auto cal = as_calendar_values(); return cal.day; } - int32_t DateTime::hour() const { + Hours DateTime::hour() const { auto cal = as_calendar_values(); return cal.hour; } - int32_t DateTime::minute() const { + Minutes DateTime::minute() const { auto cal = as_calendar_values(); return cal.minute; } - int32_t DateTime::second() const { + Seconds DateTime::second() const { auto cal = as_calendar_values(); return cal.second; } - int32_t DateTime::millisecond() const { + Milliseconds DateTime::millisecond() const { auto cal = as_calendar_values(); return cal.millisecond; } - int32_t DateTime::microsecond() const { + Microseconds DateTime::microsecond() const { auto cal = as_calendar_values(); return cal.microsecond; } - int32_t DateTime::nanosecond() const { + Nanoseconds DateTime::nanosecond() const { auto cal = as_calendar_values(); return cal.nanosecond; } std::string DateTime::strftime(const std::string& fmt) const { - auto tz_adjust = timezone_.utc_offset.repr_ + (timezone_.is_dst ? 1_hour : 0_hours).repr_; - struct tm t = nanoseconds_to_tm(repr_.time_since_epoch() + tz_adjust); - t.tm_isdst = timezone_.is_dst; - t.tm_gmtoff = timezone_.utc_offset.repr_.count(); + struct tm t = utc_epoch_to_tm_local(repr_.time_since_epoch()); std::array buffer; size_t len = ::strftime(buffer.data(), buffer.size(), fmt.c_str(), &t); return std::string(buffer.data(), len); } Maybe DateTime::strptime(const std::string& input, const std::string& fmt) { - struct tm t; + struct tm t = {0}; char* r = ::strptime(input.c_str(), fmt.c_str(), &t); if (r == nullptr || r == input.c_str()) { // Conversion failed. return Nothing; } else { - return DateTime::at(Timezone{Seconds(t.tm_gmtoff), t.tm_isdst != 0}, tm_to_calendar_values(t)); + return DateTime{Repr{local_tm_to_utc_epoch(t)}}; } } std::string DateTime::iso8601() const { - return strftime("%Y-%m-%d %H:%M:%S %z"); + return strftime("%Y-%m-%d %T %z"); } bool DateTimeArithmetic::is_months(const DateTimeInterval& interval) { @@ -303,7 +298,7 @@ namespace wayward { // If we're adding years from a leap year, we way end up with // an off-by-one-day error. - uint32_t dim = days_in_month(cal.year, cal.month); + Days dim = days_in_month(cal.year, cal.month); if (cal.day > dim) { cal.day = dim; } @@ -316,7 +311,7 @@ namespace wayward { cal.month += by.repr_.count(); // Don't leak into next month. - uint32_t dim = days_in_month(cal.year, cal.month); + Days dim = days_in_month(cal.year, cal.month); if (cal.day > dim) { cal.day = dim; } diff --git a/wayward/support/datetime/datetime.hpp b/wayward/support/datetime/datetime.hpp index 51b15eb..0062021 100644 --- a/wayward/support/datetime/datetime.hpp +++ b/wayward/support/datetime/datetime.hpp @@ -16,7 +16,7 @@ namespace wayward { template struct DateTimeArithmetic; /* - DateTime represents a point in time, with nanosecond precision. + DateTime represents a UTC point in time, with nanosecond precision. */ struct DateTime { using Clock = std::chrono::system_clock; @@ -24,15 +24,10 @@ namespace wayward { DateTime() {} explicit DateTime(Repr repr) : repr_(repr) {} - DateTime(Repr repr, Timezone tz) : repr_(repr), timezone_(tz) {} - operator Repr() const { return repr_; } + DateTime(const DateTime&) = default; Repr& r() { return repr_; } DateTime& operator=(const DateTime&) = default; - // Returns the same time in the UTC time zone. - DateTime utc() const; - Repr r_utc() const; - template DateTime operator+(DateTimeDuration duration) { DateTime copy = *this; @@ -61,39 +56,40 @@ namespace wayward { DateTimeInterval operator-(DateTime other) const; - bool operator==(const DateTime& other) const { return r_utc() == other.r_utc(); } - bool operator!=(const DateTime& other) const { return r_utc() != other.r_utc(); } - bool operator<(const DateTime& other) const { return r_utc() < other.r_utc(); } - bool operator>(const DateTime& other) const { return r_utc() > other.r_utc(); } - bool operator<=(const DateTime& other) const { return r_utc() <= other.r_utc(); } - bool operator>=(const DateTime& other) const { return r_utc() >= other.r_utc(); } + bool operator==(const DateTime& other) const { return repr_ == other.repr_; } + bool operator!=(const DateTime& other) const { return repr_ != other.repr_; } + bool operator<(const DateTime& other) const { return repr_ < other.repr_; } + bool operator>(const DateTime& other) const { return repr_ > other.repr_; } + bool operator<=(const DateTime& other) const { return repr_ <= other.repr_; } + bool operator>=(const DateTime& other) const { return repr_ >= other.repr_; } static DateTime now() { return clock().now(); } - int32_t year() const; - int32_t month() const; - int32_t day() const; - int32_t hour() const; - int32_t minute() const; - int32_t second() const; - int32_t millisecond() const; - int32_t microsecond() const; - int32_t nanosecond() const; + Years year() const; + Months month() const; + Days day() const; + Hours hour() const; + Minutes minute() const; + Seconds second() const; + Milliseconds millisecond() const; + Microseconds microsecond() const; + Nanoseconds nanosecond() const; Seconds unix_timestamp() const; std::string strftime(const std::string& format) const; std::string iso8601() const; struct CalendarValues { - int32_t year; - int32_t month; - int32_t day; - int32_t hour; - int32_t minute; - int32_t second; - int32_t millisecond; - int32_t microsecond; - int32_t nanosecond; + Years year; + Months month; + Days day; + Hours hour; + Minutes minute; + Seconds second; + Milliseconds millisecond; + Microseconds microsecond; + Nanoseconds nanosecond; + Timezone timezone = Timezone::UTC; }; CalendarValues as_calendar_values() const; @@ -107,15 +103,13 @@ namespace wayward { NOTE: Months/days start at 1. */ - static DateTime at(int32_t year, int32_t month, int32_t d, int32_t h, int32_t m, int32_t s, int32_t ms = 0, int32_t us = 0, int32_t ns = 0); + static DateTime at(Years year, Months month, Days d, Hours h, Minutes m, Seconds s, Milliseconds ms = 0, Microseconds us = 0, Nanoseconds ns = 0); + static DateTime at(Timezone tz, Years year, Months month, Days d, Hours h, Minutes m, Seconds s, Milliseconds ms = 0, Microseconds us = 0, Nanoseconds ns = 0); static DateTime at(const CalendarValues& calendar_values); - static DateTime at(Timezone tz, int32_t year, int32_t month, int32_t d, int32_t h, int32_t m, int32_t s, int32_t ms = 0, int32_t us = 0, int32_t ns = 0); - static DateTime at(Timezone tz, const CalendarValues& calendar_values); static Maybe strptime(const std::string& input, const std::string& format); Repr repr_; - Timezone timezone_ = Timezone::UTC; }; template <> diff --git a/wayward/support/datetime/duration_units.hpp b/wayward/support/datetime/duration_units.hpp index ac2d420..fb537b6 100644 --- a/wayward/support/datetime/duration_units.hpp +++ b/wayward/support/datetime/duration_units.hpp @@ -15,37 +15,58 @@ namespace wayward { using Repr = typename IntervalType::rep; using RatioToSeconds = typename IntervalType::period; using Self = DateTimeDuration; - explicit DateTimeDuration(Repr r) : repr_(r) {} - DateTimeDuration(IntervalType repr) : repr_(repr) {} + DateTimeDuration(Repr r = 0) : repr_(r) {} + DateTimeDuration(IntervalType interval) : repr_(interval) {} + + DateTimeDuration(const DateTimeDuration&) = default; + + template + DateTimeDuration(DateTimeDuration other) { + using OtherRatio = typename DateTimeDuration::RatioToSeconds; + static_assert(OtherRatio::den <= RatioToSeconds::den, "Converting to duration with longer denominator would lose precision."); + const auto mul = OtherRatio::num * RatioToSeconds::den; + const auto divide = OtherRatio::den * RatioToSeconds::num; + repr_ = IntervalType{other.count() * mul / divide}; + } + + bool operator==(const DateTimeDuration& other) const { return repr_ == other.repr_; } + bool operator!=(const DateTimeDuration& other) const { return repr_ != other.repr_; } + bool operator<(const DateTimeDuration& other) const { return repr_ < other.repr_; } + bool operator>(const DateTimeDuration& other) const { return repr_ > other.repr_; } + bool operator<=(const DateTimeDuration& other) const { return repr_ <= other.repr_; } + bool operator>=(const DateTimeDuration& other) const { return repr_ >= other.repr_; } + + Repr count() const { return repr_.count(); } + operator IntervalType() const { return repr_; } Self& operator=(const Self&) = default; template - auto operator+(DateTimeDuration other) -> DateTimeDuration() + other.repr_)> { + auto operator+(DateTimeDuration other) const -> DateTimeDuration() + other.repr_)> { return repr_ + other.repr_; } template - auto operator-(DateTimeDuration other) -> DateTimeDuration() - other.repr_)> { + auto operator-(DateTimeDuration other) const -> DateTimeDuration() - other.repr_)> { return repr_ - other.repr_; } template - auto operator*(T scalar) -> DateTimeDuration() * scalar)> { + auto operator*(T scalar) const -> DateTimeDuration() * scalar)> { return repr_ * scalar; } template - auto operator/(DateTimeDuration other) -> decltype(std::declval() / other.repr_) { + auto operator/(DateTimeDuration other) const -> decltype(std::declval() / other.repr_) { return repr_ / other.repr_; } template - auto operator/(T scalar) -> DateTimeDuration() / scalar)> { + auto operator/(T scalar) const -> DateTimeDuration() / scalar)> { return repr_ / scalar; } template - auto operator%(DateTimeDuration other) -> DateTimeDuration() * other.repr_)> { + auto operator%(DateTimeDuration other) const -> DateTimeDuration() * other.repr_)> { return repr_ % other.repr_; } template - auto operator%(T scalar) -> decltype(std::declval() % scalar) { + auto operator%(T scalar) const -> DateTimeDuration() % scalar)> { return repr_ % scalar; } @@ -74,9 +95,19 @@ namespace wayward { return *this; } + Self operator-() const { + return Self{-repr_}; + } + IntervalType repr_; }; + template + typename std::enable_if::value, DateTimeDuration>::type + operator*(T scalar, DateTimeDuration duration) { + return duration * scalar; + } + using Years = DateTimeDuration>>; using Months = DateTimeDuration>>; using Weeks = DateTimeDuration>>; diff --git a/wayward/support/datetime/interval.cpp b/wayward/support/datetime/interval.cpp index 01251e6..290f7ed 100644 --- a/wayward/support/datetime/interval.cpp +++ b/wayward/support/datetime/interval.cpp @@ -96,33 +96,33 @@ namespace wayward { } } - bool DateTimeInterval::operator==(const DateTimeInterval& other) const { - auto common = common_unit(*this, other); + bool operator==(const DateTimeInterval& a, const DateTimeInterval& b) { + auto common = common_unit(a, b); return common.first == common.second; } - bool DateTimeInterval::operator!=(const DateTimeInterval& other) const { - auto common = common_unit(*this, other); + bool operator!=(const DateTimeInterval& a, const DateTimeInterval& b) { + auto common = common_unit(a, b); return common.first != common.second; } - bool DateTimeInterval::operator<(const DateTimeInterval& other) const { - auto common = common_unit(*this, other); + bool operator<(const DateTimeInterval& a, const DateTimeInterval& b) { + auto common = common_unit(a, b); return common.first < common.second; } - bool DateTimeInterval::operator>(const DateTimeInterval& other) const { - auto common = common_unit(*this, other); + bool operator>(const DateTimeInterval& a, const DateTimeInterval& b) { + auto common = common_unit(a, b); return common.first > common.second; } - bool DateTimeInterval::operator<=(const DateTimeInterval& other) const { - auto common = common_unit(*this, other); + bool operator<=(const DateTimeInterval& a, const DateTimeInterval& b) { + auto common = common_unit(a, b); return common.first <= common.second; } - bool DateTimeInterval::operator>=(const DateTimeInterval& other) const { - auto common = common_unit(*this, other); + bool operator>=(const DateTimeInterval& a, const DateTimeInterval& b) { + auto common = common_unit(a, b); return common.first >= common.second; } diff --git a/wayward/support/datetime/interval.hpp b/wayward/support/datetime/interval.hpp index 1f5d9e2..287c5d3 100644 --- a/wayward/support/datetime/interval.hpp +++ b/wayward/support/datetime/interval.hpp @@ -26,13 +26,6 @@ namespace wayward { Microseconds microseconds() const; Nanoseconds nanoseconds() const; - bool operator==(const DateTimeInterval& other) const; - bool operator!=(const DateTimeInterval& other) const; - bool operator<(const DateTimeInterval& other) const; - bool operator>(const DateTimeInterval& other) const; - bool operator<=(const DateTimeInterval& other) const; - bool operator>=(const DateTimeInterval& other) const; - DateTimeInterval operator-() const { DateTimeInterval copy = *this; copy.value_ = -copy.value_; return copy; } DateTimeInterval operator+(const DateTimeInterval&) const; DateTimeInterval operator-(const DateTimeInterval&) const; @@ -64,6 +57,13 @@ namespace wayward { {} std::ostream& operator<<(std::ostream&, const DateTimeInterval&); + + bool operator==(const DateTimeInterval& a, const DateTimeInterval& b); + bool operator!=(const DateTimeInterval& a, const DateTimeInterval& b); + bool operator<(const DateTimeInterval& a, const DateTimeInterval& b); + bool operator>(const DateTimeInterval& a, const DateTimeInterval& b); + bool operator<=(const DateTimeInterval& a, const DateTimeInterval& b); + bool operator>=(const DateTimeInterval& a, const DateTimeInterval& b); } #endif // WAYWARD_SUPPORT_DATETIME_INTERVAL_HPP_INCLUDED diff --git a/wayward/support/datetime/private.hpp b/wayward/support/datetime/private.hpp index dafd114..f90b9af 100644 --- a/wayward/support/datetime/private.hpp +++ b/wayward/support/datetime/private.hpp @@ -2,6 +2,8 @@ #ifndef WAYWARD_SUPPORT_DATETIME_PRIVATE_HPP_INCLUDED #define WAYWARD_SUPPORT_DATETIME_PRIVATE_HPP_INCLUDED +#include + namespace wayward { void set_clock(IClock*); } diff --git a/wayward/support/datetime/timezone.hpp b/wayward/support/datetime/timezone.hpp index f339658..32ba7dd 100644 --- a/wayward/support/datetime/timezone.hpp +++ b/wayward/support/datetime/timezone.hpp @@ -2,16 +2,18 @@ #ifndef WAYWARD_SUPPORT_DATETIME_TIMEZONE_HPP_INCLUDED #define WAYWARD_SUPPORT_DATETIME_TIMEZONE_HPP_INCLUDED +#include + #include namespace wayward { struct Timezone { static const Timezone UTC; Timezone() {} - explicit Timezone(Seconds utc_offset, bool is_dst = false) : utc_offset(utc_offset), is_dst(is_dst) {} + Timezone(const Timezone&) = default; + explicit Timezone(std::string name) : zone(std::move(name)) {} - Seconds utc_offset = Seconds{0}; - bool is_dst = false; + std::string zone; }; } diff --git a/wayward/support/datetime/type.cpp b/wayward/support/datetime/type.cpp new file mode 100644 index 0000000..732437b --- /dev/null +++ b/wayward/support/datetime/type.cpp @@ -0,0 +1,8 @@ +#include "wayward/support/datetime/type.hpp" + +namespace wayward { + const DateTimeType* build_type(const TypeIdentifier*) { + static DateTimeType* t = new DateTimeType; + return t; + } +} diff --git a/wayward/support/datetime/type.hpp b/wayward/support/datetime/type.hpp new file mode 100644 index 0000000..3f9acfc --- /dev/null +++ b/wayward/support/datetime/type.hpp @@ -0,0 +1,20 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_DATETIME_TYPE_HPP_INCLUDED +#define WAYWARD_SUPPORT_DATETIME_TYPE_HPP_INCLUDED + +#include +#include +#include + +namespace wayward { + struct DateTimeType : DataTypeFor { + std::string name() const final { return "DateTime"; } + bool is_nullable() const final { return false; } + void visit(DateTime& value, DataVisitor& visitor) const final { return visitor(value); } + bool has_value(const wayward::DateTime&) const final { return true; } + }; + + const DateTimeType* build_type(const TypeIdentifier*); +} + +#endif // WAYWARD_SUPPORT_DATETIME_TYPE_HPP_INCLUDED diff --git a/wayward/support/either.hpp b/wayward/support/either.hpp index 502e979..b67677a 100644 --- a/wayward/support/either.hpp +++ b/wayward/support/either.hpp @@ -119,10 +119,31 @@ namespace wayward { } template - typename std::enable_if::Value < NumTypes, Maybe>::type - get() const { - if (is_a()) { - return Maybe{*memory_as()}; + typename std::enable_if::Type, Types>::Value < NumTypes, Maybe>::type + get() & { + using RawType = typename meta::RemoveConstRef::Type; + if (is_a::Type>()) { + return Maybe{ *memory_as() }; + } + return Nothing; + } + + template + typename std::enable_if::Type, Types>::Value < NumTypes, Maybe>::type + get() const & { + using RawType = typename meta::RemoveConstRef::Type; + if (is_a::Type>()) { + return Maybe{ *memory_as() }; + } + return Nothing; + } + + template + typename std::enable_if::Type, Types>::Value < NumTypes, Maybe::Type>>::type + get() && { + using RawType = typename meta::RemoveConstRef::Type; + if (is_a()) { + return Maybe(std::move(*memory_as())); } return Nothing; } diff --git a/wayward/support/event_loop_private.hpp b/wayward/support/event_loop_private.hpp index f375075..9ef9b7a 100644 --- a/wayward/support/event_loop_private.hpp +++ b/wayward/support/event_loop_private.hpp @@ -2,6 +2,8 @@ #ifndef WAYWARD_SUPPORT_EVENT_LOOP_PRIVATE_INCLUDED #define WAYWARD_SUPPORT_EVENT_LOOP_PRIVATE_INCLUDED +#include + #include namespace wayward { diff --git a/wayward/support/http.cpp b/wayward/support/http.cpp index ef835f6..924c212 100644 --- a/wayward/support/http.cpp +++ b/wayward/support/http.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include @@ -31,24 +31,19 @@ namespace wayward { return std::move(keys); } - void add_destructured_param_to_dict(MutableNode& params, std::string k, std::string v) { + void add_destructured_param_to_dict(data_franca::Object& params, std::string k, std::string v) { static const std::regex restructuralize_dictionary_keys { "^([^\\[]+)(\\[[^\\]]+\\])*$" }; MatchResults results; if (std::regex_match(k, results, restructuralize_dictionary_keys)) { auto keys = get_keys_from_destructured_param(k); - MutableNode dict; - dict.take_data(params.data()); + data_franca::Object* dict = ¶ms; for (size_t i = 0; i < keys.size(); ++i) { auto& key = keys[i]; - if (dict.type() != NodeType::Dictionary) { - dict = MutableNode::dictionary(); - } - if (i + 1 == keys.size()) { - dict[key] = std::move(v); + (*dict)[key] = std::move(v); } else { - dict.take_data(dict[key].data()); + dict = &(*dict)[key]; } } } else { @@ -112,7 +107,7 @@ namespace wayward { default: break; } - MutableNode params = MutableNode::dictionary(); + r.params = data_franca::Object::dictionary(); if (uri->query) { for (auto param = uri->query->tqh_first; param; param = param->next.tqe_next) { @@ -120,11 +115,21 @@ namespace wayward { std::string v { param->val, param->vlen }; k = URI::decode(k); v = URI::decode(v); - add_destructured_param_to_dict(params, std::move(k), std::move(v)); + add_destructured_param_to_dict(r.params, std::move(k), std::move(v)); } } - r.params.take_data(std::move(params).data()); + if (r.method == "POST" && r.body.size()) { + printf("POST BODY: %s\n", r.body.c_str()); + auto kv_pairs = split(r.body, "&"); + for (auto& p: kv_pairs) { + auto kv = split(p, "=", 2); + if (kv.size() < 2) continue; + auto k = URI::decode(kv[0]); + auto v = URI::decode(kv[1]); + add_destructured_param_to_dict(r.params, std::move(k), std::move(v)); + } + } std::string query_raw { uri->query_raw ? reinterpret_cast(uri->query_raw) : "" }; std::string fragment { uri->fragment ? reinterpret_cast(uri->fragment) : "" }; @@ -337,6 +342,17 @@ namespace wayward { { throw HTTPError(wayward::format("Invalid method: {0}", req.method)); } + + for (auto& pair: req.headers) { + auto kv = evhtp_header_new(pair.first.c_str(), pair.second.c_str(), 0, 0); // 0 means "do not copy" + evhtp_headers_add_header(r->headers_out, kv); + } + + if (req.body.size()) { + evbuffer_add(r->buffer_out, req.body.c_str(), req.body.size()); + evbuffer_add(r->buffer_out, "\n\n", 2); + } + p_->initiating_fiber = fiber::current(); p_->recorded_response = Nothing; p_->error = nullptr; diff --git a/wayward/support/http.hpp b/wayward/support/http.hpp index c985e6a..74f2b24 100644 --- a/wayward/support/http.hpp +++ b/wayward/support/http.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include namespace wayward { @@ -108,7 +108,7 @@ namespace wayward { }; using Headers = std::map; - using Params = MutableNode; + using Params = data_franca::Object; struct Request { Headers headers; diff --git a/wayward/support/json.cpp b/wayward/support/json.cpp index f83edd1..6c1dd5a 100644 --- a/wayward/support/json.cpp +++ b/wayward/support/json.cpp @@ -3,6 +3,8 @@ namespace wayward { namespace { + using namespace data_franca; + void escape_json_stream(std::ostream& os, const std::string& input) { for (auto it = input.cbegin(); it != input.cend(); it++) { switch (*it) { @@ -25,13 +27,13 @@ namespace wayward { } } - void node_to_json_stream(std::ostream& os, const Node& node, JSONMode mode, int indent = 0) { + void node_to_json_stream(std::ostream& os, const Spectator& node, JSONMode mode, int indent = 0) { switch (node.type()) { - case NodeType::Nil: { + case DataType::Nothing: { os << "null"; break; } - case NodeType::Boolean: { + case DataType::Boolean: { bool b; if (node >> b) { os << (b ? "true" : "false"); @@ -41,8 +43,8 @@ namespace wayward { } break; } - case NodeType::Integer: { - int64_t n; + case DataType::Integer: { + Integer n; if (node >> n) { os << n; } else { @@ -51,8 +53,8 @@ namespace wayward { } break; } - case NodeType::Float: { - double n; + case DataType::Real: { + Real n; if (node >> n) { os << n; } else { @@ -61,8 +63,8 @@ namespace wayward { } break; } - case NodeType::String: { - std::string str; + case DataType::String: { + String str; if (node >> str) { os << "\""; escape_json_stream(os, str); @@ -73,13 +75,15 @@ namespace wayward { } break; } - case NodeType::List: { + case DataType::List: { size_t len = node.length(); os << '['; if (mode == JSONMode::Compact) { - for (size_t i = 0; i < len; ++i) { - node_to_json_stream(os, node[i], mode); - if (i+1 != len) { + auto end = node.end(); + for (auto it = node.begin(); it != end;) { + node_to_json_stream(os, *it, mode); + ++it; + if (it != end) { os << ", "; } } @@ -87,10 +91,12 @@ namespace wayward { if (len != 0) { os << '\n'; indentation(os, indent); - for (size_t i = 0; i < len; ++i) { + auto end = node.end(); + for (auto it = node.begin(); it != end;) { indentation(os, 1); - node_to_json_stream(os, node[i], mode, indent+1); - if (i+1 != len) { + node_to_json_stream(os, *it, mode, indent+1); + ++it; + if (it != end) { os << ','; } os << '\n'; @@ -101,31 +107,32 @@ namespace wayward { os << ']'; break; } - case NodeType::Dictionary: { - auto keys = node.keys(); - size_t len = keys.size(); + case DataType::Dictionary: { os << '{'; if (mode == JSONMode::Compact) { - for (auto it = keys.begin(); it != keys.end(); ++it) { + auto end = node.end(); + for (auto it = node.begin(); it != end;) { os << "\""; - escape_json_stream(os, *it); + escape_json_stream(os, *it.key()); os << "\": "; - node_to_json_stream(os, node[*it], mode); - if (it+1 != keys.end()) { + node_to_json_stream(os, *it, mode); + ++it; + if (it != end) { os << ", "; } } } else { - if (len != 0) { + if (node.length() != 0) { indentation(os, indent); - for (auto it = keys.begin(); it != keys.end(); ++it) { + auto end = node.end(); + for (auto it = node.begin(); it != end;) { indentation(os, 1); - os << "\""; - escape_json_stream(os, *it); + escape_json_stream(os, *it.key()); os << "\": "; - node_to_json_stream(os, node[*it], mode, indent+1); - if (it+1 != keys.end()) { - os << ','; + node_to_json_stream(os, *it, mode, indent+1); + ++it; + if (it != end) { + os << ", "; } os << '\n'; indentation(os, indent); @@ -145,7 +152,7 @@ namespace wayward { return ss.str(); } - std::string as_json(const Node& node, JSONMode mode) { + std::string as_json(const data_franca::Spectator& node, JSONMode mode) { std::stringstream ss; node_to_json_stream(ss, node, mode); return ss.str(); diff --git a/wayward/support/json.hpp b/wayward/support/json.hpp index 68cdc6f..9fdfb4f 100644 --- a/wayward/support/json.hpp +++ b/wayward/support/json.hpp @@ -2,7 +2,8 @@ #ifndef WAYWARD_SUPPORT_JSON_HPP_INCLUDED #define WAYWARD_SUPPORT_JSON_HPP_INCLUDED -#include +#include +#include #include namespace wayward { @@ -16,7 +17,7 @@ namespace wayward { }; std::string escape_json(const std::string& input); - std::string as_json(const Node& data, JSONMode mode = JSONMode::Compact); + std::string as_json(const data_franca::Spectator& data, JSONMode mode = JSONMode::Compact); } #endif // WAYWARD_SUPPORT_JSON_HPP_INCLUDED diff --git a/wayward/support/maybe.hpp b/wayward/support/maybe.hpp index 7439ede..79386c6 100644 --- a/wayward/support/maybe.hpp +++ b/wayward/support/maybe.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace wayward { struct EmptyMaybeDereference : Error { @@ -18,13 +19,13 @@ namespace wayward { /* NOTE: This is equivalent to std::optional/boost::optional, but we must not introduce dependencies! */ - template + template class Maybe { public: constexpr Maybe() {} Maybe(T value) : has_value_(true) { new(&storage_) T(std::move(value)); } Maybe(const Maybe& other); - Maybe(Maybe&& other) : has_value_(false) { swap(other); } + Maybe(Maybe&& other) : has_value_(other.has_value_) { if (has_value_) new(unsafe_get()) T(std::move(*other.unsafe_get())); } constexpr Maybe(NothingType) : has_value_(false) {} ~Maybe(); Maybe& operator=(T value); @@ -44,39 +45,110 @@ namespace wayward { T& operator*() { return *get(); } void swap(Maybe& other); + + T* unsafe_get() { return reinterpret_cast(&storage_); } + const T* unsafe_get() const { return reinterpret_cast(&storage_); } private: typename std::aligned_storage::type storage_; bool has_value_ = false; }; + template + class Maybe { + public: + constexpr Maybe() : ref_(std::ref(*reinterpret_cast((void*)0))) {} + Maybe(T& value) : ref_(std::ref(value)), has_value_(true) {} + Maybe(const Maybe&) = default; + constexpr Maybe(NothingType) : ref_(std::ref(*reinterpret_cast((void*)0))), has_value_(false) {} + ~Maybe() {} + Maybe& operator=(std::reference_wrapper ref) { ref_ = ref; has_value_ = true; return *this; } + Maybe& operator=(const Maybe& value) = default; + + T* get() { if (has_value_) return &ref_.get(); throw EmptyMaybeDereference(); } + const T* get() const { if (has_value_) return &ref_.get(); throw EmptyMaybeDereference(); } + + bool operator==(NothingType) const { return !has_value_; } + bool operator!=(NothingType) const { return has_value_; } + + explicit operator bool() const { return has_value_; } + const T* operator->() const { return get(); } + const T& operator*() const { return ref_; } + T* operator->() { return get(); } + T& operator*() { return ref_; } + + void swap(Maybe& other) { std::swap(ref_, other.ref_); std::swap(has_value_, other.has_value_); } + + T* unsafe_get() { return &ref_.get(); } + const T* unsafe_get() const { return &ref_.get(); } + private: + std::reference_wrapper ref_; + bool has_value_ = false; + }; + + /* + Specialize for pointer-like types — in case of a nullable pointer, we treat null as + Nothing. + */ + template + class Maybe::Value>::type> { + public: + constexpr Maybe() : ptr_{nullptr} {} + Maybe(T value) : ptr_(std::move(value)) {} + Maybe(Maybe&& other) = default; + Maybe(const Maybe& other) = default; + constexpr Maybe(NothingType) : ptr_{nullptr} {} + ~Maybe() {} + Maybe& operator=(const Maybe& value) = default; + Maybe& operator=(Maybe&& value) = default; + + T* get() { if (ptr_) return &ptr_; throw EmptyMaybeDereference(); } + const T* get() const { if (ptr_) return &ptr_; throw EmptyMaybeDereference(); } + + bool operator==(NothingType) const { return ptr_ == nullptr; } + bool operator!=(NothingType) const { return ptr_ != nullptr; } + + explicit operator bool() const { return (bool)ptr_; } + const T* operator->() const { return get(); } + const T& operator*() const { return ptr_; } + T* operator->() { return get(); } + T& operator*() { return ptr_; } + + void swap(Maybe& other) { std::swap(ptr_, other.ptr_); } + + T* unsafe_get() { return &ptr_; } + const T* unsafe_get() const { return &ptr_; } + private: + T ptr_; + }; + template - void when_maybe(const Maybe& m, F then) { + void when_maybe(const Maybe& m, F&& then) { if (m) { then(*m); } } template - void when_maybe(Maybe& m, F then) { + void when_maybe(Maybe& m, F&& then) { if (m) { then(*m); } } - template - Maybe::Maybe(const Maybe& other) : has_value_(other.has_value_) { + template + Maybe::Maybe(const Maybe& other) : has_value_(other.has_value_) { if (has_value_) { new(get()) T(*other); } } - template - Maybe::~Maybe() { + template + Maybe::~Maybe() { if (has_value_) get()->~T(); } - template - Maybe& Maybe::operator=(T value) { + template + Maybe& Maybe::operator=(T value) { if (has_value_) { *get() = std::move(value); } else { @@ -86,21 +158,21 @@ namespace wayward { return *this; } - template - Maybe& Maybe::operator=(Maybe&& value) { + template + Maybe& Maybe::operator=(Maybe&& value) { swap(value); return *this; } - template - Maybe& Maybe::operator=(const Maybe& value) { + template + Maybe& Maybe::operator=(const Maybe& value) { Maybe copy = value; swap(copy); return *this; } - template - void Maybe::swap(Maybe& other) { + template + void Maybe::swap(Maybe& other) { if (has_value_) { if (other.has_value_) { std::swap(*get(), *other.get()); @@ -132,16 +204,35 @@ namespace wayward { template struct Join> { using Type = Maybe; }; + template <> struct Join> { + using Type = void; + }; - template - struct Bind> { + template struct BindMaybeReturnWrapper; + template struct BindMaybeReturnWrapper { + template + static auto wrap(Maybe m, F&& f) -> void { + if (m) { + f(*m); + } + } + }; + template struct BindMaybeReturnWrapper { template - static auto bind(Maybe m, F f) -> typename Join>::Type { + static auto wrap(Maybe m, F&& f) -> RT { if (m) { return f(*m); - } else { - return Nothing; } + return Nothing; + } + }; + + template + struct Bind> { + template + static auto bind(Maybe m, F&& f) -> typename Join>::Type { + using RT = typename Join>::Type; + return BindMaybeReturnWrapper::wrap(m, std::forward(f)); } }; } diff --git a/wayward/support/meta.hpp b/wayward/support/meta.hpp index 7b0c80b..03141ec 100644 --- a/wayward/support/meta.hpp +++ b/wayward/support/meta.hpp @@ -3,9 +3,18 @@ #define WAYWARD_SUPPORT_META_HPP_INCLUDED #include +#include +#include namespace wayward { namespace meta { + struct TrueType { static const bool Value = true; }; + struct FalseType { static const bool Value = false; }; + + template struct IsPointerLike { static const bool Value = std::is_pointer::value; }; + template struct IsPointerLike> : TrueType {}; + template struct IsPointerLike> : TrueType {}; + constexpr size_t max(size_t a, size_t b) { return a < b ? b : a; } diff --git a/wayward/support/monad.hpp b/wayward/support/monad.hpp index a7b53af..82b342f 100644 --- a/wayward/support/monad.hpp +++ b/wayward/support/monad.hpp @@ -7,18 +7,37 @@ namespace wayward { template struct Join; template struct Bind; - template class MonadA, class A, template class MonadB, class B, class F> - auto lift(MonadA a, MonadB b, F f) -> MonadB(), std::declval()))> { - return Bind>::bind(a, [&](A a_) { - return Bind>::bind(b, [&](B b_) { + template struct Join { + using Type = T; + }; + + template class MonadA, class A, template class MonadB, class B, class F, class... Rest> + auto lift(MonadA a, MonadB b, F f) -> MonadB(), std::declval())), Rest...> { + return Bind>::bind(std::forward>(a), [&](A a_) { + return Bind>::bind(std::forward>(b), [&](B b_) { return f(a_, b_); }); }); } - template class Monad, class A, class F> - auto fmap(Monad a, F&& f) -> typename Join()))>>::Type { - return Bind>::bind(std::move(a), std::forward(f)); + template class Monad, class A, class F, class... Rest> + auto fmap(Monad&& a, F&& f) -> typename Join())), Rest...>>::Type { + return Bind>::bind(std::forward>(a), std::forward(f)); + } + + template class Monad, class A, class F, class... Rest> + auto fmap(Monad& a, F&& f) -> typename Join())), Rest...>>::Type { + return Bind>::bind(std::forward>(a), std::forward(f)); + } + + template class Monad, class A, class F, class... Rest> + auto fmap(const Monad& a, F&& f) -> typename Join())), Rest...>>::Type { + return Bind>::bind(std::forward>(a), std::forward(f)); + } + + template + auto fmap(Value&& value, F&& f) -> decltype(Bind::bind(std::forward(value), std::forward(f))) { + return Bind::bind(std::forward(value), std::forward(f)); } } } diff --git a/wayward/support/mutable_node.cpp b/wayward/support/mutable_node.cpp deleted file mode 100644 index e572da8..0000000 --- a/wayward/support/mutable_node.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include "wayward/support/mutable_node.hpp" - -namespace wayward { - NodeType MutableNode::Data::type() const { - // XXX: A little bit fragile. - switch (value_.which()) { - case 0: return NodeType::Nil; - case 1: return NodeType::Boolean; - case 2: return NodeType::Integer; - case 3: return NodeType::Float; - case 4: return NodeType::String; - case 5: return NodeType::List; - case 6: return NodeType::Dictionary; - default: assert(false); // Invalid which() - } - } - - size_t MutableNode::Data::length() const { - size_t n = 0; - value_.template when([&](const List& list) { - n = list.size(); - }); - return n; - } - - std::vector MutableNode::Data::keys() const { - std::vector kx; - value_.template when([&](const Dictionary& dict) { - kx.reserve(dict.size()); - for (auto& pair: dict) { - kx.push_back(pair.first); - } - }); - return std::move(kx); - } - - StructuredDataConstPtr MutableNode::Data::get(const std::string& str) const { - StructuredDataConstPtr ptr; - value_.template when([&](const Dictionary& dict) { - auto it = dict.find(str); - if (it != dict.end()) { - ptr = std::static_pointer_cast(it->second); - } - }); - return std::move(ptr); - } - - StructuredDataConstPtr MutableNode::Data::get(size_t idx) const { - StructuredDataConstPtr ptr; - value_.template when([&](const List& list) { - if (idx < list.size()) { - ptr = std::static_pointer_cast(list.at(idx)); - } - }); - return std::move(ptr); - } - - Maybe MutableNode::Data::get_string() const { - Maybe str; - value_.template when([&](const std::string& s) { - str = s; - }); - return std::move(str); - } - - Maybe MutableNode::Data::get_integer() const { - Maybe integer; - value_.template when([&](int64_t s) { - integer = s; - }); - return integer; - } - - Maybe MutableNode::Data::get_float() const { - Maybe number; - value_.template when([&](double s) { - number = s; - }); - return number; - } - - Maybe MutableNode::Data::get_boolean() const { - Maybe boolean; - value_.template when([&](bool b) { - boolean = b; - }); - return boolean; - } - - NodeType MutableNode::type() const { - return data_->type(); - } - - size_t MutableNode::length() const { - return data_->length(); - } - - std::vector MutableNode::keys() const { - return data_->keys(); - } - - Node MutableNode::get(const std::string& key) const { - return Node{data_->get(key)}; - } - - Node MutableNode::get(size_t idx) const { - return Node{data_->get(idx)}; - } - - Node MutableNode::operator[](const std::string& key) const { - return get(key); - } - - Node MutableNode::operator[](size_t idx) const { - return get(idx); - } - - bool MutableNode::operator>>(std::string& str) const { - return Node{*this} >> str; - } - - bool MutableNode::operator>>(int64_t& n) const { - return Node{*this} >> n; - } - - bool MutableNode::operator>>(double& d) const { - return Node{*this} >> d; - } - - bool MutableNode::operator>>(bool& b) const { - return Node{*this} >> b; - } - - MutableNode MutableNode::operator[](const std::string& key) { - if (type() != NodeType::Dictionary) { - data_->value_ = Data::Dictionary{}; - } - DataPtr ptr; - data_->value_.template when([&](Data::Dictionary& dict) { - auto it = dict.find(key); - if (it != dict.end()) { - ptr = it->second; - } else { - ptr = std::make_shared(); - dict[key] = ptr; - } - }); - if (ptr == nullptr) { - ptr = std::make_shared(); - } - return MutableNode{std::move(ptr)}; - } - - void MutableNode::push_back(MutableNode node) { - if (type() != NodeType::List) { - data_->value_ = Data::List{}; - } - data_->value_.template when([&](Data::List& list) { - list.push_back(std::move(std::move(node).data())); - }); - } -} diff --git a/wayward/support/mutable_node.hpp b/wayward/support/mutable_node.hpp deleted file mode 100644 index 7a3dab5..0000000 --- a/wayward/support/mutable_node.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once -#ifndef WAYWARD_SUPPORT_MUTABLE_NODE_HPP_INCLUDED -#define WAYWARD_SUPPORT_MUTABLE_NODE_HPP_INCLUDED - -#include -#include -#include - -namespace wayward { - struct MutableNode { - struct Data; - using DataPtr = std::shared_ptr; - - struct Data : IStructuredData { - NodeType type() const final; - - size_t length() const final; - std::vector keys() const final; - - StructuredDataConstPtr get(const std::string& str) const final; - StructuredDataConstPtr get(size_t idx) const final; - Maybe get_string() const final; - Maybe get_integer() const final; - Maybe get_float() const final; - Maybe get_boolean() const final; - - using Dictionary = std::map; - using List = std::vector; - Either value_; - - Data() : value_{Nothing} {} - }; - - /// Immutable interface (should be equivalent to Node): - - NodeType type() const; - size_t length() const; - std::vector keys() const; - Node get(const std::string& key) const; - Node get(size_t idx) const; - Node operator[](const std::string& key) const; - Node operator[](size_t idx) const; - explicit operator bool() const; - - bool operator>>(std::string& str) const; - bool operator>>(int64_t& n) const; - bool operator>>(double& n) const; - bool operator>>(bool& b) const; - - std::string to_string() const { return Node{*this}.to_string(); } - - operator Node() const { return Node{data()}; } - - /// Mutable interface: - MutableNode(NothingType = Nothing) : data_{new Data} {} - MutableNode(std::string str) : MutableNode() { data_->value_ = std::move(str); } - MutableNode(const char* str) : MutableNode() { data_->value_ = std::string{str}; } - MutableNode(int n) : MutableNode() { data_->value_ = (int64_t)n; } - MutableNode(int64_t n) : MutableNode() { data_->value_ = n; } - MutableNode(double d) : MutableNode() { data_->value_ = d; } - MutableNode(bool b) : MutableNode() { data_->value_ = b; } - static MutableNode dictionary() { MutableNode node; node.data_->value_ = Data::Dictionary{}; return std::move(node); } - static MutableNode list() { MutableNode node; node.data_->value_ = Data::List{}; return std::move(node); } - - // Copy, move: - MutableNode(const MutableNode& other) : MutableNode() { data_->value_ = other.data_->value_; } - MutableNode(MutableNode&& other) : data_(std::move(other.data_)) {} - MutableNode& operator=(const MutableNode& other) { data_->value_ = other.data_->value_; return *this; } - MutableNode& operator=(MutableNode&& other) { data_->value_ = std::move(other.data_->value_); return *this; } - - // Coerces the type to Dictionary: - MutableNode operator[](const std::string& key); - - // Coerces the type to List: - void push_back(MutableNode node); - - DataPtr data() & { return data_; } - DataPtr data() && { return std::move(data_); } - std::shared_ptr data() const& { return std::static_pointer_cast(data_); } - - MutableNode(DataPtr data) : data_{std::move(data)} {} - void take_data(DataPtr new_ptr) { data_ = std::move(new_ptr); } - private: - DataPtr data_; - }; - - template <> struct GetStructuredDataAdapter { - static StructuredDataConstPtr get(const MutableNode& node) { - return node.data(); - } - }; - template <> struct GetStructuredDataAdapter { - static StructuredDataConstPtr get(MutableNode&& node) { - return std::static_pointer_cast(node.data()); - } - }; - template <> struct GetStructuredDataAdapter { - static StructuredDataConstPtr get(MutableNode node) { - return std::static_pointer_cast(std::move(node).data()); - } - }; - template <> struct GetStructuredDataAdapter { - static StructuredDataConstPtr get(MutableNode& node) { - return std::static_pointer_cast(node.data()); - } - }; -} - -#endif // WAYWARD_SUPPORT_MUTABLE_NODE_HPP_INCLUDED diff --git a/wayward/support/node.cpp b/wayward/support/node.cpp deleted file mode 100644 index 3e2c005..0000000 --- a/wayward/support/node.cpp +++ /dev/null @@ -1,258 +0,0 @@ -#include - -#include // For strtol, strtod -#include - -namespace wayward { - namespace { - bool str2int64_base10(const std::string& input, int64_t& out) { - if (input.size() == 0) { - return false; - } - - char* endp; - errno = 0; - out = ::strtoll(input.c_str(), &endp, 10); - if (errno != 0) { - return false; - } - if (endp == input.c_str() + input.size()) { - return true; - } - return false; - } - - bool str2double(const std::string& input, double& out) { - if (input.size() == 0) { - return false; - } - - char* endp; - errno = 0; - out = ::strtod(input.c_str(), &endp); - if (errno != 0) { - return false; - } - if (endp == input.c_str() + input.size()) { - return true; - } - return false; - } - - template - bool coerce_scalar_node_to_string(const Ptr& ptr, std::string& str) { - if (ptr) { - switch (ptr->type()) { - case NodeType::Integer: { - std::stringstream ss; - ss << *ptr->get_integer(); - str = ss.str(); - return true; - } - case NodeType::Float: { - std::stringstream ss; - ss << *ptr->get_float(); - str = ss.str(); - return true; - } - case NodeType::String: { - str = *ptr->get_string(); - return true; - } - default: return false; - } - } - return false; - } - - template - bool coerce_scalar_node_to_integer(const Ptr& ptr, int64_t& n) { - if (ptr) { - switch (ptr->type()) { - case NodeType::Integer: { - n = *ptr->get_integer(); - return true; - } - case NodeType::Float: { - n = *ptr->get_float(); - return true; - } - case NodeType::String: { - return str2int64_base10(*ptr->get_string(), n); - } - default: return false; - } - } - return false; - } - - template - bool coerce_scalar_node_to_float(const Ptr& ptr, double& n) { - if (ptr) { - switch (ptr->type()) { - case NodeType::Integer: { - n = *ptr->get_integer(); - return true; - } - case NodeType::Float: { - n = *ptr->get_float(); - return true; - } - case NodeType::String: { - return str2double(*ptr->get_string(), n); - } - default: - return false; - } - } - return false; - } - - template - bool coerce_scalar_node_to_boolean(const Ptr& ptr, bool& b) { - if (ptr) { - switch (ptr->type()) { - case NodeType::Boolean: { - b = *ptr->get_boolean(); - return true; - } - case NodeType::String: { - std::string str = *ptr->get_string(); - if (str == "true") { - b = true; - return true; - } else if (str == "false") { - b = false; - return true; - } else { - return false; - } - } - default: return false; - } - } - return false; - } - - std::string node_to_string_representation(const Node& node) { - switch (node.type()) { - case NodeType::Nil: { - return "nil"; - } - case NodeType::Boolean: { - bool b = false; - node >> b; - return b ? "true" : "false"; - } - case NodeType::Integer: { - std::string str; - node >> str; - return std::move(str); - } - case NodeType::Float: { - std::string str; - node >> str; - return std::move(str); - } - case NodeType::String: { - std::string str; - node >> str; - return str; - } - case NodeType::List: { - std::stringstream ss; - ss << '['; - for (size_t i = 0; i < node.length(); ++i) { - auto n = node[i]; - if (n.type() == NodeType::String) { - ss << "\""; - ss << n.to_string(); - ss << "\""; - } else { - ss << n.to_string(); - } - if (i + 1 != node.length()) { - ss << ", "; - } - } - ss << ']'; - return ss.str(); - } - case NodeType::Dictionary: { - std::stringstream ss; - ss << '{'; - auto kx = node.keys(); - for (size_t i = 0; i < kx.size(); ++i) { - auto n = node[kx[i]]; - ss << kx[i] << ": "; - if (n.type() == NodeType::String) { - ss << "\""; - ss << n.to_string(); - ss << "\""; - } else { - ss << n.to_string(); - } - if (i + 1 != kx.size()) { - ss << ", "; - } - } - ss << '}'; - return ss.str(); - } - } - } - - bool is_node_truthy(const Node& node) { - auto t = node.type(); - if (t == NodeType::Nil) - return false; - if (t == NodeType::Boolean) - return *node.ptr_->get_boolean(); - return true; - } - } - - NodeType Node::type() const { - return ptr_ ? ptr_->type() : NodeType::Nil; - } - - size_t Node::length() const { - return ptr_ ? ptr_->length() : 0; - } - - std::vector Node::keys() const { - return ptr_ ? ptr_->keys() : std::vector{}; - } - - Node Node::operator[](const std::string& key) const { - return ptr_ ? Node{ptr_->get(key)} : Node{}; - } - - Node Node::operator[](size_t idx) const { - return ptr_ ? Node{ptr_->get(idx)} : Node{}; - } - - Node::operator bool() const { - return is_node_truthy(*this); - } - - bool Node::operator>>(std::string& str) const { - return coerce_scalar_node_to_string(ptr_, str); - } - - bool Node::operator>>(std::int64_t& n) const { - return coerce_scalar_node_to_integer(ptr_, n); - } - - bool Node::operator>>(double& n) const { - return coerce_scalar_node_to_float(ptr_, n); - } - - bool Node::operator>>(bool& b) const { - return coerce_scalar_node_to_boolean(ptr_, b); - } - - std::string Node::to_string() const { - return node_to_string_representation(*this); - } -} diff --git a/wayward/support/node.hpp b/wayward/support/node.hpp deleted file mode 100644 index 5c326a3..0000000 --- a/wayward/support/node.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once -#ifndef WAYWARD_SUPPORT_NODE_HPP_INCLUDED -#define WAYWARD_SUPPORT_NODE_HPP_INCLUDED - -#include -#include -#include - -namespace wayward { - /* - A Node is a form of type-erasure interface that provides generic access, for instance to templating engines or serialization purposes. - - Think of it as any subtree of JSON data. - */ - struct Node { - explicit Node(StructuredDataConstPtr ptr) : ptr_(std::move(ptr)) {} - Node(NothingType) {} - Node() {} - Node(const Node&) = default; - Node(Node&&) = default; - Node& operator=(const Node&) = default; - Node& operator=(Node&&) = default; - template - Node(T&& implicitly_adaptable_data); - - NodeType type() const; - size_t length() const; - std::vector keys() const; - Node get(const std::string& key) const; - Node get(size_t idx) const; - Node operator[](const std::string& key) const; - Node operator[](size_t idx) const; - explicit operator bool() const; - - // Extractors that try their hardest to convert the data to the target type: - bool operator>>(std::string& str) const; - bool operator>>(int64_t& n) const; - bool operator>>(double& n) const; - bool operator>>(bool& b) const; - - std::string to_string() const; - - StructuredDataConstPtr ptr_; - }; - - using Dict = std::map; - - template <> struct GetStructuredDataAdapter { - static StructuredDataConstPtr get(const Node& node) { - return node.ptr_; - } - }; - - template <> struct GetStructuredDataAdapter { - static StructuredDataConstPtr get(const Node& node) { - return node.ptr_; - } - }; - - template <> struct GetStructuredDataAdapter { - static StructuredDataConstPtr get(Node&& node) { - return std::move(node.ptr_); - } - }; - - template - Node::Node(T&& implicitly_adaptable_data) - : ptr_( - std::static_pointer_cast( - GetStructuredDataAdapter::get( - std::forward(implicitly_adaptable_data) - ) - ) - ) - {} -} - -#endif // WAYWARD_SUPPORT_NODE_HPP_INCLUDED diff --git a/wayward/support/options.hpp b/wayward/support/options.hpp new file mode 100644 index 0000000..18ba8a3 --- /dev/null +++ b/wayward/support/options.hpp @@ -0,0 +1,12 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_OPTIONS_HPP_INCLUDED +#define WAYWARD_SUPPORT_OPTIONS_HPP_INCLUDED + +#include +#include + +namespace wayward { + using Options = std::map; +} + +#endif // WAYWARD_SUPPORT_OPTIONS_HPP_INCLUDED diff --git a/wayward/support/result.hpp b/wayward/support/result.hpp index ced6817..f556274 100644 --- a/wayward/support/result.hpp +++ b/wayward/support/result.hpp @@ -13,16 +13,52 @@ namespace wayward { struct Result { Result(T result) : result_{std::move(result)} {} Result(ErrorPtr error) : result_{std::move(error)} {} + Result(Result&&) = default; + Result(const Result&) = delete; + Result& operator=(Result&&) = default; + Result& operator=(const Result&) = delete; bool is_error() const { return !result_.template is_a(); } bool good() const { return !is_error(); } + explicit operator bool() const { return good(); } - T get_result() && { return std::move(result_.template get()); } - ErrorPtr get_error_ptr() && { return std::move(result_.template get()); } + T& get() & { return *result_.template get(); } + const T& get() const& { return *result_.template get(); } + T get() && { return std::move(*std::move(result_).template get()); } + + ErrorPtr& error() & { return *result_.template get(); } + const ErrorPtr& error() const& { return *result_.template get(); } + ErrorPtr error() && { return std::move(*std::move(result_).template get()); } private: Either result_; }; + struct SuccessType { constexpr SuccessType() {} }; + static const constexpr SuccessType Success; + + template <> + struct Result { + Result() {} + Result(NothingType) {} + Result(SuccessType) {} + Result(ErrorPtr error) : error_(std::move(error)) {} + + bool is_error() const { return (bool)error_; } + bool good() const { return !is_error(); } + explicit operator bool() const { return good(); } + + ErrorPtr& error() & { return error_; } + const ErrorPtr& error() const& { return error_; } + ErrorPtr error() && { return std::move(error_); } + private: + ErrorPtr error_; + }; + + template + ErrorPtr make_error(Args&&... args) { + return ErrorPtr{ new E{std::forward(args)...} }; + } + namespace monad { template struct Join>> { @@ -35,12 +71,24 @@ namespace wayward { template struct Bind> { - template - auto bind(Result result, F f) -> typename Join()))>::Type { - if (result.is_error()) { - return std::move(result).get_error_ptr(); + template + static auto bind(R&& result, F&& f) -> typename Join()))>>::Type { + if (result.good()) { + return f(std::move((std::forward(result).get()))); + } else { + return std::move((std::forward(result).error())); + } + } + }; + + template <> + struct Bind> { + template + static auto bind(R&& result, F&& f) -> typename Join>::Type { + if (result.good()) { + return f(); } else { - return f(std::move(result).get_result()); + return std::forward(result).error(); } } }; diff --git a/wayward/support/string.cpp b/wayward/support/string.cpp new file mode 100644 index 0000000..a82b094 --- /dev/null +++ b/wayward/support/string.cpp @@ -0,0 +1,349 @@ +#include "wayward/support/string.hpp" + +#include + +namespace wayward { + std::vector split(const std::string& input, const std::string& delim) { + if (delim.size() == 0) { + return {input}; + } + + std::vector result; + size_t p = 0; + size_t i = 0; + while (i < input.size() - delim.size()) { + if (input.substr(i, delim.size()) == delim) { + result.push_back(input.substr(p, i)); + p = i + delim.size(); + i = p; + } else { + ++i; + } + } + + if (p < input.size()) { + result.push_back(input.substr(p)); + } + + return std::move(result); + } + + std::vector split(const std::string& input, const std::string& delim, size_t max_groups) { + if (delim.size() == 0) { + return {input}; + } + + std::vector result; + size_t p = 0; + size_t i = 0; + while (i < input.size() - delim.size() && result.size() < max_groups) { + if (input.substr(i, delim.size()) == delim) { + result.push_back(input.substr(p, i)); + p = i + delim.size(); + i = p; + } else { + ++i; + } + } + + if (p < input.size()) { + result.push_back(input.substr(p)); + } + + return std::move(result); + } + + std::string trim(const std::string& input) { + if (input.size() == 0) + return input; + + std::string::size_type p0 = 0; + std::string::size_type p1 = input.size() - 1; + while (::isspace(input[p0])) ++p0; + while (::isspace(input[p1])) --p1; + + return input.substr(p0, p1 - p0 + 1); + } + + namespace utf8 { + size_t char_length(const char* utf8, size_t max_bytes) { + if (max_bytes) { + char ch = utf8[0]; + if (utf8::is_7bit_ascii(ch)) + return 1; + if (utf8::is_utf8_leading_byte(ch)) { + if ((ch & 0xfe) == 0xfc) return 6; + if ((ch & 0xfc) == 0xf8) return 5; + if ((ch & 0xf8) == 0xf0) return 4; + if ((ch & 0xf0) == 0xe0) return 3; + if ((ch & 0xe0) == 0xc0) return 2; + } + } + return 0; + } + + size_t count_utf8_code_points(const char* utf8, size_t max_bytes) { + size_t count = 0; + for (size_t i = 0; i < max_bytes; ++i) { + if (is_7bit_ascii(utf8[i]) || is_utf8_leading_byte(utf8[i])) + ++count; + } + return count; + } + + size_t char_count_to_byte_count(const char* utf8, size_t max_bytes, size_t chcount) { + size_t b = 0; + size_t c = 0; + while (b < max_bytes && c < chcount) { + b += char_length(utf8 + b, max_bytes - b); + ++c; + } + return b; + } + + bool is_7bit_ascii(char ch) { + return (ch & 0x80) == 0; + } + + bool is_utf8_leading_byte(char ch) { + return (ch & 0xc0) == 0xc0; + } + + bool is_utf8_byte(char ch) { + return !is_7bit_ascii(ch); + } + + bool counts_as_character(char ch) { + return ((ch & 0x80) == 0) || ((ch & 0xc0) == 0xc0); + } + + bool contains_utf8(const char* utf8, size_t max_bytes) { + for (auto p = utf8; p != utf8 + max_bytes; ++p) { + if (is_utf8_byte(*p)) return true; + } + return false; + } + + char32_t utf8_to_char32(const char* utf8, size_t len) { + if (len == 0) return 0; + const char* p = utf8; + char32_t result = 0; + + char ch = utf8[0]; + + if (is_7bit_ascii(ch)) + return ch; + + if (is_utf8_leading_byte(ch)) { + size_t num_continuation_bytes; + if ((ch & 0xfe) == 0xfc) { + num_continuation_bytes = 5; + result = ch & 0x1; + } + else if ((ch & 0xfc) == 0xf8) { + num_continuation_bytes = 4; + result = ch & 0x3; + } + else if ((ch & 0xf8) == 0xf0) { + num_continuation_bytes = 3; + result = ch & 0x7; + } + else if ((ch & 0xf0) == 0xe0) { + num_continuation_bytes = 2; + result = ch & 0xf; + } + else if ((ch & 0xe0) == 0xc0) { + num_continuation_bytes = 1; + result = ch & 0x1f; + } + else { + // Invalid UTF-8! + return '\x7f'; + } + + if (num_continuation_bytes + 1 > len) { + // Invalid UTF-8! + return '\x7f'; + } + + size_t i = 0; + do { + result <<= 6; + result |= utf8[i + 1] & 0x3f; + ++i; + } while (i < num_continuation_bytes); + + return result; + } else { + // Invalid UTF-8! + return '\x7f'; + } + } + + size_t char32_to_utf8(char32_t ch, char* p, size_t max_len) { + assert(max_len >= 6); // Not enough room to contain all characters in pre-2003 UTF-8. + + if (ch >= 0x4000000) { + p[0] = (ch >> 30) | 0xfc; + p[1] = ((ch >> 24) & 0x3f) | 0x80; + p[2] = ((ch >> 18) & 0x3f) | 0x80; + p[3] = ((ch >> 12) & 0x3f) | 0x80; + p[4] = ((ch >> 6) & 0x3f) | 0x80; + p[5] = ( ch & 0x3f) | 0x80; + return 6; + } else + if (ch >= 0x200000) { + p[0] = (ch >> 24) | 0xf8; + p[1] = ((ch >> 18) & 0x3f) | 0x80; + p[2] = ((ch >> 12) & 0x3f) | 0x80; + p[3] = ((ch >> 6) & 0x3f) | 0x80; + p[4] = ( ch & 0x3f) | 0x80; + return 5; + } else + if (ch >= 0x10000) { + p[0] = (ch >> 18) | 0xf0; + p[1] = ((ch >> 12) & 0x3f) | 0x80; + p[2] = ((ch >> 6) & 0x3f) | 0x80; + p[3] = ( ch & 0x3f) | 0x80; + return 4; + } else + if (ch >= 0x800) { + p[0] = (ch >> 12) | 0xe0; + p[1] = ((ch >> 6) & 0x3f) | 0x80; + p[2] = ( ch & 0x3f) | 0x80; + return 3; + } else + if (ch >= 0x80) { + p[0] = (ch >> 6) | 0xc0; + p[1] = (ch & 0x3f) | 0x80; + return 2; + } else { + // Plain 7-bit ASCII: + p[0] = (char)ch; + return 1; + } + } + } + + Char::Char(const char* utf8, size_t max_bytes) + : char_(utf8::utf8_to_char32(utf8, max_bytes)) + {} + + Char::Char(const char* utf8) : Char(utf8, ::strlen(utf8)) {} + + bool Char::is_ascii() const { + return char_ < 128; + } + + char32_t Char::codepoint() const { + return char_; + } + + String Char::string() const { + char buffer[6] = {0}; + String::ByteCount len = utf8::char32_to_utf8(char_, buffer, 6); + return String{buffer, len}; + } + + String::String(std::string s, bool is_ascii) : bytes_(std::move(s)), is_ascii_(is_ascii) {} + + String::String(std::string s) + : bytes_(std::move(s)) + , is_ascii_(!utf8::contains_utf8(c_str(), size())) + {} + + + String::String(CharCount count, Char ch) + : is_ascii_(ch.is_ascii()) + { + for (CharCount i = 0; i < count; ++i) { + utf8::copy_char_as_utf8(ch, std::back_inserter(bytes_)); + } + bytes_.shrink_to_fit(); + } + + String::String(const String& other, CharCount pos, CharCount characters) { + auto bpos = other.char_position_to_byte_position(pos); + if (characters != NPos) { + auto epos = other.char_position_to_byte_position(pos + characters, bpos, pos); + bytes_ = other.bytes_.substr(bpos, epos - bpos); + } else { + bytes_ = other.bytes_.substr(bpos); + } + is_ascii_ = utf8::contains_utf8(c_str(), size()); + } + + String::String(const char* utf8, ByteCount bc) { + bytes_ = std::string{utf8, (size_t)bc}; + is_ascii_ = !utf8::contains_utf8(c_str(), bc); + } + + String::String(const char* utf8) + : bytes_(utf8) + , is_ascii_(!utf8::contains_utf8(c_str(), size())) + {} + + String::String(std::initializer_list init) : is_ascii_(true) { + for (auto ch: init) { + utf8::copy_char_as_utf8(ch, std::back_inserter(bytes_)); + is_ascii_ = is_ascii_ && ch.is_ascii(); + } + bytes_.shrink_to_fit(); + } + + String::String(std::initializer_list init) + : bytes_(std::move(init)) + , is_ascii_(!utf8::contains_utf8(c_str(), size())) + {} + + String::ByteCount String::size() const { + return bytes_.size(); + } + + String::CharCount String::length() const { + if (is_ascii_) + return size(); + return utf8::count_utf8_code_points(c_str(), size()); + } + + const char* String::c_str() const { + return bytes_.c_str(); + } + + const std::string& String::bytes() const { + return bytes_; + } + + std::string& String::bytes() { + return bytes_; + } + + Char String::operator[](String::CharCount idx) const { + return at(idx); + } + + Char String::at(String::CharCount idx) const { + ByteCount pos = char_position_to_byte_position(idx); + return Char{ c_str() + pos, (size_t)(size() - pos) }; + } + + String::iterator String::begin() const { + return iterator{c_str()}; + } + + String::iterator String::end() const { + return iterator{c_str() + size()}; + } + + String::ByteCount String::char_position_to_byte_position(CharCount chpos) const { + return char_position_to_byte_position(chpos, 0, 0); + } + + String::ByteCount String::char_position_to_byte_position(CharCount chpos, ByteCount start, CharCount count_at_start) const { + return utf8::char_count_to_byte_count(c_str() + start, size() - start, chpos) + count_at_start; + } + + String operator+(const String& a, const String& b) { + return String{a.bytes_ + b.bytes_, a.is_ascii_ && b.is_ascii_}; + } +} diff --git a/wayward/support/string.hpp b/wayward/support/string.hpp new file mode 100644 index 0000000..c07b1c4 --- /dev/null +++ b/wayward/support/string.hpp @@ -0,0 +1,207 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_STRING_HPP_INCLUDED +#define WAYWARD_SUPPORT_STRING_HPP_INCLUDED + +#include +#include + +#include + +namespace wayward { + std::vector split(const std::string& input, const std::string& delimiter); + std::vector split(const std::string& input, const std::string& delimiter, size_t max_groups); + std::string trim(const std::string& input); + + struct String; + + struct Char { + Char() : char_(0) {} + Char(char32_t c) : char_(c) {} + explicit Char(const char* utf8); + Char(const char* utf8, size_t max_bytes); + + String string() const; + char32_t codepoint() const; + size_t size() const; + bool is_ascii() const; + + char32_t char_; + }; + + inline bool operator==(Char a, Char b) { return a.codepoint() == b.codepoint(); } + inline bool operator!=(Char a, Char b) { return a.codepoint() != b.codepoint(); } + inline bool operator<(Char a, Char b) { return a.codepoint() < b.codepoint(); } + inline bool operator>(Char a, Char b) { return a.codepoint() > b.codepoint(); } + inline bool operator<=(Char a, Char b) { return a.codepoint() <= b.codepoint(); } + inline bool operator>=(Char a, Char b) { return a.codepoint() >= b.codepoint(); } + + namespace utf8 { + size_t char_length(const char* utf8, size_t max_bytes); + size_t count_utf8_code_points(const char* utf8, size_t max_bytes); + size_t char_count_to_byte_count(const char* utf8, size_t max_bytes, size_t chcount); + bool is_7bit_ascii(char ch); + bool is_utf8_leading_byte(char ch); + bool is_utf8_byte(char ch); + bool contains_utf8(const char* utf8, size_t max_bytes); + bool is_valid_utf8(const char* utf8, size_t max_bytes); + char32_t utf8_to_char32(const char* utf8, size_t len); + size_t char32_to_utf8(char32_t ch, char* out_utf8, size_t max_len); + + template + void copy_char_as_utf8(Char ch, OutputIterator it) { + char utf8[6]; + size_t len = char32_to_utf8(ch.codepoint(), utf8, 6); + for (auto p = utf8; p < utf8 + len; ++p, ++it) { + *it = *p; + } + } + + template + void copy_char_as_utf8(char ch, OutputIterator it) { + *it++ = ch; + } + } + + struct InvalidUTF8Error : Error { + InvalidUTF8Error(const std::string& message) : Error(message) {} + }; + + // A wayward::String is an immutable UTF8-aware string type. + // (API-compatible with std::string, except for mutating methods). + struct String { + using ByteCount = ssize_t; + using CharCount = ssize_t; + static constexpr const auto NPos = std::string::npos; + + explicit String(std::string s); + String(CharCount count, Char ch); + String(const String&, CharCount pos, CharCount characters = NPos); + String(const char* utf8, ByteCount count); + String(const char* utf8); + String(const String&) = default; + String(String&&) noexcept = default; + String(std::initializer_list init); + String(std::initializer_list init); + + template + String(InputIterator first, InputIterator last); + + // Slimmed-down std::string interface + String& operator=(const String&) = default; + String& operator=(String&&) = default; + Char at(CharCount position) const; + Char operator[](CharCount position) const; + Char front() const; + Char back() const; + const char* data() const; + const char* c_str() const; + ByteCount size() const; + bool empty() const; + CharCount find(const String& needle, CharCount start = 0, CharCount count = NPos) const; + CharCount rfind(const String& needle, CharCount start = NPos) const; + CharCount find_first_of(const String& needle, CharCount start = 0, CharCount count = NPos) const; + CharCount find_first_not_of(const String& needle, CharCount start = 0, CharCount count = NPos) const; + CharCount find_last_of(const String& needle, CharCount start = NPos) const; + CharCount find_last_not_of(const String& needle, CharCount start = NPos) const; + + // Own interface: + const std::string& bytes() const; + std::string& bytes(); + CharCount length() const; // Number of codepoints. + struct iterator; + iterator begin() const; + iterator end() const; + bool is_ascii() const; + + String normalized_nfc() const; + String normalized_nfd() const; + String normalized_nfkc() const; + String normalized_nfkd() const; + + std::vector split(const String& delim) const; + std::vector split(const String& delim, size_t max_groups) const; + + std::string bytes_; + bool is_ascii_; + + private: + String(std::string s, bool is_ascii); + + // Allow access to private constructor to avoid rescanning strings for non-ASCII + // characters on concatenation. + friend String operator+(const String&, const String&); + + ByteCount char_position_to_byte_position(CharCount chpos) const; + ByteCount char_position_to_byte_position(CharCount chpos, ByteCount start, CharCount count_at_start) const; + }; + + String operator+(const String&, const String&); + + inline bool operator==(const String& a, const String& b) { return a.bytes_ == b.bytes_; } + inline bool operator!=(const String& a, const String& b) { return a.bytes_ != b.bytes_; } + // Warning: These are byte-level orderings. The correct way to + // sort strings for user presentation is locale-dependent. + inline bool operator<(const String& a, const String& b) { return a.bytes_ < b.bytes_; } + inline bool operator>(const String& a, const String& b) { return a.bytes_ > b.bytes_; } + inline bool operator<=(const String& a, const String& b) { return a.bytes_ <= b.bytes_; } + inline bool operator>=(const String& a, const String& b) { return a.bytes_ >= b.bytes_; } + + template + String::String(InputIterator first, InputIterator last) : is_ascii_(true) { + for (auto it = first; it != last; ++it) { + is_ascii_ = is_ascii_ && utf8::is_7bit_ascii(*it); + utf8::copy_char_as_utf8(*it, std::back_inserter(bytes_)); + } + bytes_.shrink_to_fit(); + } + + struct String::iterator { + iterator(const iterator&) = default; + iterator& operator=(const iterator&) = default; + + Char operator*() const { return Char{p_, current_char_length_}; } + iterator& operator++() { increment(); return *this; } + iterator operator++(int) { auto copy = *this; increment(); return copy; } + + bool operator==(const iterator& other) const { return p_ == other.p_; } + bool operator!=(const iterator& other) const { return p_ != other.p_; } + private: + friend struct String; + explicit iterator(const char* p) : p_(p) { update(); } + explicit iterator() : p_(nullptr), current_char_length_(0) {} + const char* p_; + size_t current_char_length_; + + void update() { + current_char_length_ = utf8::char_length(p_, 6); + } + + void increment() { + p_ += current_char_length_; + update(); + } + }; +} + +namespace std { + template <> + struct hash<::wayward::String> { + size_t operator()(const ::wayward::String& str) const { + return std::hash()(str.bytes()); + } + }; +} + +// TODO: Streams should be encoding-aware. +template +OS& operator<<(OS& os, const wayward::String& str) { + os << str.bytes(); + return os; +} +template +OS& operator<<(OS& os, const wayward::Char& c) { + os << c.string(); + return os; +} + +#endif // WAYWARD_SUPPORT_STRING_HPP_INCLUDED diff --git a/wayward/support/structured_data.hpp b/wayward/support/structured_data.hpp deleted file mode 100644 index 1f9f010..0000000 --- a/wayward/support/structured_data.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#ifndef WAYWARD_SUPPORT_STRUCTURED_DATA_HPP_INCLUDED -#define WAYWARD_SUPPORT_STRUCTURED_DATA_HPP_INCLUDED - -#include -#include -#include - -#include -#include - -namespace wayward { - using std::int64_t; - - enum class NodeType { - Nil, - Boolean, - Integer, - Float, - String, - List, - Dictionary, - }; - - struct IStructuredData; - using StructuredDataConstPtr = std::shared_ptr; - - struct IStructuredData { - virtual ~IStructuredData() {} - virtual NodeType type() const = 0; - - virtual size_t length() const = 0; - virtual std::vector keys() const = 0; - - virtual StructuredDataConstPtr get(const std::string& str) const = 0; - virtual StructuredDataConstPtr get(size_t idx) const = 0; - virtual Maybe get_string() const = 0; - virtual Maybe get_integer() const = 0; - virtual Maybe get_float() const = 0; - virtual Maybe get_boolean() const = 0; - }; - - // Default implementation deliberately left out. - template struct StructuredDataAdapter; - - template - StructuredDataConstPtr make_structured_data_adapter(T&& object) { - using Adapter = StructuredDataAdapter::Type>; - auto ptr = std::make_shared(std::forward(object)); - return std::static_pointer_cast(std::move(ptr)); - } - - template struct GetStructuredDataAdapter; - - template struct GetStructuredDataAdapter { - static auto get(T x) -> decltype(make_structured_data_adapter(x)) { - return make_structured_data_adapter(std::move(x)); - } - }; -} - -#endif // WAYWARD_SUPPORT_STRUCTURED_DATA_HPP_INCLUDED diff --git a/wayward/support/structured_data_adapters.hpp b/wayward/support/structured_data_adapters.hpp deleted file mode 100644 index 8206eea..0000000 --- a/wayward/support/structured_data_adapters.hpp +++ /dev/null @@ -1,208 +0,0 @@ -#pragma once -#ifndef WAYWARD_SUPPORT_STRUCTURED_DATA_ADAPTERS_HPP_INCLUDED -#define WAYWARD_SUPPORT_STRUCTURED_DATA_ADAPTERS_HPP_INCLUDED - -#include -#include -#include -#include - -namespace wayward { - template - struct StructuredDataAdapter> : IStructuredData { - StructuredDataAdapter(Maybe&& value) : value_(std::move(value)), access_(value_) {} - StructuredDataAdapter(const Maybe& value) : access_(value) {} - StructuredDataAdapter(Maybe& value) : access_(value) {} - - NodeType type() const final { - return access_ ? make_structured_data_adapter(*access_)->type() : NodeType::Nil; - } - - size_t length() const final { - return access_ ? make_structured_data_adapter(*access_)->length() : 0; - } - - std::vector keys() const final { - return access_ ? make_structured_data_adapter(*access_)->keys() : std::vector(); - } - - StructuredDataConstPtr get(const std::string& key) const final { - return access_ ? make_structured_data_adapter(*access_)->get(key) : nullptr; - } - - StructuredDataConstPtr get(size_t idx) const final { - return access_ ? make_structured_data_adapter(*access_)->get(idx) : nullptr; - } - - Maybe get_string() const final { - return access_ ? make_structured_data_adapter(*access_)->get_string() : Maybe(Nothing); - } - - Maybe get_integer() const final { - return access_ ? make_structured_data_adapter(*access_)->get_integer() : Maybe(Nothing); - } - - Maybe get_float() const final { - return access_ ? make_structured_data_adapter(*access_)->get_float() : Maybe(Nothing); - } - - Maybe get_boolean() const final { - return access_ ? make_structured_data_adapter(*access_)->get_boolean() : Maybe(Nothing); - } - private: - Maybe value_; - const Maybe& access_; - }; - - struct StructuredDataValue : IStructuredData { - size_t length() const override { return 0; } - std::vector keys() const override { return std::vector(); } - StructuredDataConstPtr get(const std::string&) const override { return nullptr; } - StructuredDataConstPtr get(size_t) const override { return nullptr; } - Maybe get_string() const override { return Nothing; } - Maybe get_integer() const override { return Nothing; } - Maybe get_float() const override { return Nothing; } - Maybe get_boolean() const override { return Nothing; } - }; - - template <> - struct StructuredDataAdapter : StructuredDataValue { - StructuredDataAdapter(bool b) : b_(b) {} - Maybe get_boolean() const final { return b_; } - bool b_; - }; - - struct StructuredDataStringAdapter : StructuredDataValue { - StructuredDataStringAdapter(std::string str) : string_(std::move(str)) {} - NodeType type() const final { return NodeType::String; } - Maybe get_string() const final { return string_; } - private: - std::string string_; - }; - - template <> - struct StructuredDataAdapter : StructuredDataStringAdapter { - StructuredDataAdapter(std::string value) : StructuredDataStringAdapter(std::move(value)) {} - }; - - template - struct StructuredDataAdapter : StructuredDataStringAdapter { - StructuredDataAdapter(const char* str) : StructuredDataStringAdapter(std::string(str, N ? N-1 : 0)) {} - }; - - template - struct StructuredDataAdapter : StructuredDataStringAdapter { - StructuredDataAdapter(const char* str) : StructuredDataStringAdapter(std::string(str, N ? N-1 : 0)) {} - }; - - template - struct StructuredDataAdapter : StructuredDataStringAdapter { - StructuredDataAdapter(const char* str) : StructuredDataStringAdapter(std::string(str, N ? N-1 : 0)) {} - }; - - template <> - struct StructuredDataAdapter : StructuredDataStringAdapter { - StructuredDataAdapter(const char* str) : StructuredDataStringAdapter(std::string(str)) {} - }; - - struct StructuredDataIntegerAdapter : StructuredDataValue { - StructuredDataIntegerAdapter(int64_t n) : number_(n) {} - NodeType type() const final { return NodeType::Integer; } - Maybe get_integer() const final { return number_; } - private: - int64_t number_; - }; - - // This matches all integer types, and reference/constref to all. - template - struct StructuredDataAdapter::value - >::type - > : StructuredDataIntegerAdapter { - StructuredDataAdapter(T number) : StructuredDataIntegerAdapter(number) {} - }; - - struct StructuredDataFloatAdapter : StructuredDataValue { - StructuredDataFloatAdapter(double n) : number_(n) {} - NodeType type() const final { return NodeType::Float; } - Maybe get_float() const final { return number_; } - private: - double number_; - }; - - // This matches float, double, and reference/constref to both. - template - struct StructuredDataAdapter::value - >::type - > : StructuredDataFloatAdapter { - StructuredDataAdapter(T number) : StructuredDataFloatAdapter(number) {} - }; - - template - struct StructuredDataRandomAccessListReferenceAdapter : StructuredDataValue { - using ValueType = typename T::value_type; - - StructuredDataRandomAccessListReferenceAdapter(const T& collection) : collection_(collection) {} - NodeType type() const final { return NodeType::List; } - size_t length() const final { return collection_.size(); } - StructuredDataConstPtr get(const std::string&) const final { return nullptr; } - StructuredDataConstPtr get(size_t idx) const final { - return GetStructuredDataAdapter::get(collection_.at(idx)); - } - private: - const T& collection_; - }; - - template - struct StructuredDataAdapter> : StructuredDataRandomAccessListReferenceAdapter> { - using Base = StructuredDataRandomAccessListReferenceAdapter>; - StructuredDataAdapter(std::vector&& collection) : Base(collection_), collection_(std::move(collection)) {} - StructuredDataAdapter(const std::vector& collection) : Base(collection) {} - StructuredDataAdapter(std::vector& collection) : Base(collection) {} - private: - std::vector collection_; - }; - - template - struct StructuredDataDictionaryReferenceAdapter : StructuredDataValue { - using MappedType = typename T::mapped_type; - - StructuredDataDictionaryReferenceAdapter(const T& collection) : collection_(collection) {} - NodeType type() const final { return NodeType::Dictionary; } - size_t length() const final { return collection_.size(); } - std::vector keys() const { - std::vector k; - k.reserve(length()); - for (auto& pair: collection_) { - k.push_back(pair.first); - } - return k; - } - StructuredDataConstPtr get(const std::string& key) const final { - auto it = collection_.find(key); - if (it != collection_.end()) { - return GetStructuredDataAdapter::get(it->second); - } else { - return nullptr; - } - } - StructuredDataConstPtr get(size_t idx) const final { return nullptr; } - private: - const T& collection_; - }; - - template - struct StructuredDataAdapter> : StructuredDataDictionaryReferenceAdapter> { - using Base = StructuredDataDictionaryReferenceAdapter>; - StructuredDataAdapter(std::map&& collection) : Base(collection_), collection_(std::move(collection)) {} - StructuredDataAdapter(std::map& collection) : Base(collection) {} - StructuredDataAdapter(const std::map& collection) : Base(collection) {} - private: - std::map collection_; - }; -} - -#endif // WAYWARD_SUPPORT_STRUCTURED_DATA_ADAPTERS_HPP_INCLUDED diff --git a/wayward/support/type.hpp b/wayward/support/type.hpp new file mode 100644 index 0000000..814d495 --- /dev/null +++ b/wayward/support/type.hpp @@ -0,0 +1,62 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_TYPE_HPP_INCLUDED +#define WAYWARD_SUPPORT_TYPE_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace wayward { + struct DataVisitor; + + struct TypeError : Error { + TypeError(const std::string& msg) : Error(msg) {} + }; + + struct IType { + virtual std::string name() const = 0; + virtual bool is_nullable() const = 0; + virtual const TypeInfo& type_info() const = 0; + + virtual void visit_data(AnyConstRef data, DataVisitor& visitor) const = 0; + }; + + template + struct IDataTypeFor : Base { + virtual void visit(T& value, DataVisitor& visitor) const = 0; + + virtual bool has_value(const T& value) const = 0; + }; + + template + struct DataTypeFor : IDataTypeFor { + const TypeInfo& type_info() const final { + return GetTypeInfo::Value; + } + + void visit_data(AnyConstRef data, DataVisitor& visitor) const final { + if (&data.type_info() != &this->type_info()) { + throw TypeError(format("visit_data called with data of wrong type (got {0}, expected {1}).", data.type_info().name(), this->name())); + } + // XXX: Erm.. Maybe we should find a better way to do this. + this->visit(const_cast(*data.get()), visitor); + } + }; + + template + struct TypeIdentifier { TypeIdentifier() {} }; + + // Implement this: + // const IType* build_type(const TypeIdentifier* null); + + template + auto get_type() -> decltype(build_type(std::declval::Type>*>())) { + static const TypeIdentifier::Type> type_id; + static const auto t = build_type(&type_id); + return t; + } +} + +#endif // WAYWARD_SUPPORT_TYPE_HPP_INCLUDED diff --git a/wayward/support/type_info.hpp b/wayward/support/type_info.hpp index 89adc82..68a9d3b 100644 --- a/wayward/support/type_info.hpp +++ b/wayward/support/type_info.hpp @@ -3,8 +3,12 @@ #define WAYWARD_SUPPORT_TYPE_INFO_HPP_INCLUDED #include +#include +#include namespace wayward { + std::string demangle_symbol(const std::string&); + struct TypeInfo { using Constructor = void(*)(void*); using Destructor = void(*)(void*); @@ -12,6 +16,7 @@ namespace wayward { using MoveConstructor = void(*)(void*, void*); using CopyAssign = void(*)(void*, const void*); using MoveAssign = void(*)(void*, void*); + using GetID = const std::type_info&(*)(); size_t size; size_t alignment; @@ -21,6 +26,9 @@ namespace wayward { MoveConstructor move_construct; CopyAssign copy_assign; MoveAssign move_assign; + GetID get_id; + + std::string name() const { return demangle_symbol(get_id().name()); } }; template @@ -53,6 +61,11 @@ namespace wayward { *reinterpret_cast(a) = std::move(*reinterpret_cast(b)); } + template + const std::type_info& get_id() { + return typeid(T); + } + #define DEFINE_GET_FUNCTION_IF_SUPPORTED_BY_TYPE(CHECK_SUPPORT_STRUCT, FUNCTION_NAME) \ template struct GetFunctionIfSupportedImpl_ ## FUNCTION_NAME; \ template struct GetFunctionIfSupported_ ## FUNCTION_NAME { \ @@ -86,6 +99,7 @@ namespace wayward { .move_construct = GET_FUNCTION_IF_SUPPORTED(T, move_construct), .copy_assign = GET_FUNCTION_IF_SUPPORTED(T, copy_assign), .move_assign = GET_FUNCTION_IF_SUPPORTED(T, move_assign), + .get_id = get_id, }; }; diff --git a/persistence/types.cpp b/wayward/support/types.cpp similarity index 76% rename from persistence/types.cpp rename to wayward/support/types.cpp index 42b8f7b..63b1a50 100644 --- a/persistence/types.cpp +++ b/wayward/support/types.cpp @@ -1,7 +1,11 @@ -#include -#include +#include "wayward/support/types.hpp" + +namespace wayward { + const NothingTypeType* build_type(const TypeIdentifier*) { + static const NothingTypeType* p = new NothingTypeType; + return p; + } -namespace persistence { const StringType* build_type(const TypeIdentifier*) { static const StringType* p = new StringType; return p; diff --git a/wayward/support/types.hpp b/wayward/support/types.hpp new file mode 100644 index 0000000..6e2136b --- /dev/null +++ b/wayward/support/types.hpp @@ -0,0 +1,93 @@ +#pragma once +#ifndef WAYWARD_SUPPORT_TYPES_HPP_INCLUDED +#define WAYWARD_SUPPORT_TYPES_HPP_INCLUDED + +#include +#include +#include + +namespace wayward { + struct NothingTypeType : IType { + bool is_nullable() const final { return true; } + std::string name() const final { return "NothingType"; } + const TypeInfo& type_info() const { return GetTypeInfo::Value; } + void visit_data(AnyRef data, DataVisitor& visitor) const { visitor.visit_nil(); } + void visit_data(AnyConstRef data, DataVisitor& visitor) const { visitor.visit_nil(); } + }; + const NothingTypeType* build_type(const TypeIdentifier*); + + struct StringType : DataTypeFor { + bool is_nullable() const final { return false; } + std::string name() const final { return "std::string"; } + bool has_value(const std::string& value) const final { return true; } + void visit(std::string& value, DataVisitor& visitor) const final { visitor(value); } + }; + + const StringType* build_type(const TypeIdentifier*); + + template + struct NumericType : DataTypeFor { + NumericType(std::string name) : name_(std::move(name)) {} + bool is_nullable() const final { return false; } + std::string name() const final { return name_; } + size_t bits() const { return sizeof(T)*8; } + bool is_signed() const { return std::is_signed::value; } + bool is_float() const { return std::is_floating_point::value; } + + bool has_value(const T& value) const final { return true; } + + void visit(T& value, DataVisitor& visitor) const final { + visitor(value); + } + private: + std::string name_; + }; + + const NumericType* build_type(const TypeIdentifier*); + const NumericType* build_type(const TypeIdentifier*); + const NumericType* build_type(const TypeIdentifier*); + const NumericType* build_type(const TypeIdentifier*); + const NumericType* build_type(const TypeIdentifier*); + const NumericType* build_type(const TypeIdentifier*); + + namespace detail { + std::string maybe_type_name(const IType* inner); + } + + template + struct MaybeType : DataTypeFor> { + MaybeType() {} + std::string name() const final { return detail::maybe_type_name(get_type()); } + bool is_nullable() const { return true; } + + bool has_value(const wayward::Maybe& value) const final { + return static_cast(value); + } + + void visit(Maybe& value, DataVisitor& visitor) const final { + if (visitor.can_modify()) { + if (visitor.is_nil_at_current()) { + value = Nothing; + } else { + value = T{}; + visitor(*value); + } + } else { + if (value) { + get_type()->visit_data(*value, visitor); + } else { + visitor.visit_nil(); + } + } + } + }; + + template + const MaybeType* build_type(const TypeIdentifier>*) { + static const MaybeType* p = new MaybeType{}; + return p; + } +} + +#endif // WAYWARD_SUPPORT_TYPES_HPP_INCLUDED + diff --git a/wayward/support/uri.cpp b/wayward/support/uri.cpp index 4f372a9..c8727a3 100644 --- a/wayward/support/uri.cpp +++ b/wayward/support/uri.cpp @@ -73,7 +73,11 @@ namespace wayward { continue; } } - result.push_back(input[i]); + + if (input[i] == '+') + result.push_back(' '); + else + result.push_back(input[i]); } return std::move(result); } diff --git a/wayward/synth/synth.cpp b/wayward/synth/synth.cpp index 016d6e5..4b6ebd1 100644 --- a/wayward/synth/synth.cpp +++ b/wayward/synth/synth.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -8,41 +8,62 @@ namespace ajg { namespace synth { + using namespace adapters; + + using wayward::data_franca::Spectator; + using wayward::data_franca::ReaderPtr; + using wayward::data_franca::ReaderEnumeratorPtr; + using wayward::data_franca::DataType; + using wayward::data_franca::make_reader; + using wayward::data_franca::Boolean; + using wayward::data_franca::Integer; + using wayward::data_franca::Real; + using wayward::data_franca::String; + struct AdaptedWaywardNode { - explicit AdaptedWaywardNode(const wayward::Node& node) : node(node.ptr_) {} - wayward::StructuredDataConstPtr node; + + AdaptedWaywardNode() {} + AdaptedWaywardNode(AdaptedWaywardNode&&) = default; + AdaptedWaywardNode(const AdaptedWaywardNode&) = default; + AdaptedWaywardNode& operator=(AdaptedWaywardNode&&) = default; + AdaptedWaywardNode& operator=(const AdaptedWaywardNode&) = default; + explicit AdaptedWaywardNode(Spectator node) : node(std::move(node)) {} + Spectator node; bool operator==(const AdaptedWaywardNode& other) const { if (!node || !other.node) { - return node == other.node; - } else if (node->type() == other.node->type()) { - auto t = node->type(); + return node.is_nothing() == other.node.is_nothing(); + } else if (node.type() == other.node.type()) { + auto t = node.type(); switch (t) { - case wayward::NodeType::Nil: { + case DataType::Nothing: { return true; break; } - case wayward::NodeType::Boolean: { - return *node->get_boolean() == *other.node->get_boolean(); - break; + case DataType::Boolean: { + Boolean a, b; + return (node >> a) && (other.node >> b) && a == b; } - case wayward::NodeType::Integer: { - return *node->get_integer() == *other.node->get_integer(); + case DataType::Integer: { + Integer a, b; + return (node >> a) && (other.node >> b) && a == b; break; } - case wayward::NodeType::Float: { - return *node->get_float() == *other.node->get_float(); + case DataType::Real: { + Real a, b; + return (node >> a) && (other.node >> b) && a == b; break; } - case wayward::NodeType::String: { - return *node->get_string() == *other.node->get_string(); + case DataType::String: { + String a, b; + return (node >> a) && (other.node >> b) && a == b; break; } - case wayward::NodeType::List: { + case DataType::List: { return false; break; } - case wayward::NodeType::Dictionary: { + case DataType::Dictionary: { return false; break; } @@ -53,36 +74,147 @@ namespace ajg { bool operator<(const AdaptedWaywardNode& other) const { if (node && other.node) { - return wayward::Node{node}.to_string() < wayward::Node{other.node}.to_string(); + auto t = node.type(); + if (t == other.node.type()) { + switch (t) { + case DataType::Nothing: { + return false; + } + case DataType::Boolean: { + Boolean a, b; + return (node >> a) && (other.node >> b) && a < b; + } + case DataType::Integer: { + Integer a, b; + return (node >> a) && (other.node >> b) && a < b; + break; + } + case DataType::Real: { + Real a, b; + return (node >> a) && (other.node >> b) && a < b; + break; + } + case DataType::String: { + String a, b; + return (node >> a) && (other.node >> b) && a < b; + break; + } + case DataType::List: { + // TODO: + return false; + } + case DataType::Dictionary: { + // TODO: + return false; + } + } + } else { + return node.type() < other.node.type(); + } } else { - return node < other.node; + return false; } } struct iterator { - iterator(const AdaptedWaywardNode& node, size_t idx) : node(node), idx(idx) {} - iterator(const iterator& other) = default; - bool operator==(const iterator& other) const { return &node == &other.node && idx == other.idx; } + Spectator::iterator it; + + iterator(Spectator::iterator it) : it(std::move(it)) {} + iterator(const iterator&) = default; + iterator(iterator&&) = default; + + + bool operator==(const iterator& other) const { return it == other.it; } bool operator!=(const iterator& other) const { return !(*this == other); } AdaptedWaywardNode operator*() const { - return AdaptedWaywardNode{wayward::Node{node.node}[idx]}; + return AdaptedWaywardNode{Spectator{*it}}; } - iterator& operator++() { ++idx; return *this; } - iterator operator++(int) { auto copy = *this; ++idx; return copy; } - - const AdaptedWaywardNode& node; - size_t idx; + iterator& operator++() { ++it; return *this; } + iterator operator++(int) { auto copy = *this; ++it; return copy; } }; - iterator begin() const { return iterator{*this, 0}; } - iterator end() const { return iterator{*this, wayward::Node{node}.length()}; } + iterator begin() const { return iterator{node.begin()}; } + iterator end() const { return iterator{node.end()}; } + + std::string to_string() const { + switch (node.type()) { + case DataType::Nothing: { + return ""; + } + case DataType::Boolean: { + Boolean b; + node >> b; + return b ? "true" : "false"; + } + case DataType::Integer: { + Integer n; + node >> n; + std::stringstream ss; + ss << n; + return ss.str(); + } + case DataType::Real: { + Real r; + node >> r; + std::stringstream ss; + ss << r; + return ss.str(); + } + case DataType::String: { + String s; + node >> s; + return std::move(s); + } + case DataType::List: { + std::stringstream ss; + ss << '['; + for (auto it = node.begin(); it != node.end();) { + if (it->type() == DataType::String) + ss << '"'; + ss << AdaptedWaywardNode{*it}.to_string(); + if (it->type() == DataType::String) + ss << '"'; + ++it; + if (it != node.end()) { + ss << ", "; + } + } + ss << ']'; + return ss.str(); + } + case DataType::Dictionary: { + std::stringstream ss; + ss << '{'; + for (auto it = node.begin(); it != node.end();) { + ss << *it.key(); + ss << ": "; + if (it->type() == DataType::String) + ss << '"'; + ss << AdaptedWaywardNode{*it}.to_string(); + if (it->type() == DataType::String) + ss << '"'; + ++it; + if (it != node.end()) { + ss << ", "; + } + } + ss << '}'; + return ss.str(); + } + } + } }; template OS& operator<<(OS& os, const AdaptedWaywardNode& node) { - return os << wayward::Node(node.node).to_string(); + return os << node.to_string(); + } + + template + bool operator>>(OS& os, AdaptedWaywardNode& node) { + return false; } template @@ -91,37 +223,45 @@ namespace ajg { AJG_SYNTH_ADAPTER_TYPEDEFS(Behavior); - wayward::Node node() const { return wayward::Node(this->adapted().node); } + const Spectator& node() const { return this->adapted().node; } boolean_type to_boolean() const { return (bool)node(); } // Conversion with operator bool() //datetime_type to_datetime() const { return boost::local_sec_clock::local_time(); /* TODO */ } - void output(ostream_type& out) const { out << get_string(); } + bool output(ostream_type& out) const { out << get_string(); return true; } const_iterator begin() const { return this->adapted().begin(); } const_iterator end() const { return this->adapted().end(); } - boolean_type is_boolean() const { return node().type() == wayward::NodeType::Boolean; } - boolean_type is_string() const { return node().type() == wayward::NodeType::String; } - boolean_type is_numeric() const { return node().type() == wayward::NodeType::Integer || node().type() == wayward::NodeType::Float; } + boolean_type is_boolean() const { return node().type() == DataType::Boolean; } + boolean_type is_string() const { return node().type() == DataType::String; } + boolean_type is_numeric() const { return node().type() == DataType::Integer || node().type() == DataType::Real; } optional index(const value_type& what) const { - const wayward::Node& o = node(); + auto& o = node(); // Dictionary lookup: - if (o.type() == wayward::NodeType::Dictionary) { + if (o.type() == DataType::Dictionary) { std::string key = what.to_string(); - return optional(AdaptedWaywardNode(o[key])); + return optional(AdaptedWaywardNode{o[key]}); } - if (o.type() == wayward::NodeType::List) { - size_t idx = static_cast(what.to_floating()); - return optional(AdaptedWaywardNode(o[idx])); + // List lookup: + if (o.type() == DataType::List) { + if (what.template is() || what.template is()) { + size_t idx = static_cast(what.to_floating()); + return optional(AdaptedWaywardNode{o[idx]}); + } else if (what.template is()) { + std::string key = what.to_string(); + if (key == "count" || key == "length") { + return optional(AdaptedWaywardNode{(int64_t)o.length()}); + } + } } return boost::none; } private: - std::string get_string() const { + optional get_string() const { std::string str; node() >> str; return std::move(str); @@ -134,24 +274,28 @@ namespace wayward { struct SynthTemplateEngine : ITemplateEngine { std::string template_path; - void initialize(Dict options) final { + void initialize(Options options) final { options["template_path"] >> template_path; } - std::string render(const std::string& template_name, Dict params) final { + std::string render(const std::string& template_name, Options params) final { namespace synth = ajg::synth; using Traits = synth::default_traits; using Engine = synth::engines::django::engine; using Template = synth::templates::path_template; - using Context = typename Template::context_type; + using Context = Template::context_type;//>; auto path = wayward::format("{0}/{1}", template_path, template_name); - Template templ { path, { template_path } }; - Context ctx; + Template::options_type options; + options.directories.push_back(template_path); + Template templ { path, options }; + + std::map values; for (auto& pair: params) { - ctx[pair.first] = synth::AdaptedWaywardNode{pair.second}; + values[pair.first] = synth::AdaptedWaywardNode{pair.second}; } + Context ctx { std::move(values) }; wayward::log::debug("synth", wayward::format("Rendering template: {0}", path)); diff --git a/wayward/template_engine.cpp b/wayward/template_engine.cpp index 2cc0670..abbb7a0 100644 --- a/wayward/template_engine.cpp +++ b/wayward/template_engine.cpp @@ -10,7 +10,7 @@ namespace wayward { g_template_engines[name] = func; } - std::shared_ptr template_engine(const std::string& name, Dict options) { + std::shared_ptr template_engine(const std::string& name, const Options& options) { auto it = g_template_engines.find(name); if (it != g_template_engines.end()) { auto template_engine = it->second(); @@ -21,7 +21,7 @@ namespace wayward { } } - void set_template_engine(const std::string& name, Dict options) { + void set_template_engine(const std::string& name, Options options) { g_current_engine = template_engine(name, std::move(options)); } diff --git a/wayward/template_engine.hpp b/wayward/template_engine.hpp index 78dd130..f47e2ab 100644 --- a/wayward/template_engine.hpp +++ b/wayward/template_engine.hpp @@ -2,7 +2,7 @@ #ifndef WAYWARD_TEMPLATE_ENGINE_HPP_INCLUDED #define WAYWARD_TEMPLATE_ENGINE_HPP_INCLUDED -#include +#include #include namespace wayward { @@ -15,19 +15,19 @@ namespace wayward { /* Initialize a template engine with options. */ - virtual void initialize(Dict options) = 0; + virtual void initialize(Options options) = 0; /* Render a template. */ - virtual std::string render(const std::string& template_name, Dict params) = 0; + virtual std::string render(const std::string& template_name, Options params) = 0; }; using TemplateEngineInstantiator = std::shared_ptr(*)(); void register_template_engine(std::string name, TemplateEngineInstantiator instantiator_function); - std::shared_ptr template_engine(const std::string& name, Dict options = Dict{}); - void set_template_engine(const std::string& name, Dict options = Dict{}); + std::shared_ptr template_engine(const std::string& name, const Options& options = Options{}); + void set_template_engine(const std::string& name, Options options = Options{}); std::shared_ptr current_template_engine(); } diff --git a/wayward/w.hpp b/wayward/w.hpp index 3368170..75e50f1 100644 --- a/wayward/w.hpp +++ b/wayward/w.hpp @@ -12,11 +12,12 @@ #include #include #include -#include #include #include #include +#include +#include #if !defined(WAYWARD_NO_SHORTHAND_NAMESPACE) namespace w = wayward; @@ -40,7 +41,7 @@ namespace wayward { return r; } - Response render(const std::string& template_name, Dict params = Dict{}, HTTPStatusCode code = HTTPStatusCode::OK); + Response render(const std::string& template_name, Options params = Options{}, HTTPStatusCode code = HTTPStatusCode::OK); Response redirect(std::string new_location, HTTPStatusCode code = HTTPStatusCode::Found); Response file(std::string path, Maybe content_type = Nothing); @@ -73,6 +74,7 @@ namespace wayward { R routes; routes.before(req); auto response = routes.around(req, [&](Request& r) { return (routes.*handler)(r); }); + // TODO: Make the response available to "after" filters. routes.after(req); return std::move(response); }; diff --git a/wayward_build.py b/wayward_build.py index 636a82d..64410a9 100644 --- a/wayward_build.py +++ b/wayward_build.py @@ -3,23 +3,41 @@ import copy from SCons.Script import * +opts = Variables() +opts.Add(PathVariable('PREFIX', 'Directory to install under', '/usr/local', PathVariable.PathIsDir)) + +lib_prefix = '$PREFIX/lib' +bin_prefix = '$PREFIX/bin' +inc_prefix = '$PREFIX/include' + libevent_cflags = os.popen('pkg-config --cflags libevent libevent_pthreads').read().strip() libevent_libs = os.popen('pkg-config --libs libevent libevent_pthreads').read().strip() libpq_cflags = os.popen('pkg-config --cflags libpq').read().strip() libpq_libs = os.popen('pkg-config --libs libpq').read().strip() -def WaywardLibrary(env, target, source): +def WaywardLibrary(env, target, source, headers): + opts.Update(env) if platform.system() == 'Darwin': - return env.SharedLibrary(target = target, source = source) + lib = env.SharedLibrary(target = target, source = source) elif platform.system() == 'Linux': - return env.StaticLibrary(target = target, source = source) + lib = env.StaticLibrary(target = target, source = source) + + for header in headers: + subdir = os.path.dirname(header) + path = inc_prefix + '/' + subdir + env.Install(path, header) + env.Alias('install-inc', path) + + env.Install(lib_prefix, lib) + env.Alias('install-lib', lib_prefix) + return lib _wayward_default_libs = [] def WaywardAddDefaultLibraries(targets): _wayward_default_libs.extend(targets) -def WaywardProgram(environment, target_name, source, rpaths = []): +def WaywardInternalProgram(environment, target_name, source, rpaths = []): linkflags = [] env = environment.Clone() if platform.system() == 'Darwin': @@ -39,6 +57,13 @@ def WaywardProgram(environment, target_name, source, rpaths = []): env.Append(LINKFLAGS = linkflags) return env.Program(target = target_name, source = source) +def WaywardProgram(environment, target_name, source, rpaths = []): + opts.Update(environment) + program = WaywardInternalProgram(environment, target_name, source, rpaths) + environment.Install(bin_prefix, program) + environment.Alias('install-bin', bin_prefix) + return program + def WaywardPlugin(env, target, source): env = env.Clone() if platform.system() == 'Darwin': @@ -61,7 +86,7 @@ def WaywardEnvironment(base): env.Replace(CXX = 'clang++') env.Replace(CC = 'clang') env.Append(CCFLAGS = Split('-fcolor-diagnostics')) # SCons messes with the TTY so clang can't autodetect color capability. - env.Append(CXXFLAGS = Split('-std=c++11')) + env.Append(CXXFLAGS = Split('-std=c++1y')) env.Append(CFLAGS = Split('-I/usr/local/include')) env.Append(LINKFLAGS = Split('-rpath @loader_path/')) env.Append(SHLINKFLAGS = Split('-install_name @rpath/${TARGET.file}'))