2014-12-15 01:27:03 +01:00
|
|
|
|
/*
|
|
|
|
|
|
DIFF-LANGUAGE.hpp - language to describe differences in linearised form
|
|
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2014, Hermann Vosseler <Ichthyostega@web.de>
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
**Lumiera** is free software; you can redistribute it and/or modify it
|
|
|
|
|
|
under the terms of the GNU General Public License as published by the
|
|
|
|
|
|
Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
|
|
option) any later version. See the file COPYING for further details.
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @file diff-language.hpp
|
|
|
|
|
|
** Fundamental definitions for a representation of changes.
|
2014-12-15 03:21:19 +01:00
|
|
|
|
** We describe differences in data structures or changes to be applied
|
|
|
|
|
|
** in the form of a "linearised diff language". Such a diff can be represented
|
|
|
|
|
|
** as a sequence of tokens of constant size. Using a linearised constant size
|
|
|
|
|
|
** representation allows to process diff generation and diff application in
|
|
|
|
|
|
** a pipeline, enabling maximum decoupling of sender and receiver.
|
2015-01-04 09:08:36 +01:00
|
|
|
|
** Changes sent as diff message serve as a generic meta-representation to keep
|
|
|
|
|
|
** separate and different representations of the same logical structure in sync.
|
|
|
|
|
|
** Such an architecture allows for tight cooperation between strictly separated
|
|
|
|
|
|
** components, without the need of a fixed, predefined and shared data structure.
|
2014-12-15 01:27:03 +01:00
|
|
|
|
**
|
2018-10-08 05:00:06 +02:00
|
|
|
|
** # Basic Assumptions
|
|
|
|
|
|
**
|
2014-12-15 03:21:19 +01:00
|
|
|
|
** While the \em linearisation folds knowledge about the underlying data structure
|
|
|
|
|
|
** down into the actual diff, we deliberately assume that the data to be diffed is
|
|
|
|
|
|
** \em structured data. Moreover, we'll assume implicitly that this data is \em typed,
|
|
|
|
|
|
** and we'll assume explicitly that the atomic elements in the data structure have a
|
|
|
|
|
|
** well-defined identity and can be compared with the \c == operator. We treat those
|
|
|
|
|
|
** elements as values, which can be copied and moved cheaply. We include a copy of
|
|
|
|
|
|
** all content elements right within the tokens of the diff language, either to
|
|
|
|
|
|
** send the actual content data this way, or to serve as redundancy to verify
|
2015-01-04 09:08:36 +01:00
|
|
|
|
** proper application of the changes at the diff receiver downstream.
|
2014-12-15 03:21:19 +01:00
|
|
|
|
**
|
2018-10-08 05:00:06 +02:00
|
|
|
|
** # Solution Pattern
|
|
|
|
|
|
**
|
2014-12-15 03:21:19 +01:00
|
|
|
|
** The representation of this linearised diff language relies on a specialised form
|
|
|
|
|
|
** of the <b>visitor pattern</b>: We assume the vocabulary of the diff language to be
|
|
|
|
|
|
** relatively fixed, while the actual effect when consuming the stream of diff tokens
|
|
|
|
|
|
** is provided as a private detail of the receiver, implemented as a concrete "Interpreter"
|
|
|
|
|
|
** (visitor) of the specific diff language flavour in use. Thus, our implementation relies
|
|
|
|
|
|
** on \em double-dispatch, based both on the type of the individual diff tokens and on the
|
2015-01-04 09:08:36 +01:00
|
|
|
|
** concrete implementation of the Interpreter. Typical usage will employ a DiffApplicator,
|
2014-12-15 03:21:19 +01:00
|
|
|
|
** so the "interpretation" of the language means to apply it to a target data structure in
|
|
|
|
|
|
** this standard case.
|
|
|
|
|
|
**
|
|
|
|
|
|
** Due to the nature of double-dispatch, the interpretation of each token requires two
|
|
|
|
|
|
** indirections. The first indirection forwards to a handler function corresponding to the
|
|
|
|
|
|
** token, while the second indirection uses the VTable of the concrete Interpreter to pick
|
|
|
|
|
|
** the actual implementation of this handler function for this specific case. Basically
|
|
|
|
|
|
** the individual token ("verb") in the language is characterised by the handler function
|
|
|
|
|
|
** it corresponds to (thus the meaning of a \em verb, an operation). To support diagnostics,
|
|
|
|
|
|
** each token also bears a string id. And in addition, each token carries a single data
|
|
|
|
|
|
** content element as argument. The idea is, that the "verbs", the handler functions and
|
|
|
|
|
|
** the symbolic IDs are named alike (use the macro DiffStep_CTOR to define the tokens
|
2015-01-04 09:08:36 +01:00
|
|
|
|
** in accordance to that rule). Such a combination of verb and data argument is called
|
2014-12-15 03:21:19 +01:00
|
|
|
|
** a DiffStep, since it represents a single step in the process of describing changes
|
|
|
|
|
|
** or transforming a data structure. For example, a list diff language can be built
|
|
|
|
|
|
** using the following four verbs:
|
|
|
|
|
|
** - pick-next
|
|
|
|
|
|
** - insert-new
|
|
|
|
|
|
** - delete-next
|
2015-01-04 09:08:36 +01:00
|
|
|
|
** - find reordered element
|
2014-12-15 03:21:19 +01:00
|
|
|
|
**
|
|
|
|
|
|
** @see list-diff-application.hpp
|
2014-12-15 01:27:03 +01:00
|
|
|
|
** @see diff-list-application-test.cpp
|
|
|
|
|
|
** @see VerbToken
|
|
|
|
|
|
**
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef LIB_DIFF_DIFF_LANGUAGE_H
|
|
|
|
|
|
#define LIB_DIFF_DIFF_LANGUAGE_H
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-12-15 03:21:19 +01:00
|
|
|
|
#include "lib/error.hpp"
|
2018-03-24 05:35:13 +01:00
|
|
|
|
#include "lib/nocopy.hpp"
|
2014-12-15 01:27:03 +01:00
|
|
|
|
#include "lib/verb-token.hpp"
|
2015-11-01 03:54:43 +01:00
|
|
|
|
#include "lib/util.hpp"
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
#include <tuple>
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-03-16 02:04:47 +01:00
|
|
|
|
namespace lumiera {
|
|
|
|
|
|
namespace error {
|
|
|
|
|
|
LUMIERA_ERROR_DECLARE(DIFF_STRUCTURE); ///< Invalid diff structure: implicit rules and assumptions violated.
|
|
|
|
|
|
LUMIERA_ERROR_DECLARE(DIFF_CONFLICT); ///< Collision in diff application: contents of target not as expected.
|
|
|
|
|
|
}}
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
2014-12-15 03:21:19 +01:00
|
|
|
|
namespace diff{
|
|
|
|
|
|
|
2014-12-15 01:27:03 +01:00
|
|
|
|
namespace error = lumiera::error;
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-01-04 09:26:25 +01:00
|
|
|
|
template<class I, typename E>
|
2015-10-24 02:42:13 +02:00
|
|
|
|
using HandlerFun = void (I::*) (E const&); // NOTE: element value taken by const&
|
2015-01-04 09:26:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @internal type rebinding helper */
|
|
|
|
|
|
template<class I>
|
|
|
|
|
|
struct InterpreterScheme ///< base case is to expect typedef I::Val
|
|
|
|
|
|
{
|
|
|
|
|
|
using Interpreter = I;
|
|
|
|
|
|
using Val = typename I::Val;
|
|
|
|
|
|
using Handler = HandlerFun<I,Val>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template<template<typename> class IP, typename E>
|
|
|
|
|
|
struct InterpreterScheme<IP<E>> ///< alternatively, the interpreter value type can be templated
|
|
|
|
|
|
{
|
|
|
|
|
|
using Val = E;
|
|
|
|
|
|
using Interpreter = IP<E>;
|
|
|
|
|
|
using Handler = HandlerFun<Interpreter,Val>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2015-06-04 19:26:45 +02:00
|
|
|
|
template<class I, typename E> ///< alternatively derive value and interpreter from a Handler binding
|
2015-01-04 09:26:25 +01:00
|
|
|
|
struct InterpreterScheme<HandlerFun<I,E>>
|
|
|
|
|
|
{
|
|
|
|
|
|
using Val = E;
|
|
|
|
|
|
using Interpreter = I;
|
|
|
|
|
|
using Handler = HandlerFun<I,E>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-12-15 03:21:19 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Definition frame for a language to describe differences in data structures.
|
|
|
|
|
|
* We use a \em linearised representation as a sequence of DiffStep messages
|
|
|
|
|
|
* of constant size. The actual verbs of the diff language in use are defined
|
|
|
|
|
|
* through the operations of the \em Interpreter; each #VerbToken corresponds
|
|
|
|
|
|
* to a handler function on the Interpreter interface. In addition to the verb,
|
2015-10-02 18:47:44 +02:00
|
|
|
|
* each DiffStep also carries a content data element as argument,
|
|
|
|
|
|
* like e.g. "insert elm at next position".
|
2014-12-15 03:21:19 +01:00
|
|
|
|
* @param I Interpreter interface of the actual language to use
|
|
|
|
|
|
* @param E type of the elementary data elements.
|
|
|
|
|
|
* @remarks recommendation is to set up a builder function for each distinct
|
|
|
|
|
|
* kind of verb to be used in the actual language: this #diffTokenBuilder
|
|
|
|
|
|
* takes the data element as argument and wraps a copy in the created
|
|
|
|
|
|
* DiffStep of the specific kind it is configured for.
|
|
|
|
|
|
*/
|
2014-12-15 01:27:03 +01:00
|
|
|
|
template< class I, typename E>
|
|
|
|
|
|
struct DiffLanguage
|
|
|
|
|
|
{
|
|
|
|
|
|
|
2015-10-24 02:42:13 +02:00
|
|
|
|
using DiffVerb = VerbToken<I, void(E const&)>; // NOTE: element value taken by const&
|
2015-01-03 12:52:09 +01:00
|
|
|
|
using DiffToken = std::tuple<DiffVerb, E>;
|
2015-01-03 02:37:33 +01:00
|
|
|
|
using Interpreter = I;
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
struct DiffStep
|
2015-01-03 12:52:09 +01:00
|
|
|
|
: DiffToken
|
2014-12-15 01:27:03 +01:00
|
|
|
|
{
|
|
|
|
|
|
DiffVerb& verb() { return std::get<0>(*this); }
|
2015-10-24 02:42:13 +02:00
|
|
|
|
E const& elm() { return std::get<1>(*this); }
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
DiffStep(DiffVerb verb, E e)
|
2015-01-03 12:52:09 +01:00
|
|
|
|
: DiffToken(verb,e)
|
2014-12-15 01:27:03 +01:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
operator string() const
|
|
|
|
|
|
{
|
2016-10-02 23:34:07 +02:00
|
|
|
|
return string(unConst(this)->verb())
|
|
|
|
|
|
+ "("+string(unConst(this)->elm())+")";
|
2014-12-15 01:27:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2015-01-03 02:37:33 +01:00
|
|
|
|
applyTo (Interpreter& interpreter)
|
2014-12-15 01:27:03 +01:00
|
|
|
|
{
|
2015-11-01 03:54:43 +01:00
|
|
|
|
TRACE (diff, "verb %4s(%s)", cStr(verb()), cStr(elm()) );
|
2014-12-15 01:27:03 +01:00
|
|
|
|
verb().applyTo (interpreter, elm());
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2015-01-04 12:02:41 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const DiffStep NIL;
|
2014-12-15 01:27:03 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
2015-01-04 09:08:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** generator to produce specific language tokens */
|
|
|
|
|
|
template<class I>
|
|
|
|
|
|
struct DiffStepBuilder
|
|
|
|
|
|
{
|
|
|
|
|
|
using Scheme = InterpreterScheme<I>;
|
|
|
|
|
|
using Handler = typename Scheme::Handler;
|
|
|
|
|
|
using Val = typename Scheme::Val;
|
|
|
|
|
|
|
|
|
|
|
|
using Lang = DiffLanguage<I,Val>;
|
2014-12-15 01:27:03 +01:00
|
|
|
|
using Step = typename Lang::DiffStep;
|
|
|
|
|
|
using Verb = typename Lang::DiffVerb;
|
|
|
|
|
|
|
2015-01-04 09:08:36 +01:00
|
|
|
|
Handler handler;
|
2014-12-15 01:27:03 +01:00
|
|
|
|
Literal id;
|
|
|
|
|
|
|
|
|
|
|
|
Step
|
2015-01-04 09:08:36 +01:00
|
|
|
|
operator() (Val elm) const
|
2014-12-15 01:27:03 +01:00
|
|
|
|
{
|
|
|
|
|
|
return { Verb(handler,id), elm };
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** set up a diff language token generator,
|
|
|
|
|
|
* based on the specific handler function given.
|
|
|
|
|
|
* This generator will produce tokens, wrapping concrete content elements
|
|
|
|
|
|
* of type \c E. In the end, the purpose is to send a sequence of such tokens
|
|
|
|
|
|
* around, to feed them to a consumer, which implements the \em Interpreter
|
|
|
|
|
|
* interface of the diff language. E.g. this consumer might apply the diff.
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class H>
|
2015-01-04 09:08:36 +01:00
|
|
|
|
inline DiffStepBuilder<typename InterpreterScheme<H>::Interpreter>
|
2014-12-15 01:27:03 +01:00
|
|
|
|
diffTokenBuilder (H handlerFun, Literal id)
|
|
|
|
|
|
{
|
|
|
|
|
|
return { handlerFun, id };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-01-04 12:02:41 +01:00
|
|
|
|
/** shortcut to define tokens of the diff language.
|
|
|
|
|
|
* Use it to define namespace or class level function objects, which,
|
|
|
|
|
|
* when supplied with an argument value of type \c E, will generate
|
|
|
|
|
|
* a specific language token wrapping a copy of this element.
|
|
|
|
|
|
* @see ListDiffLanguage usage example
|
|
|
|
|
|
* @note need a typedef \c Interpreter at usage site
|
|
|
|
|
|
* to refer to the actual language interpreter interface;
|
|
|
|
|
|
* the template parameters of the Language and the element
|
|
|
|
|
|
* type will be picked up from the given member function pointer.
|
|
|
|
|
|
*/
|
2014-12-15 01:27:03 +01:00
|
|
|
|
#define DiffStep_CTOR(_ID_) \
|
2015-01-04 09:08:36 +01:00
|
|
|
|
const DiffStepBuilder<Interpreter> _ID_ = diffTokenBuilder (&Interpreter::_ID_, STRINGIFY(_ID_));
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-01-04 12:02:41 +01:00
|
|
|
|
/** fixed "invalid" marker token
|
|
|
|
|
|
* @warning use for internal state marking only --
|
|
|
|
|
|
* invoking this token produces undefined behaviour */
|
|
|
|
|
|
template<class I, typename E>
|
|
|
|
|
|
const typename DiffLanguage<I,E>::DiffStep DiffLanguage<I,E>::NIL = DiffStep(DiffVerb(), E());
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-12-15 03:21:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ==== Implementation Pattern for Diff Application ==== */
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
/**
|
2014-12-15 03:21:19 +01:00
|
|
|
|
* Extension point: define how a specific diff language
|
|
|
|
|
|
* can be applied to elements in a concrete container
|
|
|
|
|
|
* @remarks the actual diff fed to the DiffApplicator
|
|
|
|
|
|
* assumes that this DiffApplicationStrategy is
|
|
|
|
|
|
* an Interpreter for the given diff language.
|
2016-07-28 22:58:21 +02:00
|
|
|
|
* @remarks the second template parameter allows for
|
|
|
|
|
|
* `std::enable_if` based on the concrete
|
|
|
|
|
|
* target type `TAR` (1st template arg)
|
2014-12-15 03:21:19 +01:00
|
|
|
|
* @warning the actual language remains unspecified;
|
|
|
|
|
|
* it is picked from the visible context.
|
2019-12-15 15:06:04 +01:00
|
|
|
|
* @see tree-diff-application.hpp
|
|
|
|
|
|
* @see list-diff-application.hpp
|
2014-12-15 01:27:03 +01:00
|
|
|
|
*/
|
2016-07-28 22:58:21 +02:00
|
|
|
|
template<class TAR, typename SEL =void>
|
2014-12-15 03:21:19 +01:00
|
|
|
|
class DiffApplicationStrategy;
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-06-04 19:26:45 +02:00
|
|
|
|
* generic builder to apply a diff description to a given target data structure.
|
2014-12-15 01:27:03 +01:00
|
|
|
|
* The usage pattern is as follows
|
2015-06-04 19:26:45 +02:00
|
|
|
|
* #. construct a DiffApplicator instance, wrapping the target data
|
|
|
|
|
|
* #. feed the diff (sequence of diff verbs) to the #consume function
|
2016-02-21 00:49:13 +01:00
|
|
|
|
* #. the wrapped target data has been altered, to conform to the given diff
|
2014-12-15 01:27:03 +01:00
|
|
|
|
* @note a suitable DiffApplicationStrategy will be picked, based on the type
|
|
|
|
|
|
* of the concrete target sequence given at construction. (Effectively
|
|
|
|
|
|
* this means you need a suitable DiffApplicationStrategy specialisation,
|
|
|
|
|
|
* e.g. for a target sequence within a vector)
|
|
|
|
|
|
*/
|
2016-02-21 00:49:13 +01:00
|
|
|
|
template<class TAR>
|
2014-12-15 01:27:03 +01:00
|
|
|
|
class DiffApplicator
|
2018-03-24 05:35:13 +01:00
|
|
|
|
: util::NonCopyable
|
2014-12-15 01:27:03 +01:00
|
|
|
|
{
|
2016-02-21 00:49:13 +01:00
|
|
|
|
using Interpreter = DiffApplicationStrategy<TAR>;
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
2014-12-15 03:21:19 +01:00
|
|
|
|
Interpreter target_;
|
2014-12-15 01:27:03 +01:00
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
explicit
|
2016-02-21 00:49:13 +01:00
|
|
|
|
DiffApplicator(TAR& targetStructure)
|
|
|
|
|
|
: target_(targetStructure)
|
2014-12-15 01:27:03 +01:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
template<class DIFF>
|
|
|
|
|
|
void
|
|
|
|
|
|
consume (DIFF&& diff)
|
|
|
|
|
|
{
|
2015-10-31 05:15:47 +01:00
|
|
|
|
target_.initDiffApplication();
|
2014-12-15 01:27:03 +01:00
|
|
|
|
for ( ; diff; ++diff )
|
|
|
|
|
|
diff->applyTo(target_);
|
2019-12-15 15:06:04 +01:00
|
|
|
|
target_.completeDiffApplication();
|
2014-12-15 01:27:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-12-15 03:21:19 +01:00
|
|
|
|
}} // namespace lib::diff
|
|
|
|
|
|
#endif /*LIB_DIFF_DIFF_LANGUAGE_H*/
|