New Core Tutorial

git-svn-id: svn://ultimatepp.org/upp/trunk@10538 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
cxl 2016-12-12 10:08:40 +00:00
parent dd88feaf2e
commit 6c22e727de
37 changed files with 3125 additions and 0 deletions

View file

@ -0,0 +1,26 @@
#include "Tutorial.h"
void AnyTutorial()
{
/// .`Any`
/// `Any` is a container that can contain none or one element of %any% type. `Any::Is`
/// method matches exact type ignoring class hierarchies (unlike `One::Is`). You can use
/// `Get` to retrieve a reference to the instance stored:
for(int pass = 0; pass < 2; pass++) {
Any x;
if(pass)
x.Create<String>() = "Hello!";
else
x.Create<Color>() = Blue();
if(x.Is<String>())
LOG("Any is now String: " << x.Get<String>());
if(x.Is<Color>())
LOG("Any is now Color: " << x.Get<Color>());
}
///
}

View file

@ -0,0 +1,25 @@
#include "Tutorial.h"
void ArrayTutorial()
{
/// .Array flavor
/// If elements are not `Moveable` and therefore cannot be stored in `Vector` flavor, they
/// can still be stored in `Array` flavor. Another reason for using Array is the need for
/// referencing elements - Array flavor never invalidates references or pointers to them.
/// Finally, if sizeof(T) is large (say more than 100-200 bytes), using Array might be better
/// from performance perspective.
/// Example of elements that cannot be stored in Vector flavor are standard library objects like
/// `std::string` (because obviously, standard library knows nothing about U++ Moveable
/// concept):
Array<std::string> as;
for(int i = 0; i < 4; i++)
as.Add("Test");
for(auto s : as)
DUMP(s.c_str());
///
}

View file

@ -0,0 +1,82 @@
#include "Tutorial.h"
void AsStringTutorial()
{
/// .`AsString`, `ToString` and `operator<<`
/// U++ Core provides simple yet effective standard schema for converting values to default
/// textual form. System is based on the combination of template functions (following code
/// is part of U++ library):
#if 0
namespace Upp {
template <class T>
inline String AsString(const T& x)
{
return x.ToString();
}
template <class T>
inline Stream& operator<<(Stream& s, const T& x)
{
s << AsString(x);
return s;
}
template <class T>
inline String& operator<<(String& s, const T& x)
{
s.Cat(AsString(x));
return s;
}
};
#endif
/// Client types have to either define `String ToString` method or specialize `AsString`
/// template in `Upp` namespace. Such types can be appended to Streams or Strings using
/// `operator<<`. Of course, U++ value types and primitive types have required items
/// predefined by U++:
FileOut fout(ConfigFile("test.txt"));
String sout;
fout << 1.23 << ' ' << GetSysDate() << ' ' << GetSysTime();
sout << 1.23 << ' ' << GetSysDate() << ' ' << GetSysTime();
fout.Close();
DUMP(LoadFile(ConfigFile("test.txt")));
DUMP(sout);
/// Getting client types involved into this schema is not too difficult, all you need to do
/// is to add `ToString` method:
struct BinFoo {
int x;
String ToString() const { return FormatIntBase(x, 2); }
};
BinFoo bf;
bf.x = 30;
sout.Clear();
sout << bf;
DUMP(sout);
/// If you cannot add `ToString`, you can still specialize template in Upp namespace:
struct RomanFoo {
int x;
RomanFoo(int x) : x(x) {}
};
#if 0
namespace Upp {
template <> String Upp::AsString(const RomanFoo& a) { return FormatIntRoman(a.x); }
};
#endif
///
}

View file

@ -0,0 +1,54 @@
#include "Tutorial.h"
void Bidirectional()
{
/// .Bidirectional containers
/// `Vector` and `Array` containers allow fast adding and removing elements at the end of
/// sequence. Sometimes, same is needed at begin of sequence too (usually to support FIFO
/// queues). `BiVector` and `BiArray` are optimal for this scenario:
BiVector<int> n;
n.AddHead(1);
n.AddTail(2);
n.AddHead(3);
n.AddTail(4);
DUMP(n);
///
n.DropHead();
DUMP(n);
///
n.DropTail();
DUMP(n);
///
struct Val {
virtual String ToString() const = 0;
virtual ~Val() {}
};
struct Number : Val {
int n;
virtual String ToString() const { return AsString(n); }
};
struct Text : Val {
String s;
virtual String ToString() const { return s; }
};
BiArray<Val> num;
num.CreateHead<Number>().n = 3;
num.CreateTail<Text>().s = "Hello";
num.CreateHead<Text>().s = "World";
num.CreateTail<Number>().n = 2;
DUMP(num);
///
}

View file

@ -0,0 +1,59 @@
#include "Tutorial.h"
void ContainerClientTypes()
{
/// .Client types in U++ containers
/// So far we were using int as type of elements. In order to store client defined types
/// into the `Vector` (and the Vector ^topic://Core/src/Overview$en-us:flavor^) the type
/// must satisfy ^topic://Core/src/Moveable$en-us:moveable^ requirement - in short, it must
/// not contain back-pointers nor virtual methods. Type must be marked as %moveable% in
/// order to define interface contract using `Moveable`
/// ^https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern:CRTP idiom^:
struct Distribution : Moveable<Distribution> {
String text;
Vector<int> data;
String ToString() const { return text + ": " + AsString(data); }
};
/// Now to add `Distribution` elements you cannot use `Vector::Add(const T&)`, because it requires
/// elements to have default deep-copy constructor - and `Distribution does not have one, as
/// `Vector<int>` has default pick-constructor, so Distribution itself has pick-constructor.
/// It would no be a good idea either, because deep-copy would involve expensive copying of
/// inner Vector.
/// Instead, Add without parameters has to be used - it default constructs (that is cheap)
/// element in Vector and returns reference to it:
Vector<Distribution> dist;
for(int n = 5; n <= 10; n++) {
Distribution& d = dist.Add();
d.text << "Test " << n;
for(int i = 0; i < 10000; i++)
d.data.At(Random(n), 0)++;
}
DUMPC(dist);
/// Another possibility is to use `Vector::Add(T&&)` method, which uses pick-constructor
/// instead of deep-copy constructor. E.g. `Distribution` elements might be generated by
/// some function:
#if 0
Distribution CreateDist(int n);
/// and code for adding such elements to Vector then looks like:
for(n = 5; n <= 10; n++)
dist.Add(CreateDist(n));
/// alternatively, you can use default-constructed variant too
dist.Add() = CreateDist();
#endif
///
}

View file

@ -0,0 +1,35 @@
#include "Tutorial.h"
void CombineHashTutorial()
{
/// .CombineHash
/// To simplify providing of high quality hash codes for composite types, U++ provides
/// `CombineHash` utility class. This class uses `GetHashValue` function to gather hash
/// codes of all values and combines them to provide final hash value for composite type:
struct Foo {
String a;
int b;
unsigned GetHashValue() const { return CombineHash(a, b); }
};
/// Note that `GetHashValue` is defined as function template that calls `GetHashValue`
/// method of its argument, therefore defining `GetHashValue` method defines `GetHashValue`
/// function too:
Foo x;
x.a = "world";
x.b = 22;
DUMP(GetHashValue(x));
///
x.a << '!';
DUMP(GetHashValue(x));
///
}

View file

@ -0,0 +1,134 @@
#include "Tutorial.h"
void ComparableTutorial()
{
/// .SgnCompare and CombineCompare
/// Traditional approach of C language of representing comparison results was 3-state:
/// comparing a and b results in negative value (if a < b), zero (if a == b) or positive
/// value (a > b). In C++ standard library, comparisons are usually represented with `bool`
/// predicates.
/// However, with `bool` predicate it becomes somewhat more difficult to provide
/// comparisons for composite types:
struct Foo {
String a;
int b;
int c;
// we want to order Foo instances by a first, then b, then c
bool operator<(const Foo& x) const {
return a < x.a ? true
: a == x.a ? b < x.b ? true
: b == x.b ? false
: c < x.c
: false;
}
};
/// U++ provides standard function `SgnCompare`, which returns negative value/zero/positive
/// in "C style":
int a = 1;
int b = 2;
DUMP(SgnCompare(a, b));
DUMP(SgnCompare(b, a));
DUMP(SgnCompare(a, a));
/// Default implementation of `SgnCompare` calls `Compare` method of value:
struct MyClass {
int val;
int Compare(const MyClass& x) const { return SgnCompare(val, x.val); }
};
/// `SgnCompare` is now defined for `MyClass`:
MyClass u, v;
u.val = 1;
v.val = 2;
DUMP(SgnCompare(u, v));
DUMP(SgnCompare(v, u));
DUMP(SgnCompare(v, v));
/// Now getting back to `Foo`, with `SgnCompare` `operator<` becomes much less difficult:
struct Foo2 {
String a;
int b;
int c;
bool operator<(const Foo2& x) const {
int q = SgnCompare(a, x.a);
if(q) return q < 0;
q = SgnCompare(b, x.b);
if(q) return q < 0;
q = SgnCompare(c, x.c);
return q < 0;
}
};
/// Alternatively, it is possible to define just `Compare` method and use `Comparable`
/// ^https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern:CRTP idiom^ to
/// define all relation operators:
struct Foo3 : Comparable<Foo3> {
String a;
int b;
int c;
int Compare(const Foo3& x) const {
int q = SgnCompare(a, x.a);
if(q) return q;
q = SgnCompare(b, x.b);
if(q) return q;
return SgnCompare(c, x.c);
}
};
Foo3 m, n;
m.a = "A";
m.b = 1;
m.c = 2;
n.a = "A";
n.b = 1;
n.c = 3;
DUMP(m < n);
DUMP(m == n);
DUMP(m != n);
DUMP(SgnCompare(m, n));
/// While the content of `Compare` method is trivial, it can be further simplified using
/// `CombineCompare` helper class:
struct Foo4 : Comparable<Foo4> {
String a;
int b;
int c;
int Compare(const Foo4& x) const {
return CombineCompare(a, x.a)(b, x.b)(c, x.c);
}
};
Foo4 o, p;
o.a = "A";
o.b = 1;
o.c = 2;
p.a = "A";
p.b = 1;
p.c = 3;
DUMP(o < p);
DUMP(o == p);
DUMP(o != p);
DUMP(SgnCompare(o, p));
///
}

View file

@ -0,0 +1,45 @@
uses
Core,
RichEdit;
file
Tutorial.h,
Tutorial.cpp,
Core readonly separator,
ToDo.cpp,
Range.cpp,
Logging.cpp,
String.cpp,
StringBuffer.cpp,
WString.cpp,
DateTime.cpp,
AsString.cpp,
Value.cpp,
Null.cpp,
Value2.cpp,
CombineHash.cpp,
Compare.cpp,
Vector.cpp,
Vector2.cpp,
Transfer.cpp,
ClientTypes.cpp,
Array.cpp,
PolyArray.cpp,
Bidirectional.cpp,
Index.cpp,
IndexClient.cpp,
Map.cpp,
One.cpp,
Any.cpp,
InVector.cpp,
SortedMap.cpp,
Tuple.cpp,
Ranges.cpp,
Sort.cpp,
Function.cpp,
tutorial2.cpp,
help.qtf;
mainconfig
"" = "GUI";

View file

@ -0,0 +1,119 @@
#include "Tutorial.h"
void DateTime()
{
/// .Date and Time
/// To represent date and time, U++ provides `Date` and `Time` concrete types.
Date date = GetSysDate();
DUMP(date);
/// All data members of `Date` structure are public:
DUMP((int)date.year); // we need to cast to int because some date members
DUMP((int)date.month); // are of unsigned character type which would log
DUMP((int)date.day); // as characters
/// Dates can be compared:
DUMP(date > Date(2000, 1, 1));
/// Adding a number to `Date` adds a number of days to it, incrementing/decrementing goes to
/// the next/previous day:
DUMP(date + 1);
DUMP(--date);
DUMP(++date);
/// Subtraction of dates yields a number of days between them:
DUMP(date - Date(2000, 1, 1));
/// There are several `Date` and calendar related functions:
DUMP(IsLeapYear(2012));
DUMP(IsLeapYear(2014));
DUMP(IsLeapYear(2015));
DUMP(IsLeapYear(2016));
DUMP(IsLeapYear(2017));
///
DUMP(GetDaysOfMonth(2, 2015));
DUMP(GetDaysOfMonth(2, 2016));
///
DUMP(DayOfWeek(date)); // 0 is Sunday
///
DUMP(LastDayOfMonth(date));
DUMP(FirstDayOfMonth(date));
DUMP(LastDayOfYear(date));
DUMP(FirstDayOfYear(date));
DUMP(DayOfYear(date)); // number of days since Jan-1 + 1
DUMP(DayOfYear(Date(2016, 1, 1)));
///
DUMP(AddMonths(date, 20));
DUMP(GetMonths(date, date + 100)); // number of 'whole months' between two dates
DUMP(GetMonthsP(date, date + 100)); // number of 'whole or partial months' between two dates
DUMP(AddYears(date, 2));
///
DUMP(GetWeekDate(2015, 1));
int year;
DUMP(GetWeek(Date(2016, 1, 1), year)); // first day of year can belong to previous year
DUMP(year);
///
DUMP(EasterDay(2015));
DUMP(EasterDay(2016));
/// U++ defines the beginning and the end of era, most algorithms can safely assume that as
/// minimal and maximal values `Date` can represent:
DUMP(Date::Low());
DUMP(Date::High());
/// Time is derived from `Date`, adding members to represent time:
Time time = GetSysTime();
DUMP(time);
DUMP((Date)time);
DUMP((int)time.hour);
DUMP((int)time.minute);
DUMP((int)time.second);
/// Times can be compared:
DUMP(time > Time(1970, 0, 0));
/// Warning: As `Time` is derived from the `Date`, most operations automatically convert
/// `Time` back to `Date`. You have to use `ToTime` conversion function to convert `Date`
/// to `Time`:
DUMP(time > date); // time gets converted to Date...
DUMP(time > ToTime(date));
/// Like `Date`, `Time` supports add and subtract operations, but numbers represent seconds
/// (using `int64` datatype):
DUMP(time + 1);
DUMP(time + 24 * 3600);
DUMP(time - date); // time converts to Date, so the result is in days
DUMP(time - ToTime(date)); // Time - Time is in seconds
/// `Time` defines era limits too:
DUMP(Time::Low());
DUMP(Time::High());
///
}

View file

@ -0,0 +1,90 @@
#include "Tutorial.h"
void FunctionTutorial()
{
/// .Function
/// U++ `Function` is quite similar to `std::function` - it is a function wrapper that can
/// store/copy/invoke any callable target. There are two important differences.
/// First, invoking empty `Function` is NOP, if `Function` has return type `T`, it returns
/// `T()`. Second, `Function` allows effective chaining of callable targets using
/// `operator<<`, if `Function` has return type, the return type of last callable appended
/// is used.
/// Usually, the callable target is C++11 lambda:
Function<int (int)> fn = [](int n) { LOG("Called A"); return 3 * n; };
LOG("About to call function");
int n = fn(7);
DUMP(n);
/// If you chain another lambda into `Function`, all are called, but the last one's return
/// value is used:
fn << [](int n) { LOG("Called B"); return n * n; };
LOG("About to call combined function");
n = fn(7);
DUMP(n);
/// Invoking empty lambda does nothing and returns default constructed return value. This
/// is quite useful for GUI classes, which have a lot of output events represented by
/// `Function` which are often unassigned to any action.
fn.Clear();
LOG("About to call empty function");
n = fn(7);
DUMP(n);
/// While using `Function` with lambda expression is the most common, you can use any
/// target that has corresponding `operator()` defined:
struct Functor {
int operator()(int x) { LOG("Called Foo"); return x % 2; }
};
fn = Functor();
LOG("About to call Functor");
n = fn(7);
DUMP(n);
/// As `Function` with `void` and `bool` return types are the most frequently used, U++
/// defines template aliases `Event`:
Event<> ev = [] { LOG("Event invoked"); };
ev();
/// and `Gate`:
Gate<int> gt = [](int x) { LOG("Gate invoked with " << x); return x < 10; };
bool b = gt(9);
DUMP(b);
b = gt(10);
DUMP(b);
/// Using lambda to define calls to methods with more parameters can be verbose and
/// error-prone. The issue can be simplified by using `THISFN` macro:
struct Foo {
void Test(int a, const String& b) { LOG("Foo::Test " << a << ", " << b); }
typedef Foo CLASSNAME; // required for THISFN
void Do() {
Event<int, const String&> fn;
fn = [=](int a, const String& b) { Test(a, b); };
fn(1, "using lambda");
fn = THISFN(Test); // this is functionally equivalent, but less verbose
fn(2, "using THISFN");
}
};
Foo f;
f.Do();
///
}

View file

@ -0,0 +1,28 @@
#include "Tutorial.h"
void InVectorTutorial()
{
/// .`InVector`, `InArray`
/// `InVector` and `InArray` are container types quite similar to `Vector`/`Array`, but
/// they trade the speed of `operator[]` with the ability to insert or remove elements at
/// any position quickly. You can expect `operator[]` to be about 10 times slower than in
/// Vector (but that is still quite fast), while `Insert` at any position scales well up to
/// hundreds of megabytes of data (e.g. `InVector` containing 100M of String elements is
/// handled without problems).
InVector<int> v;
for(int i = 0; i < 1000000; i++)
v.Add(i);
v.Insert(0, -1); // This is fast
/// While the interface of `InVector`/`InArray` is almost identical to `Vector`/`Array`,
/// `InVector`/`InArray` in addition implements `FindLowerBound`/`FindUpperBound` methods -
/// while normal generic range algorithms work, it is possible to provide
/// `InVector`/`InArray` specific optimizations that basically match the performace of
/// `Find*Bound` on simple `Vector`.
DUMP(v.FindLowerBound(55));
///
}

View file

@ -0,0 +1,115 @@
#include "Tutorial.h"
void IndexTutorial() {
/// .`Index`
/// `Index` is the the foundation of all U++ associative operations and is one of defining
/// features of U++.
/// `Index` is a container very similar to the plain `Vector` (it is random access array of
/// elements with fast addition at the end) with one additional feature - it is able to fast
/// retrieve position of element with required value using `Find` method:
Index<String> ndx;
ndx.Add("alfa");
ndx.Add("beta");
ndx.Add("gamma");
ndx.Add("delta");
ndx.Add("kappa");
DUMP(ndx);
DUMP(ndx.Find("beta"));
/// If element is not present in `Index`, `Find` returns a negative value:
DUMP(ndx.Find("something"));
/// Any element can be replaced using `Set` method:
ndx.Set(1, "alfa");
DUMP(ndx);
/// If there are more elements with the same value, they can be iterated using `FindNext`
/// method:
int fi = ndx.Find("alfa");
while(fi >= 0) {
DUMP(fi);
fi = ndx.FindNext(fi);
}
/// `FindAdd` method retrieves position of element like `Find`, but if element is not
/// present in `Index`, it is added:
DUMP(ndx.FindAdd("one"));
DUMP(ndx.FindAdd("two"));
DUMP(ndx.FindAdd("three"));
DUMP(ndx.FindAdd("two"));
DUMP(ndx.FindAdd("three"));
DUMP(ndx.FindAdd("one"));
/// Removing elements from random access sequence tends to be expensive, that is why rather
/// than remove, `Index` supports `Unlink` and `UnlinkKey` operations, which retain the
/// element in `Index` but make it invisible for `Find` operation:
ndx.Unlink(2);
ndx.UnlinkKey("kappa");
DUMP(ndx.Find(ndx[2]));
DUMP(ndx.Find("kappa"));
/// You can test whether element at given position is unlinked using `IsUnlinked` method
DUMP(ndx.IsUnlinked(1));
DUMP(ndx.IsUnlinked(2));
/// Unlinked positions can be reused by `Put` method:
ndx.Put("foo");
DUMP(ndx);
DUMP(ndx.Find("foo"));
/// You can also remove all unlinked elements from `Index` using `Sweep` method:
ndx.Sweep();
DUMP(ndx);
/// Operations directly removing or inserting elements of Index are expensive, but
/// available too:
ndx.Remove(1);
DUMP(ndx);
///
ndx.RemoveKey("two");
DUMP(ndx);
///
ndx.Insert(0, "insert");
DUMP(ndx);
/// PickKeys operation allows you to obtain Vector of elements of Index in low
/// constant time operation (while destroying source Index)
Vector<String> d = ndx.PickKeys();
DUMP(d);
/// Pick-assigning `Vector` to `Index` is supported as well:
d[0] = "test";
ndx = pick(d);
DUMP(ndx);
///
}

View file

@ -0,0 +1,31 @@
#include "Tutorial.h"
void IndexClient()
{
/// .Index and client types
/// In order to store elements to `Index`, they type must be `Moveable`, have deep copy and
/// defined the `operator==` and a `GetHashValue` function or method to compute the hash
/// code. It is recommended to use `CombineHash` to combine hash values of types that
/// already provide `GetHashValue`:
struct Person : Moveable<Person> {
String name;
String surname;
unsigned GetHashValue() const { return CombineHash(name, surname); }
bool operator==(const Person& b) const { return name == b.name && surname == b.surname; }
Person(String name, String surname) : name(name), surname(surname) {}
Person() {}
};
Index<Person> p;
p.Add(Person("John", "Smith"));
p.Add(Person("Paul", "Carpenter"));
p.Add(Person("Carl", "Engles"));
DUMP(p.Find(Person("Paul", "Carpenter")));
///
}

View file

@ -0,0 +1,85 @@
#include "Tutorial.h"
void Logging()
{
///. Logging
/// Logging is a useful technique to trace the flow of the code and examine results. In
/// this tutorial we will be using logging extensively, so let us start tutorial with the
/// explanation of logging.
/// In debug mode and with default settings, macro `LOG` puts string into output log file.
/// Log file is placed into 'config-directory', which by default is .exe directory in Win32
/// and ~/.upp/appname in POSIX.
/// In TheIDE, you can access the log using 'Debug'/'View the log file Alt+L'.
LOG("Hello world");
/// You can log values of various types, as long as they have `AsString` function defined
/// You can chain values in single `LOG` using `operator<<`:
int x = 123;
LOG("Value of x is " << x);
/// As it is very common to log a value of single variable, `DUMP` macro provides a useful
/// shortcut, creating a log line with the variable name and value:
DUMP(x);
/// To get the value in hexadecimal code, you can use `LOGHEX` / `DUMPHEX`
DUMPHEX(x);
String h = "foo";
DUMPHEX(h);
/// To log the value of a container (or generic Range), you can either use normal
/// `LOG` / `DUMP`:
Vector<int> v = { 1, 2, 3 };
DUMP(v);
/// or you can use DUMPC for multi-line output:
DUMPC(v);
/// For maps, use DUMPM:
VectorMap<int, String> map = { { 1, "one" }, { 2, "two" } };
DUMP(map);
///
DUMPM(map);
/// All normal `LOG`s are removed in release mode. If you need to log things in release mode,
/// you need to use `LOG`/`DUMP` variant with '`R`' prefix (`RLOG`, `RDUMP`, `RDUMPHEX`...):
RLOG("This will be logged in release mode too!");
/// Sort of opposite situation is when adding temporary `LOG`s to the code for debugging. In
/// that case, '`D`' prefixed variants (`DLOG`, `DDUMP`, `DDUMPHEX`...) are handy - these cause
/// compile error in release mode, so will not get forgotten in the code past the release:
DLOG("This would not compile in release mode.");
/// The last flavor of `LOG` you can encounter while reading U++ sources is the one prefixed
/// with '`L`'. This one is not actually defined in U++ library and is just a convention. On
/// the start of file, there is usually something like:
#define LLOG(x) // DLOG(x)
/// and by uncommenting the body part, you can activate the logging in that particular file.
/// While logging to .log file is default, there are various ways how to affect logging,
/// for example following line adjusts logging to output the log both to the console and
/// .log file:
#if 0
StdLogSetup(LOG_COUT|LOG_FILE);
#endif
///
}

View file

@ -0,0 +1,138 @@
#include "Tutorial.h"
void Map()
{
/// .`VectorMap`, `ArrayMap`
/// `VectorMap` is nothing else than a simple composition of `Index` of keys and `Vector`
/// of values. You can use `Add` methods to put elements into the `VectorMap`:
struct Person : Moveable<Person> {
String name;
String surname;
String ToString() const { return String() << name << ' ' << surname; }
Person(String name, String surname) : name(name), surname(surname) {}
Person() {}
};
VectorMap<String, Person> m;
m.Add("1", Person("John", "Smith"));
m.Add("2", Person("Carl", "Engles"));
Person& p = m.Add("3");
p.name = "Paul";
p.surname = "Carpenter";
DUMP(m);
/// `VectorMap` provides read-only access to its `Index` of keys and read-write access to its
/// `Vector` of values:
DUMP(m.GetKeys());
DUMP(m.GetValues());
///
m.GetValues()[2].name = "Peter";
DUMP(m);
/// You can use indices to iterate `VectorMap` contents:
for(int i = 0; i < m.GetCount(); i++)
LOG(m.GetKey(i) << ": " << m[i]);
/// Standard `begin` / `end` pair for `VectorMap` is the range of just values (internal Vector)
/// - it corresponds with `operator[]` returning values:
for(const auto& p : m)
DUMP(p);
/// To iterate through keys, you can use `begin`/`end` of internal `Index`:
for(const auto& k : m.GetKeys())
DUMP(p);
/// Alternatively, it is possible to create 'projection range' of VectorMap that provides
/// convenient key/value iteration, using `operator~` (note that is also removes 'unliked'
/// items, see later):
for(const auto& e : ~m) {
DUMP(e.key);
DUMP(e.value);
}
/// You can use Find method to retrieve position of element with required key:
DUMP(m.Find("2"));
/// or Get method to retrieve corresponding value:
DUMP(m.Get("2"));
/// Passing key not present in `VectorMap` as `Get` parameter is undefined behavior (ASSERT
/// fails in debug mode), but there exists two parameter version of `Get` that returns second
/// parameter if the key is not found in VectorMap:
DUMP(m.Get("33", Person("unknown", "person")));
/// As with `Index`, you can use `Unlink` to make elements invisible for Find operations:
m.Unlink(1);
DUMP(m.Find("2"));
/// `SetKey` changes the key of the element:
m.SetKey(1, "33");
DUMP(m.Get("33", Person("unknown", "person")));
/// If there are more elements with the same key in `VectorMap`, you can iterate them using
/// `FindNext` method:
m.Add("33", Person("Peter", "Pan"));
int q = m.Find("33");
while(q >= 0) {
DUMP(m[q]);
q = m.FindNext(q);
}
/// Unlinked positions can be 'reused' using Put method:
m.UnlinkKey("33");
m.Put("22", Person("Ali", "Baba"));
m.Put("44", Person("Ivan", "Wilks"));
DUMP(m);
/// `PickValues` / `PickIndex` / `PickKeys` / pick internal `Vector` / `Index` / `Vector`
/// of `Index`:
Vector<Person> ps = m.PickValues();
Vector<String> ks = m.PickKeys();
DUMP(ps);
DUMP(ks);
DUMP(m);
/// `VectorMap` pick constructor to create map by picking:
ks[0] = "Changed key";
m = VectorMap<String, Person>(pick(ks), pick(ps));
DUMP(m);
/// `ArrayMap` is composition of Index and Array, for cases where Array is better fit for
/// value type (e.g. they are polymorphic):
ArrayMap<String, Person> am;
am.Create<Person>("key", "new", "person");
DUMP(am);
///
}

View file

@ -0,0 +1,44 @@
#include "Tutorial.h"
void NullTutorial()
{
/// .`Null`
/// U++ defines a special `Null` constant to represent an empty value. This constant is
/// convertible to many value types including primitive types `double`, `int` and `int64`
/// (defined as lowest number the type can represent). If type supports ordering (<, >),
/// all values of the type are greater than Null value. To test whether a value is empty,
/// use `IsNull` function.
int x = Null;
int y = 120;
Date d = Null;
Date e = GetSysDate();
DUMP(x);
DUMP(y);
DUMP(d);
DUMP(e > d);
/// Null is the only instance of `Nuller` type. Assigning `Null` to
/// primitive types is achieved by cast operators of `Nuller`, other types can do it using
/// constructor from `Nuller`.
/// As a special case, if Value contains Null, it is convertible to any value type that can contain Null:
Value v = x; // x is int
e = v; // e is Date, but v is Null, so Null is assigned to e
DUMP(IsNull(e));
/// Function `Nvl` is U++ analog of well known SQL function coalesce (ifnull, Nvl), which
/// returns the first non-null argument (or `Null` if all are `Null`).
int a = Null;
int b = 123;
int c = 1;
DUMP(Nvl(a, b, c));
///
}

View file

@ -0,0 +1,52 @@
#include "Tutorial.h"
void OneTutorial()
{
/// .`One`
/// `One` is a container that can store none or one element of T or derived from T. It is
/// functionally quite similar to `std::unique_ptr`, but has some convenient features.
struct Base {
virtual String Get() = 0;
virtual ~Base() {}
};
struct Derived1 : Base {
virtual String Get() { return "Derived1"; }
};
struct Derived2 : Base {
virtual String Get() { return "Derived2"; }
};
One<Base> s;
/// `operator bool` of one returns true if it contains an element:
DUMP((bool)s);
///
s.Create<Derived1>();
DUMP((bool)s);
DUMP(s->Get());
/// You can use `Is` to check if certain type is currently stored in `One`:
DUMP(s.Is<Derived1>());
DUMP(s.Is<Base>());
DUMP(s.Is<Derived2>());
/// To get a pointer to the contained instance, use `operator~`:
Base *b = ~s;
DUMP(b->Get());
/// Clear method removes the element from One:
s.Clear();
DUMP((bool)s);
///
}

View file

@ -0,0 +1,55 @@
#include "Tutorial.h"
void PolyArray()
{
/// .Polymorphic `Array`
/// `Array` can even be used for storing polymorphic elements:
struct Number {
virtual double Get() const = 0;
String ToString() const { return AsString(Get()); }
virtual ~Number() {}
};
struct Integer : public Number {
int n;
virtual double Get() const { return n; }
};
struct Double : public Number {
double n;
virtual double Get() const { return n; }
};
/// To add such derived types to `Array`, you can best use in-place creation with `Create`
/// method:
Array<Number> num;
num.Create<Double>().n = 15.5;
num.Create<Integer>().n = 3;
DUMP(num);
/// Alternatively, you can use `Add(T *)` method and provide a pointer to the newly created
/// instance on the heap (`Add` returns a reference to the instance):
Double *nd = new Double;
nd->n = 1.1;
num.Add(nd);
DUMP(num);
/// Array takes ownership of heap object and deletes it as appropriate. We recommend to use
/// this variant only if in-place creation with `Create` is not possible.
/// It is OK do directly apply U++ algorithms on `Array` (the most stringent requirement of
/// any of basic algorithms is that there is `IterSwap` provided for container iterators
/// and that is specialized for `Array` iterators):
Sort(num, [](const Number& a, const Number& b) { return a.Get() < b.Get(); });
DUMP(num);
///
}

View file

@ -0,0 +1,6 @@
#include "Tutorial.h"
void Ranges()
{
}

View file

@ -0,0 +1,69 @@
#include "Tutorial.h"
void Range()
{
/// .Range
/// Unlike STL, which interface algorithms with data using `begin` / `end` pair, U++ algorithms
/// usually work on %Ranges%. Range is an object that has `begin` / `end` methods providing
/// random access to elements (all U++ containers are random access), `operator[]` and
/// `GetCount` method.
/// Obviously, U++ containers are ranges:
Vector<int> x = { 1, 2, 3, 4, 5, 1, 2, 3, 4 };
DUMP(FindIndex(x, 2)); // FindIndex is a trivial algorithm that does linear search
/// If you want the algorithm to run on part of container only, you can use `SubRange`
/// instance:
DUMP(SubRange(x, 3, 6));
DUMP(FindIndex(SubRange(x, 3, 6), 4));
/// As a side-job, SubRange can also be created from 'begin' / 'end' pair, thus e.g.
/// allowing algorithms to work on C arrays:
int a[] = { 1, 22, 4, 2, 8 };
auto ar = SubRange(std::begin(a), std::end(a));
DUMP(ar);
///
Sort(ar);
DUMP(ar);
/// There are some macro aliases that make type management of ranges easier:
DUMP(typeid(ValueTypeOf<decltype(x)>).name());
DUMP(typeid(ValueTypeOf<decltype(SubRange(x, 1, 1))>).name());
DUMP(typeid(IteratorOf<decltype(x)>).name());
DUMP(typeid(ConstIteratorOf<decltype(SubRange(x, 1, 1))>).name());
DUMP(typeid(SubRangeOf<Vector<int>>).name());
/// While containers themselves and SubRange are the two most common range types, U++ has two
/// special ranges. `ConstRange` simply provides the range of single value:
DUMP(ConstRange(1, 10));
/// `ViewRange` picks a source range and `Vector` of integer indices a provides a view of
/// source range through this `Vector`:
Vector<int> h{ 2, 4, 0 };
DUMP(ViewRange(x, clone(h)));
///
Sort(ViewRange(x, clone(h)));
DUMP(ViewRange(x, clone(h)));
DUMP(x);
/// Finally `FilterRange` creates a subrange of elements satisfying certain condition:
DUMP(FilterRange(x, [](int x) { return x > 3; }));
///
}

View file

@ -0,0 +1,61 @@
#include "Tutorial.h"
void Sorting()
{
/// .Sorting
/// Unsurprisingly, `Sort` function sorts a range. You can specify sorting predicate,
/// default is `operator<`:
Vector<String> x { "1", "2", "10" };
Sort(x);
DUMP(x);
///
Sort(x, [](const String& a, const String& b) { return atoi(a) < atoi(b); });
DUMP(x);
/// `IndexSort` is sort variant that is able to sort two ranges (like `Vector` or `Array`)
/// of the same size, based on values in the first range:
Vector<int> a { 5, 10, 2, 9, 7, 3 };
Vector<String> b { "five", "ten", "two", "nine", "seven", "three" };
IndexSort(a, b);
DUMP(a);
DUMP(b);
///
IndexSort(b, a);
DUMP(a);
DUMP(b);
/// There are also `IndexSort2` and `IndexSort3` variants that sort 2 or 3 dependent ranges.
/// Sometimes, instead of sorting items in the range, it is useful to know the order of
/// items as sorted, using `GetSortOrder`:
Vector<int> o = GetSortOrder(a);
DUMP(o);
/// Normal `Sort` is not stable - equal items can appear in sorted range in random order.
/// If maintaining original order of equal items is important, use `StableSort` variant
/// (with performance penalty):
Vector<Point> t { Point(10, 10), Point(7, 1), Point(7, 2), Point(7, 3), Point(1, 0) };
StableSort(t, [](const Point& a, const Point& b) { return a.x < b.x; });
DUMP(t);
/// All sorting algorithms have they 'Stable' variant, so there is `StableIndexSort`,
/// `GetStableSortOrder` etc...
}

View file

@ -0,0 +1,37 @@
#include "Tutorial.h"
void SortedMap()
{
/// .`SortedIndex`, `SortedVectorMap`, `SortedArrayMap`
/// `SortedIndex` is similar to regular `Index`, but keeps its elements in sorted order
/// (sorting predicate is a template parameter, defaults to `StdLess`). Implementation is
/// using `InVector`, so it works fine even with very large number of elements (performance
/// is similar to tree based `std::set`). Unlike `Index`, `SortedIndex` provides
/// lower/upper bounds searches, so it allows range search.
SortedIndex<int> x;
x.Add(5);
x.Add(3);
x.Add(7);
x.Add(1);
DUMPC(x);
DUMP(x.Find(3));
DUMP(x.Find(3));
DUMP(x.FindLowerBound(3));
DUMP(x.FindUpperBound(6));
/// `SortedVectorMap` and `SortedArrayMap` are then `SortedIndex` based equivalents to
/// `VectorMap`/`ArrayMap`:
SortedVectorMap<String, int> m;
m.Add("zulu", 11);
m.Add("frank", 12);
m.Add("alfa", 13);
DUMPM(m);
DUMP(m.Get("zulu"));
///
}

View file

@ -0,0 +1,133 @@
#include "Tutorial.h"
void StringTutorial()
{
/// .String
/// String is a value type useful for storing text or binary data.
String a = "Hello";
DUMP(a);
/// You camn concatenate with another String or literal:
a = a + " world";
DUMP(a);
/// Or single character or specified number of characters from another `String` or literal:
a.Cat('!');
DUMP(a);
///
a.Cat("ABCDEFGHIJKLM", 3);
DUMP(a);
/// `Clear` method empties the String:
a.Clear();
DUMP(a);
/// You can use `operator<<` to append to existing `String`. Non-string values are
/// converted to appropriate `String` representation (using standard function `AsString`,
/// whose default template definition calls `ToString` method for value):
for(int i = 0; i < 10; i++)
a << i << ", ";
DUMP(a);
/// Sometimes is is useful to use `operator<<` to produce a temporary `String` value (e.g. as
/// real argument to function call):
String b = String() << "Number is " << 123 << ".";
DUMP(b);
/// String provides many various methods for obtaining character count, inserting
/// characters into `String` or removing them:
a = "0123456789";
DUMP(a.GetCount());
///
DUMP(a.GetLength()); // GetLength is a synonym of GetCount
///
a.Insert(6, "<inserted>");
DUMP(a);
///
a.Remove(2, 2);
DUMP(a);
/// as well as searching and comparing methods:
DUMP(a.Find('e'));
DUMP(a.ReverseFind('e'));
///
DUMP(a.Find("ins"));
///
DUMP(a.StartsWith("ABC"));
DUMP(a.StartsWith("01"));
DUMP(a.EndsWith("89"));
/// You can get slice of String using Mid method; with single parameter it provides slice
/// to the end of String:
DUMP(a.Mid(3, 3));
DUMP(a.Mid(3));
/// You can also trim the length of String using Trim (this is faster than using any other
/// method):
a.Trim(4);
DUMP(a);
/// You can obtain integer values of individual characters using operator[]:
DUMP(a[0]);
/// or the value of first character using operator* (note that if `GetCount() == 0`, this
/// will return zero terminator):
DUMP(*a);
///
a.Clear();
DUMP(*a);
/// `String` has implicit cast to zero terminated `const char *ptr` (only valid as long as
/// `String` does not mutate:
a = "1234";
const char *s = a;
while(*s)
LOG(*s++);
/// `String` also has standard `begin` `end` methods, which e.g. allows for C++11 `for`:
for(char ch : a)
LOG(ch);
/// It is absolutely OK and common to use String for storing binary data, including zeroes:
a.Cat(0);
DUMPHEX(a);
///
}

View file

@ -0,0 +1,61 @@
#include "Tutorial.h"
void CApiFunction(char *c)
{
strcpy(c, "Hello");
}
void StringBufferTutorial()
{
/// .StringBuffer
/// If you need a direct write access to `String`'s C-string character buffer, you can use
/// complementary `StringBuffer` class. One of reasons to do so is when you have to deal
/// with some C-API functions that expects to write directly to `char *` and you would like
/// that result converted to the `String`:
#if 0
void CApiFunction(char *c)
{
strcpy(c, "Hello");
}
#endif
StringBuffer b;
b.SetLength(200);
CApiFunction(b);
b.Strlen();
String x = b;
DUMP(x);
/// In this case, `SetLength` creates a C array of 200 characters. You can then call C-API
/// function. Later you set the real length using `Strlen` - this function performs strlen
/// of buffer and sets the length accordingly. Later you simply assign the `StringBuffer`
/// to `String`. Note that for performance reasons, this operation clears the
/// `StringBuffer` content (operation is fast and does not depend on the number of
/// characters).
/// Another usage scenario of StringBuffer is altering existing String:
b = x;
b[1] = 'a';
x = b;
DUMP(x);
/// Similar to assigning StringBuffer to String, assigning String to StringBuffer clears
/// the source String.
/// StringBuffer also provides appending operations:
b = x;
b.Cat('!');
x = b;
DUMP(x);
/// Note that sometimes when creating some String from a lot of single characters, using
/// StringBuffer for the operation is slightly faster then using String directly.
}

View file

@ -0,0 +1,25 @@
#include "Tutorial.h"
#if 0
return CombineCompare(a.NDX, b.NDX)(a.V_TEXT, b.V_TEXT)(a.V_NUMBER)(b.V_NUMBER);
Vector<int> id = clone(_id);
SplitTo(ln.Mid(4), '@', path, ln);
CONSOLE_APP_MAIN
{
Event<const Vector<int>&> h = [](const Vector<int>& x) { DUMP(x); };
Event<> ev;
{
Vector<int> x = { 1, 2 };
ev = [=, x = pick(x)] { h(x); };
}
ev();
}
#endif

View file

@ -0,0 +1,65 @@
#include "Tutorial.h"
#include <vector>
void Transfer()
{
/// .Transfer issues
/// Often you need to pass content of one container to another of the same type. U++
/// containers always support ^topic://Core/srcdoc/pick_$en-us:pick semantics^ (synonym of
/// std::move), and, depending on type stored, also might support
/// ^topic://Core/srcdoc/pick_$en-us:clone semantics^. When transferring the value, you
/// have to explicitly specify which one to use:
Vector<int> v{ 1, 2 };
DUMP(v);
Vector<int> v1 = pick(v);
DUMP(v);
DUMP(v1);
/// now source `Vector` `v` is empty, as elements were 'picked' to `v1`.
/// If you really need to preserve value of source (and elements support deep copy
/// operation), you can use `clone`:
v = clone(v1);
DUMP(v);
DUMP(v1);
/// The requirement of explicit `clone` has the advantage of avoiding unexpected deep
/// copies. For example:
Vector<Vector<int>> x;
x.Add() << 1 << 2 << 3;
#if 0
for(auto i : x) { LOG(i); }
#endif
/// results in run-time error, whereas the equivalent code with `std::vector` compiles but
/// silently performs deep copy for each iteration:
#if 0
std::vector<std::vector<int>> sv;
sv.push_back({1, 2, 3});
for(auto i : sv) // invokes std::vector<int> copy constructor
for(auto j : i)
DUMP(j);
#endif
/// That said, in certain cases it is simpler to have default copy instead of explicit
/// `clone`. You can easily achieve that using `WithDeepCopy` template:
WithDeepCopy<Vector<int>> v2;
v2 = v;
DUMP(v);
DUMP(v2);
///
}

View file

@ -0,0 +1,110 @@
#include "Tutorial.h"
void TupleTutorial()
{
/// . Tuples
/// Template class `Tuple` allows combining 2-4 values with
/// different types. These are principally similar to `std::tuple`, with some advantages.
/// Unlike `std::tuple`, individual elements are directly accessible as member variables
/// `a`..`d`, `Tuple` supports persistent storage patterns (`Serialize`, `Jsonize`, `Xmlize`), hash
/// code (`GetHashValue`), conversion to `String` and Value conversions.
/// To create a `Tuple` value, you can use the `MakeTuple` function.
Tuple<int, String, String> x = MakeTuple(12, "hello", "world");
/// Individual values are accessible as members `a` .. `d`:
DUMP(x.a);
DUMP(x.b);
DUMP(x.c);
/// Or using `Get`:
DUMP(x.Get<1>());
DUMP(x.Get<int>());
/// As long as all individual types have conversion to `String` (`AsString`), the tuple also
/// has such conversion and thus can e.g. be easily logged:
DUMP(x);
/// As long as individual types have defined `GetHashValue`, so does `Tuple`:
DUMP(GetHashValue(x));
/// As long as individual types have defined `operator==`, `Tuple` has defined `operator==`
/// and `operator!=`:
Tuple<int, String, String> y = x;
DUMP(x == y);
DUMP(x != y);
y.a++;
DUMP(x == y);
DUMP(x != y);
/// As long as all individual types have defined `SgnCompare`,
/// Tuple has SgnCompare, Compare method and operators <, <=, >, >=:
DUMP(x.Compare(y));
DUMP(SgnCompare(x, y));
DUMP(x < y);
/// GetCount returns the width of `Tuple`:
DUMP(x.GetCount());
/// Elements that are directly convertible with `Value` can be 'Get'/'Set':
for(int i = 0; i < x.GetCount(); i++)
DUMP(x.Get(i));
///
x.Set(1, "Hi");
DUMP(x);
/// As long as all individual types are convertible with `Value`, you can convert Tuple to
/// `ValueArray` and back:
ValueArray va = x.GetArray();
DUMP(va);
va.Set(2, "Joe");
x.SetArray(va);
/// It is OK to assign `Tuple` to `Tuple` with different individual types, as long as types
/// are directly convertible:
Tuple<double, String, String> d = x;
DUMP(d);
/// Tie can be used to assign tuple to l-values:
int i;
String s1, s2;
Tie(i, s1, s2) = x;
DUMP(i);
DUMP(s1);
DUMP(s2);
/// U++ Tuples are carefully designed as POD type, which allows POD arrays to be intialized
/// with classic C style:
static Tuple2<int, const char *> map[] = {
{ 1, "one" },
{ 2, "one" },
{ 3, "one" },
};
/// Simple FindTuple template function is provided to search for tuple based on the first
/// value (`a`) (linear O(n) search):
DUMP(FindTuple(map, __countof(map), 3)->b);
///
}

View file

@ -0,0 +1,193 @@
#include "RichEdit/RichEdit.h"
using namespace Upp;
String out;
int major = 0;
int minor = 0;
String qtf =
"[ $$0,0#00000000000000000000000000000000:Default]"
"[a83;*R6 $$1,3#31310162474203024125188417583966:caption]"
"[H4;b83;*4 $$2,3#07864147445237544204411237157677:title]"
"[b42;a42 $$3,3#45413000475342174754091244180557:text]"
"[l100;C@5*;1 $$4,4#20902679421464641399138805415013:code]"
"[l100;*C$7;2 $$5,5#07531550463529505371228428965313:log]"
"[b73;*3 $$2,3#07864147445237544204111237153677:subtitle]"
;
#define OUT(x) out << x << '\n';
void FlushDoc(String& docblock)
{
docblock = TrimBoth(docblock);
if(docblock.GetCount() == 0)
return;
OUT("============= DOC");
OUT(docblock);
bool title = false;
String style = "[s3;";
if(docblock.StartsWith("..")) {
docblock = AsString(major) + "." + AsString(++minor) + ' ' + TrimBoth(docblock.Mid(2));
style = "[s7;";
title = true;
}
else
if(docblock.StartsWith(".")) {
docblock = AsString(++major) + "." + ' ' + TrimBoth(docblock.Mid(1));
minor = 0;
style = "[s2;";
title = true;
}
qtf << style << " ";
const char *s = docblock;
while(*s)
if((s == ~docblock || findarg(s[-1], '(', ' ', '\'', '\"') >= 0) && findarg(s[0], '*', '%', '_', '`', '^') >= 0 && s[1] && s[1] != ' ') {
int c = *s++;
const char *b = s;
const char *dc = NULL;
while(*s && *s != c) {
if(*s == ':')
dc = s;
s++;
}
if(c == '^') {
qtf << "[^";
if(dc) {
qtf.Cat(b, dc);
b = dc + 1;
}
else
qtf.Cat(b, s);
qtf << "^ ";
}
else
qtf << decode(c, '*', "[* ", '%', "[/ ", '_', "[_ ", '`', title ? "[C@5 " : "[C@5* ", "");
while(b < s)
qtf << '`' << *b++;
qtf << "]";
if(*s) s++;
}
else
qtf << '`' << *s++;
qtf << "&]";
docblock.Clear();
}
void FlushLog(Vector<String>& log)
{
if(log.GetCount() == 0)
return;
OUT("============ LOG");
OUT(Join(log, "\r\n"));
qtf << "[s5; \1" << Join(log, "\n") << "\1&]&";
log.Clear();
}
void FlushCode(Vector<String>& code)
{
while(code.GetCount() && TrimBoth(code[0]).GetCount() == 0)
code.Remove(0);
while(code.GetCount() && TrimBoth(code.Top()).GetCount() == 0)
code.Drop();
bool tabs = true;
for(auto l : code)
if(l.GetCount() && *l != '\t') {
tabs = false;
break;
}
if(tabs)
for(auto& l : code)
if(l.GetCount())
l.Remove(0);
if(code.GetCount() == 0)
return;
OUT("============= CODE");
OUT(Join(code, "\r\n"));
qtf << "&[s4; \1" << Join(code, "\n") << "\1&]&";
code.Clear();
}
void MakeTutorial()
{
String log = LoadFile(GetStdLogPath());
StringStream ss(log);
VectorMap<String, Vector<Tuple<int, String>>> logline;
String path;
int line;
while(!ss.IsEof()) {
String ln = ss.GetLine();
if(ln.StartsWith("-=>")) {
SplitTo(ln.Mid(4), '@', path, ln);
line = atoi(ln) - 1;
}
else
if(path.GetCount())
logline.GetAdd(path).Add(MakeTuple(line, ln));
}
for(auto&& f : ~logline) {
Vector<String> src = Split(Filter(LoadFile(f.key), [] (int c) { return c == '\r' ? 0 : c; }), '\n', false);
int i = 0;
int logi = 0;
bool wasdoc = false;
Vector<String> code;
while(i < src.GetCount()) {
String block;
while(i < src.GetCount() && TrimLeft(src[i]).StartsWith("///")) {
FlushCode(code);
Vector<String> logblock;
while(logi < f.value.GetCount() && f.value[logi].a <= i)
logblock.Add(f.value[logi++].b);
FlushLog(logblock);
String line = src[i++];
int q = line.FindAfter("///");
if(TrimRight(line).GetCount() > q) {
if(block.GetCount())
block << ' ';
block << TrimBoth(line.Mid(q));
}
else
FlushDoc(block);
wasdoc = true;
}
FlushDoc(block);
while(i < src.GetCount() && !TrimLeft(src[i]).StartsWith("///")) {
String tl = TrimLeft(src[i]);
if(!tl.StartsWith("#if") && !tl.StartsWith("#end"))
code.Add(src[i]);
i++;
}
DUMPC(code);
if(!wasdoc)
code.Clear();
}
}
LOG("--------------------------------------------");
LOG(out);
LOG("--------------------------------------------");
LOG(qtf);
RichEditWithToolBar edit;
edit.SetReadOnly();
edit <<= qtf;
TopWindow win;
win.Add(edit.SizePos());
win.Run();
}

View file

@ -0,0 +1,51 @@
#include <RichEdit/RichEdit.h>
using namespace Upp;
#define LOGPOS UPP::VppLog() << "-=> " << __FILE__ << '@' << __LINE__ << '\n',
#undef LOG
#undef DUMP
#undef DUMPC
#undef DUMPM
#undef LOGHEX
#undef DUMPHEX
#define LOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL
#define DUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL
#define DUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c))
#define DUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c))
#define LOGHEX(x) LOGPOS UPP::LogHex(x)
#define DUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x)
#undef DLOG
#undef DDUMP
#undef DDUMPC
#undef DDUMPM
#undef DLOGHEX
#undef DDUMPHEX
#define DLOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL
#define DDUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL
#define DDUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c))
#define DDUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c))
#define DLOGHEX(x) LOGPOS UPP::LogHex(x)
#define DDUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x)
#undef RLOG
#undef RDUMP
#undef RDUMPC
#undef RDUMPM
#undef RLOGHEX
#undef RDUMPHEX
#define RLOG(a) LOGPOS UPP::VppLog() << a << UPP::EOL
#define RDUMP(a) LOGPOS UPP::VppLog() << #a << " = " << (a) << UPP::EOL
#define RDUMPC(c) LOGPOS UPP::DumpContainer(UPP::VppLog() << #c << ':' << UPP::EOL, (c))
#define RDUMPM(c) LOGPOS UPP::DumpMap(VppLog() << #c << ':' << UPP::EOL, (c))
#define RLOGHEX(x) LOGPOS UPP::LogHex(x)
#define RDUMPHEX(x) LOGPOS UPP::VppLog() << #x << " = ", UPP::LogHex(x)
#define DO(x) void x(); x();
void MakeTutorial();

View file

@ -0,0 +1,77 @@
#include "Tutorial.h"
void ValueTutorial()
{
/// .Value
/// Value is sort of equivalent of polymorphic data types from scripting languages like Python
/// or JavaSript. `Value` can represent values of concrete types, some types also have
/// extended interoperability with `Value` and it is then possible to e.g. compare `Value`s
/// containing such types against each other or serialize them for persistent storage.
/// Usually, Value compatible types define typecast operator to `Value` and constructor
/// from `Value`, so that interaction is for the most part seamless:
Value a = 1;
Value b = 2.34;
Value c = GetSysDate();
Value d = "hello";
DUMP(a);
DUMP(b);
DUMP(c);
DUMP(d);
int x = a;
double y = b;
Date z = c;
String s = d;
DUMP(x);
DUMP(y);
DUMP(z);
DUMP(s);
/// As for primitive types, Value seamlessly works with `int`, `int64`, `bool` and `double`.
/// Casting `Value` to a type that it does not contain throws an exception:
try {
s = a;
DUMP(s); // we never get here....
}
catch(ValueTypeError) {
LOG("Failed Value conversion");
}
/// However, conversion between related types is possible (as long as it is supported by
/// these types):
double i = a;
int j = b;
Time k = c;
WString t = d;
DUMP(i);
DUMP(j);
DUMP(k);
DUMP(t);
/// To determine type of value stored in `Value`, you can use `Is` method:
DUMP(a.Is<int>());
DUMP(a.Is<double>());
DUMP(b.Is<double>());
DUMP(c.Is<int>());
DUMP(c.Is<Date>());
DUMP(d.Is<String>());
/// Note that Is tests for absolute type match, not for compatible types. For that reason,
/// for widely used compatible types helper functions are defined:
DUMP(IsNumber(a));
DUMP(IsNumber(b));
DUMP(IsDateTime(c));
DUMP(IsString(d));
///
}

View file

@ -0,0 +1,90 @@
#include "Tutorial.h"
void Value2Tutorial()
{
/// .Client types and `Value`, `RawValue`, `RichValue`
/// There are two Value compatibility levels. The simple one, `RawValue`, has little
/// requirements for the type used - only copy constructor and assignment operator are
/// required (and there are even forms of `RawValue` that work for types missing these):
struct RawFoo {
String x;
// default copy constructor and assignment operator are provided by compiler
};
/// To convert such type to `Value`, use `RawToValue`:
RawFoo h;
h.x = "hello";
Value q = RawToValue(h);
DUMP(q.Is<RawFoo>());
/// To convert it back, us 'To' templated member function of `Value`, it returns a constant
/// reference to the value:
DUMP(q.To<RawFoo>().x);
/// `RichValue` level `Value`s provide more operations for `Value` - equality test,
/// `IsNull` test, hashing, conversion to text, serialization (possibly to XML and Json),
/// comparison. In order to make serialization work, type must also have assigned an
/// integer id (client types should use ids in range 10000..20000). Type can provide the
/// support for these operations via template function specializations or (perhaps more
/// convenient) using defined methods and inheriting from `ValueType` base class template:
struct Foo : ValueType<Foo, 10010> {
int x;
Foo(const Nuller&) { x = Null; }
Foo(int x) : x(x) {}
Foo() {}
// We provide these methods to allow automatic conversion of Foo to/from Value
operator Value() const { return RichToValue(*this); }
Foo(const Value& v) { *this = v.Get<Foo>(); }
String ToString() const { return AsString(x); }
unsigned GetHashValue() const { return x; }
void Serialize(Stream& s) { s % x; }
bool operator==(const Foo& b) const { return x == b.x; }
bool IsNullInstance() const { return IsNull(x); }
int Compare(const Foo& b) const { return SgnCompare(x, b.x); }
// This type does not define XML nor Json serialization
};
#if 0
INITBLOCK { // This has to be at file level scope
#endif
Value::Register<Foo>(); // need to register value type integer id to allow serialization
#if 0
}
#endif
Value a = Foo(54321); // uses Foo::operator Value
Value b = Foo(54321);
Value c = Foo(600);
DUMP(a); // uses Foo::ToString
DUMP(a == b); // uses Foo::operator==
DUMP(a == c);
DUMP(c < a); // uses Foo::Compare
DUMP(IsNull(a)); // uses Foo::IsNullInstance
Foo foo = c; // Uses Foo::Foo(const Value&)
DUMP(foo);
///
String s = StoreAsString(a); // Uses Foo::Serialize
Value loaded;
// Using registered (Value::Registered) integer id creates the correct type, then uses
// Foo::Serialize to load the data from the stream
LoadFromString(loaded, s);
DUMP(loaded);
///
};

View file

@ -0,0 +1,58 @@
#include "Tutorial.h"
void Vector1()
{
/// .`Vector` basics
/// `Vector` is the basic container of U++. It is the random access container similar to
/// `std::vector` with one important performance related difference: There are rules for
/// elements of `Vector` that allow its implementation to move elements in memory using
/// plain `memcpy`/`memmove` ("Moveable" concept).
/// Anyway, for now let us start with simple `Vector` of `int`s:
Vector<int> v;
/// You can add elements to the Vector as parameters of the Add method
v.Add(1);
v.Add(2);
DUMP(v);
/// Alternative and very important possibility for U++ containers is 'in-place creation'.
/// In this case, parameter-less Add returns a reference to a new element in `Vector`:
v.Add() = 3;
DUMP(v);
/// You can also use `operator<<`
v << 4 << 5;
DUMP(v);
/// `Vector` also supports initializer lists:
v.Append({ 6, 7 });
DUMP(v);
/// To iterate `Vector` you can use indices:
for(int i = 0; i < v.GetCount(); i++)
LOG(v[i]);
/// begin/end interface:
for(auto q = v.begin(), e = v.end(); q != e; q++)
LOG(*q);
/// C++11 range-for syntax:
for(const auto& q : v)
LOG(q);
///
}

View file

@ -0,0 +1,46 @@
#include "Tutorial.h"
void Vector2()
{
/// .`Vector` operations
/// You can `Insert` or `Remove` elements at random positions of Vector (O(n) complexity):
Vector<int> v;
v.Add(1);
v.Add(2);
v.Insert(1, 10);
DUMP(v);
///
v.Insert(0, { 7, 6, 5 });
DUMP(v);
///
v.Remove(0);
DUMP(v);
/// At method returns element at specified position ensuring that such a position exists.
/// If there is not enough elements in `Vector`, required number of elements is added. If
/// second parameter of `At` is present, newly added elements are initialized to this value.
v.Clear();
for(int i = 0; i < 10000; i++)
v.At(Random(10), 0)++;
DUMP(v);
/// You can apply algorithms on containers, e.g. Sort
Sort(v);
DUMP(v);
///
}

View file

@ -0,0 +1,33 @@
#include "Tutorial.h"
void WStringTutorial()
{
/// .WString
/// String works with 8 bit characters. For 16-bit character encoding use `WString`. Both
/// classes are closely related and share most of interface methods. U++ also provides
/// conversions between `String` and `WString` and you can also use 8 bit string literals with
/// `WString`. Conversion is ruled by current default character set. Default value of default
/// character set is `CHARSET_UTF8`. This conversion is also used in `WString::ToString`,
/// e.g. when putting `WString` to log:
WString x = "characters 280-300: "; // you can assign 8-bit character literal to WString
for(int i = 280; i < 300; i++)
x.Cat(i);
DUMP(x);
/// `ToString` converts `WString` to `String`:
String y = x.ToString();
DUMP(y);
/// `ToWString` converts `String` to `WString`:
y.Cat(" (appended)"); // you can use 8-bit character literals in most WString operations
x = y.ToWString();
DUMP(x);
///
}

View file

@ -0,0 +1,747 @@
[ $$0,0#00000000000000000000000000000000:Default]
[a83;*R6 $$1,3#31310162474203024125188417583966:caption]
[H4;b83;*4 $$2,3#07864147445237544204411237157677:title]
[b42;a42;ph2 $$3,3#45413000475342174754091244180557:text]
[l321;C@5;1 $$4,4#20902679421464641399138805415013:code]
[l321;b83;a83;*C$7;2 $$5,5#07531550463529505371228428965313:result`-line]
[l321;*C$7;2 $$6,6#03451589433145915344929335295360:result]
[{_}%EN-US
[s1; U`+`+ Containers Tutorial&]
[s2; 1. Vector basics&]
[s3; Let us start with a simple [* Vector] of ints&]
[s4; -|[* Vector<int>] v;&]
[s3; You can add elements to the Vector as parameters of the [* Add]
method&]
[s4; -|v.[* Add](1);&]
[s4; -|v.Add(2);&]
[s4; -|v.Add(3);&]
[s3; To iterate Vector you can use indices&]
[s4; -|for(int i `= 0; i < v.[* GetCount](); i`+`+)&]
[s4; -|-|LOG(v[* `[]i[* `]]);&]
[s3; Alternative, U`+`+ also provides standard C`+`+ begin/end interface&]
[s4; -|for(auto q `= v.[* begin](), e `= v.[* end](); q !`= e; q`+`+)&]
[s4; -|-|LOG([* `*]q);&]
[s3; or with C`+`+11 range`-for syntax:&]
[s4; -|for(const auto`& q : v)&]
[s4; -|-|LOG(q);&]
[s3;/ &]
[s3; [/ Note: LOG is diagnostics macro that outputs its argument to
the .log file in debug mode. Another similar macros we will use
in this tutorial are DUMP (similar to the LOG, but dumps the
source expression too) and DUMPC (dumps the content of the container).]&]
[s3;/ &]
[s2; 2. Vector operations&]
[s3; You can [* Insert] or [* Remove] elements at random positions of
Vector&]
[s4; -|Vector<int> v;&]
[s4; -|v.Add(1);&]
[s4; -|v.Add(2);&]
[s4; -|&]
[s4; -|v.[* Insert](1, 10);&]
[s5; v `= `{ 1, 10, 2 `}&]
[s4; -|v.[* Remove](0);&]
[s5; v `= `{ 10, 2 `}&]
[s3; [* At] method returns element at specified position ensuring that
such a position exists. If there is not enough elements in Vector,
required number of elements is added. If second parameter of
At is present, newly added elements are initialized to this value.
As an example, we will create distribution of RandomValue with
unknown maximal value&]
[s4; &]
[s4; -|v.Clear();&]
[s4; -|for(int i `= 0; i < 10000; i`+`+)&]
[s4; -|-|v.[* At](RandomValue(), 0)`+`+;&]
[s5; v `= `{ 958, 998, 983, 1012, 1013, 1050, 989, 998, 1007, 992
`}&]
[s3; You can apply algorithms on containers, e.g. [* Sort]&]
[s4; -|[* Sort](v);&]
[s5; v `= `{ 958, 983, 989, 992, 998, 998, 1007, 1012, 1013, 1050
`}&]
[s3; &]
[s2; 3. Transfer issues&]
[s3; Often you need to pass content of one container to another of
the same type. NTL containers always support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ p
ick semantics] (equivalent of std`::move), and, depending on type
stored, also might support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ clone
sematics]. When transfering the value, you have to explicitly
specify which one to use:&]
[s4; -|Vector<int> v;&]
[s4; -|v.Add(1);&]
[s4; -|v.Add(2);&]
[s4; &]
[s4; -|Vector<int> v1 [* `=] pick(v);&]
[s3; now source Vector [C v] is destroyed `- picked `- and you can
no longer access its content. This behaviour has many advantages.
First, it is consistently fast and in most cases, transfer of
value instead of full copy is exactly what you need. Second,
NTL containers can store elements that lack copy operation `-
in that case, pick transfer is the only option anyway.&]
[s3; If you really need to preserve value of source (and elements
support deep copy operation), you can use [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ c
lone]&]
[s4; -|v [* `=] clone(v1);&]
[s3; &]
[s2; 4. Client types&]
[s3; So far we were using int as type of elements. In order to store
client defined types into the Vector (and the Vector [^topic`:`/`/Core`/src`/Overview`$en`-us^ f
lavor]) the type must satisfy [^topic`:`/`/Core`/src`/Moveable`$en`-us^ moveable]
requirement `- in short, it must not contain back`-pointers nor
virtual methods. Type must be marked as moveable in order to
define interface contract using&]
[s4; -|struct Distribution : [* Moveable]<Distribution> `{&]
[s4; -|-|String text;&]
[s4; -|-|Vector<int> data;&]
[s4; &]
[s4; -|-|rval`_default(Distribution);&]
[s4; -|-|Distribution() `{`}&]
[s4; -|`};&]
[s3; Note that rval`_default macro is helper to restore default deleted
r`-value constructor in C`+`+11 and default constructor is also
default deleted in C`+`+11.&]
[s3; Now to add Distribution elements you cannot use Add with parameter,
because it requires elements to have default deep`-copy constructor
`- and Distribution does not have one, as Vector<int> has default
pick`-constructor, so Distribution itself has pick`-constructor.
It would no be a good idea either, because deep`-copy would involve
expensive copying of inner Vector.&]
[s3; Instead, [* Add] without parameters has to be used `- it default
constructs (that is cheap) element in Vector and returns reference
to it&]
[s4; -|Vector<Distribution> dist;&]
[s4; -|for(int n `= 5; n <`= 10; n`+`+) `{&]
[s4; -|-|Distribution`& d `= dist.[* Add]();&]
[s4; -|-|d.text << `"Test `" << n;&]
[s4; -|-|for(int i `= 0; i < 10000; i`+`+)&]
[s4; -|-|-|d.data.At(rand() % n, 0)`+`+;&]
[s4; -|`}&]
[s4; &]
[s6; Test 5: `{ 2018, 1992, 2025, 1988, 1977 `}&]
[s6; Test 6: `{ 1670, 1682, 1668, 1658, 1646, 1676 `}&]
[s6; Test 7: `{ 1444, 1406, 1419, 1493, 1370, 1418, 1450 `}&]
[s6; Test 8: `{ 1236, 1199, 1245, 1273, 1279, 1302, 1250, 1216 `}&]
[s6; Test 9: `{ 1115, 1111, 1100, 1122, 1192, 1102, 1089, 1064, 1105
`}&]
[s6; Test 10: `{ 969, 956, 1002, 1023, 1006, 994, 1066, 1022, 929,
1033 `}&]
[s3; Another possibility is to use AddPick method, which uses pick`-constructor
instead of deep`-copy constructor. E.g. Distribution elements
might be generated by some function &]
[s4; -|Distribution CreateDist(int n);&]
[s3; and code for adding such elements to Vector then looks like&]
[s4; -|for(n `= 5; n <`= 10; n`+`+)&]
[s4; -|-|dist.[* AddPick](CreateDist(n));&]
[s3; alternatively, you can use default`-constructed variant too&]
[s4; -|-|dist.Add() `= CreateDist(); // alternative&]
[s3; &]
[s2; 5. Array flavor&]
[s3; If elements do not satisfy requirements for Vector flavor, they
can still be stored in Array flavor. Another reason for using
Array is need for referencing elements `- Array flavor never
invalidates references or pointers to them.&]
[s3; Example of elements that cannot be stored in Vector flavor are
STL containers (STL does not specify the NTL flavor for obvious
reasons):&]
[s4; -|[* Array]< std`::list<int> > al;&]
[s4; -|for(int i `= 0; i < 4; i`+`+) `{&]
[s4; -|-|std`::list<int>`& l `= al.Add();&]
[s4; -|-|for(int q `= 0; q < i; q`+`+)&]
[s4; -|-|-|l.push`_back(q);&]
[s4; -|`}&]
[s3; &]
[s2; 6. Polymorphic Array&]
[s3; Array can even be used for storing polymorphic elements &]
[s4; struct Number `{&]
[s4; -|virtual double Get() const `= 0;&]
[s4; &]
[s4; -|String ToString() const `{ return AsString(Get()); `}&]
[s4; -|&]
[s4; -|virtual `~Number() `{`}&]
[s4; `};&]
[s4; &]
[s4; struct Integer : public Number `{&]
[s4; -|int n;&]
[s4; -|virtual double Get() const `{ return n; `}&]
[s4; &]
[s4; -|Integer() `{`}&]
[s4; `};&]
[s4; &]
[s4; struct Double : public Number `{&]
[s4; -|double n;&]
[s4; -|virtual double Get() const `{ return n; `}&]
[s4; &]
[s4; -|Double() `{`}&]
[s4; `};&]
[s4; &]
[s4; bool operator<(const Number`& a, const Number`& b)&]
[s4; `{&]
[s4; -|return a.Get() < b.Get();&]
[s4; `}&]
[s4; &]
[s3; In this case, elements are added using Add with pointer to base
element type parameter. Do not be confused by new and pointer,
Array takes ownership of passed object and behaves like container
of base type elements&]
[s4; -|Array<Number> num;&]
[s4; -|num.Create<Double>().n `= 15.5;&]
[s4; -|num.Create<Integer>().n `= 3;&]
[s5; num `= `{ 15.5, 3 `}&]
[s3; Thanks to well defined algorithm requirements, you can e.g.
directly apply Sort on such Array&]
[s4; -|bool operator<(const Number`& a, const Number`& b)&]
[s4; -|`{&]
[s4; -|-|return a.Get() < b.Get();&]
[s4; -|`}&]
[s4; &]
[s4; .......&]
[s4; &]
[s4; -|Sort(num);&]
[s5; num `= `{ 3, 15.5 `}&]
[s3; &]
[s2; 7. Bidirectional containers&]
[s3; Vector and Array containers allow fast adding and removing elements
at the end of sequence. Sometimes, same is needed at begin of
sequence too (usually to support FIFO like operations). In such
case, BiVector and BiArray should be used&]
[s4; -|BiVector<int> n;&]
[s4; -|n.[* AddHead](1);&]
[s4; -|n.[* AddTail](2);&]
[s4; -|n.AddHead(3);&]
[s4; -|n.AddTail(4);&]
[s5; n `= `{ 3, 1, 2, 4 `}&]
[s4; -|n.[* DropHead]();&]
[s5; n `= `{ 1, 2, 4 `}&]
[s4; -|n.[* DropTail]();&]
[s5; n `= `{ 1, 2 `}&]
[s4; -|BiArray<Number> num;&]
[s4; -|num.CreateHead<Integer>().n `= 3;&]
[s4; -|num.CreateTail<Double>().n `= 15.5;&]
[s4; -|num.CreateHead<Double>().n `= 2.23;&]
[s4; -|num.CreateTail<Integer>().n `= 2;&]
[s5; num `= `{ 2.23, 3, 15.5, 2 `}&]
[s3; &]
[s2; 8. Index&]
[s3; Index is a container very similar to the plain Vector (it is
random access array of elements with fast addition at the end)
with one unique feature `- it is able to fast retrieve position
of element with required value using [* Find] method&]
[s4; -|[* Index]<String> ndx;&]
[s4; -|ndx.[* Add](`"alfa`");&]
[s4; -|ndx.Add(`"beta`");&]
[s4; -|ndx.Add(`"gamma`");&]
[s4; -|ndx.Add(`"delta`");&]
[s4; -|ndx.Add(`"kappa`");&]
[s5; ndx `= `{ alfa, beta, gamma, delta, kappa `}&]
[s4; -|DUMP(ndx.[* Find](`"beta`"));&]
[s5; ndx.Find(`"beta`") `= 1&]
[s3; If element is not present in Index, Find returns a negative
value&]
[s4; -|DUMP(ndx.Find(`"something`"));&]
[s5; ndx.Find(`"something`") `= `-1&]
[s3; Any element can be replaced using [* Set] method&]
[s4; -|ndx.[* Set](0, `"delta`");&]
[s5; ndx `= `{ delta, beta, gamma, delta, kappa `}&]
[s3; If there are more elements with the same value, they can be
iterated using [* FindNext] method&]
[s4; -|int fi `= ndx.Find(`"delta`");&]
[s4; -|while(fi >`= 0) `{&]
[s4; -|-|DUMP(fi);&]
[s4; -|-|fi `= ndx.[* FindNext](fi);&]
[s4; -|`}&]
[s4; -|cout << `'n`';&]
[s5; 0 3-|&]
[s3; [* FindAdd] method retrieves position of element like Find, but
if element is not present in Index, it is added&]
[s4; -|DUMP(ndx.[* FindAdd](`"one`"));&]
[s4; -|DUMP(ndx.FindAdd(`"two`"));&]
[s4; -|DUMP(ndx.FindAdd(`"three`"));&]
[s4; -|DUMP(ndx.FindAdd(`"two`"));&]
[s4; -|DUMP(ndx.FindAdd(`"three`"));&]
[s4; -|DUMP(ndx.FindAdd(`"one`"));&]
[s4; &]
[s6; ndx.FindAdd(`"one`") `= 5&]
[s6; ndx.FindAdd(`"two`") `= 6&]
[s6; ndx.FindAdd(`"three`") `= 7&]
[s6; ndx.FindAdd(`"two`") `= 6&]
[s6; ndx.FindAdd(`"three`") `= 7&]
[s6; ndx.FindAdd(`"one`") `= 5&]
[s3; Removing elements from random access sequence is always expensive,
that is why rather than remove, Index supports [* Unlink] and [* UnlinkKey
]operations, which leave element in Index but make it invisible
for Find operation&]
[s4; -|ndx.[* Unlink](2);&]
[s4; -|ndx.[* UnlinkKey](`"kappa`");&]
[s4; &]
[s4; -|DUMP(ndx.Find(ndx`[2`]));&]
[s4; -|DUMP(ndx.Find(`"kappa`"));&]
[s4; &]
[s6; ndx.Find(ndx`[2`]) `= `-1&]
[s6; ndx.Find(`"kappa`") `= `-1&]
[s3; You can test whether element at given position is unlinked using
[* IsUnlinked] method&]
[s4; -|DUMP(ndx.[* IsUnlinked](1));&]
[s4; -|DUMP(ndx.IsUnlinked(2));&]
[s4; &]
[s6; ndx.IsUnlinked(1) `= 0&]
[s6; ndx.IsUnlinked(2) `= 1&]
[s3; Unlinked positions can be reused by [* Put] method&]
[s4; -|ndx.[* Put](`"foo`");&]
[s5; ndx `= `{ delta, beta, foo, delta, kappa, one, two, three `}&]
[s4; -|DUMP(ndx.Find(`"foo`"));&]
[s5; ndx.Find(`"foo`") `= 2&]
[s3; You can also remove all unlinked elements from Index using [* Sweep]
method&]
[s4; -|ndx.Sweep();&]
[s5; ndx `= `{ delta, beta, foo, delta, one, two, three `}&]
[s3; As we said, operations directly removing or inserting elements
of Index are very expensive, but sometimes this might not matter,
so they are available too&]
[s4; -|ndx.[* Remove](1);&]
[s5; ndx `= `{ delta, foo, delta, one, two, three `}&]
[s4; -|ndx.[* RemoveKey](`"two`");&]
[s5; ndx `= `{ delta, foo, delta, one, three `}&]
[s4; -|ndx.[* Insert](0, `"insert`");&]
[s5; ndx `= `{ insert, delta, foo, delta, one, three `}&]
[s3; Finally, [* PickKeys] operation allows you to obtain Vector of
elements of Index in low constant time operation (while destroying
source Index)&]
[s4; -|Vector<String> d `= ndx.[* PickKeys]();&]
[s4; -|Sort(d);&]
[s5; d `= `{ delta, delta, foo, insert, one, three `}&]
[s3; &]
[s2; 9. Index and client types&]
[s3; In order to store elements to Index, they must be moveable (you
can use [* ArrayIndex] for types that are not) and they must have
defined the operator`=`= and a function to compute hash value.
Notice usage THE of [* CombineHash] to combine hash values of types
already known to U`+`+ into final result&]
[s4; &]
[s4; struct Person : Moveable<Person> `{&]
[s4; -|String name;&]
[s4; -|String surname;&]
[s4; &]
[s4; -|unsigned [* GetHashValue]() const `{ return [* CombineHash](name,
surname); `}&]
[s4; -|bool [* operator`=`=](const Person`& b) const `{ return name
`=`= b.name `&`& surname `=`= b.surname; `}&]
[s4; &]
[s4; -|Person(String name, String surname) : name(name), surname(surname)
`{`}&]
[s4; -|Person() `{`}&]
[s4; `};&]
[s4; &]
[s4; .......&]
[s4; &]
[s4; -|Index<Person> p;&]
[s4; -|p.Add(Person(`"John`", `"Smith`"));&]
[s4; -|p.Add(Person(`"Paul`", `"Carpenter`"));&]
[s4; -|p.Add(Person(`"Carl`", `"Engles`"));&]
[s4; -| &]
[s4; -|DUMP(p.Find(Person(`"Paul`", `"Carpenter`")));&]
[s5; p.Find(Person(`"Paul`", `"Carpenter`")) `= 1&]
[s3; &]
[s2; 10. VectorMap, ArrayMap&]
[s3; VectorMap is nothing more than a simple composition of Index
and Vector. You can use [* Add] methods to put elements into the
VectorMap&]
[s4; -|[* VectorMap]<String, Person> m;&]
[s4; -|&]
[s4; -|m.[* Add](`"1`", Person(`"John`", `"Smith`"));&]
[s4; -|m.Add(`"2`", Person(`"Carl`", `"Engles`"));&]
[s4; &]
[s4; -|Person`& p `= m.[* Add](`"3`");&]
[s4; -|p.name `= `"Paul`";&]
[s4; -|p.surname `= `"Carpenter`";&]
[s3; VectorMap provides constant access to its underlying Index and
Vector&]
[s4; -|DUMP(m.[* GetKeys]());&]
[s4; -|DUMP(m.[* GetValues]());&]
[s4; &]
[s6; m.GetKeys() `= `{ 1, 2, 3 `}&]
[s6; m.GetValues() `= `{ John Smith, Carl Engles, Paul Carpenter
`}&]
[s4; &]
[s3; You can use indices to iterate map contents&]
[s4; -|for(int i `= 0; i < m.GetCount(); i`+`+)&]
[s4; -|-|LOG(m.[* GetKey](i) << `": `" << m[* `[]i[* `]]);&]
[s4; &]
[s6; 1: John Smith&]
[s6; 2: Carl Engles&]
[s6; 3: Paul Carpenter&]
[s4; &]
[s3; You can use [* Find] method to retrieve position of element with
required key&]
[s4; -|DUMP(m.[* Find](`"2`"));&]
[s5; m.Find(`"2`") `= 1&]
[s3; or [* Get] method to retrieve corresponding value&]
[s4; -|DUMP(m.[* Get](`"2`"));&]
[s5; m.Get(`"2`") `= Carl Engles&]
[s3; Passing key not present in VectorMap as Get parameter is a logic
error, but there exists two parameter version that returns second
parameter if key is not in VectorMap&]
[s4; -|DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&]
[s5; m.Get(`"33`", Person(`"unknown`", `"person`")) `= unknown person&]
[s3; As with Index, you can use [* Unlink] to make elements invisible
for Find operations&]
[s4; -|m.Unlink(1);&]
[s4; -|DUMP(m.Find(`"2`"));&]
[s5; m.Find(`"2`") `= `-1&]
[s3; You can use [* SetKey] method to change the key of the element&]
[s4; -|m.[* SetKey](1, `"33`");&]
[s4; -|DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&]
[s5; m.Get(`"33`", Person(`"unknown`", `"person`")) `= Carl Engles&]
[s3; If there are more elements with the same key in VectorMap, you
can iterate them using [* FindNext] method:&]
[s4; -|m.Add(`"33`", Person(`"Peter`", `"Pan`"));&]
[s4; &]
[s6; m.GetKeys() `= `{ 1, 33, 3, 33 `}&]
[s6; m.GetValues() `= `{ John Smith, Carl Engles, Paul Carpenter,
Peter Pan `}&]
[s4; &]
[s4; -|int q `= m.Find(`"33`");&]
[s4; -|while(q >`= 0) `{&]
[s4; -|-|cout << m`[q`] << `'n`';&]
[s4; -|-|q `= m.[* FindNext](q);&]
[s4; -|`}&]
[s4; -|&]
[s6; Carl Engles&]
[s6; Peter Pan&]
[s4; &]
[s3; You can reuse unlinked positions using [* Put] method:&]
[s4; -|m.[* UnlinkKey](`"33`");&]
[s4; -|m.[* Put](`"22`", Person(`"Ali`", `"Baba`"));&]
[s4; -|m.Put(`"44`", Person(`"Ivan`", `"Wilks`"));&]
[s4; &]
[s6; m.GetKeys() `= `{ 1, 22, 3, 44 `}&]
[s6; m.GetValues() `= `{ John Smith, Ali Baba, Paul Carpenter, Ivan
Wilks `}&]
[s4; &]
[s3; [* GetSortOrder] algorithm returns order of elements as Vector<int>
container. You can use it to order content of VectorMap without
actually moving its elements&]
[s4; -|bool operator<(const Person`& a, const Person`& b)&]
[s4; -|`{&]
[s4; -|-|return a.surname `=`= b.surname ? a.name < b.name&]
[s4; -| : a.surname < b.surname;&]
[s4; -|`}&]
[s4; &]
[s4; .......&]
[s4; &]
[s4; -|Vector<int> order `= [* GetSortOrder](m.GetValues());&]
[s5; order `= `{ 1, 2, 0, 3 `}&]
[s4; -|for(int i `= 0; i < order.GetCount(); i`+`+)&]
[s4; -|-|cout << m.GetKey(order`[i`]) << `": `" << m`[order`[i`]`] <<
`'n`';&]
[s4; &]
[s6; 22: Ali Baba&]
[s6; 3: Paul Carpenter&]
[s6; 1: John Smith&]
[s6; 44: Ivan Wilks&]
[s4; &]
[s3; You can get Vector of values or keys using [* PickValues] resp.
[* PickKeys] methods in low constant time, while destroying content
of source VectorMap&]
[s4; -|Vector<Person> ps `= m.[* PickValues]();&]
[s5; ps `= `{ John Smith, Ali Baba, Paul Carpenter, Ivan Wilks `}&]
[s3; If type of values does not satisfy requirements for Vector elements
or if references to elements are needed, you can use [* ArrayMap]
instead&]
[s4; -|[* ArrayMap]<String, Number> am;&]
[s4; -|am.Create<Integer>(`"A`").n `= 11;&]
[s4; -|am.Create<Double>(`"B`").n `= 2.1;&]
[s4; &]
[s6; am.GetKeys() `= `{ A, B `}&]
[s6; am.GetValues() `= `{ 11, 2.1 `}&]
[s4; &]
[s4; -|DUMP(am.Get(`"A`"));&]
[s4; -|DUMP(am.Find(`"B`"));&]
[s4; &]
[s6; am.Get(`"A`") `= 11&]
[s6; am.Find(`"B`") `= 1&]
[s3; &]
[s2; 11. One&]
[s3; One is a container that can store none or one element of T or
derived from T. It functionally quite similiar to std`::unique`_ptr,
but has some more convenient features (like Create method).&]
[s4; struct Base `{&]
[s4; -|virtual String Get() `= 0;&]
[s4; -|virtual `~Base() `{`}&]
[s4; `};&]
[s4; &]
[s4; struct Derived1 : Base `{&]
[s4; -|virtual String Get() `{ return `"Derived1`"; `}&]
[s4; `};&]
[s4; &]
[s4; struct Derived2 : Base `{&]
[s4; -|virtual String Get() `{ return `"Derived2`"; `}&]
[s4; `};&]
[s4; &]
[s4; void MakeDerived1(One<Base>`& t)&]
[s4; `{&]
[s4; -|t.Create<Derived1>();&]
[s4; `}&]
[s4; &]
[s4; void MakeDerived2(One<Base>`& t)&]
[s4; `{&]
[s4; -|t.Create<Derived2>();&]
[s4; `}&]
[s4; &]
[s4; .......&]
[s4; -|[* One]<Base> s;&]
[s3; Operator bool of one returns true if it contains an element:&]
[s4; -|DUMP([* (bool)]s);&]
[s4; &]
[s6; (bool)s `= false&]
[s4; &]
[s4; -|&]
[s4; -|s.[* Create]<Derived1>();&]
[s4; -|DUMP((bool)s);&]
[s4; -|DUMP(s`->Get());&]
[s4; &]
[s6; (bool)s `= true&]
[s6; s`->Get() `= Derived1&]
[s4; &]
[s3; Clear method removes the element from One:&]
[s4; -|s.[* Clear]();&]
[s4; -|DUMP((bool)s);&]
[s4; &]
[s6; (bool)s `= false&]
[s4; &]
[s4; &]
[s3; One represents a convenient and recommended method how to deal
with class factories in U`+`+: Define them as a function (or method)
with reference to One parameter, e.g.:&]
[s4; void MakeDerived1(One<Base>`& t)&]
[s4; `{&]
[s4; -|t.Create<Derived1>();&]
[s4; `}&]
[s4; &]
[s4; void MakeDerived2(One<Base>`& t)&]
[s4; `{&]
[s4; -|t.Create<Derived2>();&]
[s4; `}&]
[s4; &]
[s4; VectorMap<int, void (`*)(One<Base>`&)> factories;&]
[s4; &]
[s4; INITBLOCK `{&]
[s4; -|factories.Add(0, MakeDerived1);&]
[s4; -|factories.Add(1, MakeDerived2);&]
[s4; `};&]
[s4; &]
[s4; void Create(One<Base>`& t, int what)&]
[s4; `{&]
[s4; -|(`*factories.Get(what))(t);&]
[s4; `}&]
[s3; &]
[s2; 12. Any&]
[s3; Any is a container that can contain none or one element of [*/ any]
type, the only requirement is that the type has default constructor.
Important thing to remember is that [* Is] method matches [/ exact]
type ignoring class hierarchies (FileIn is derived from Stream,
but if Any contains FileIn, Is<Stream>() returns false).&]
[s4; void Do([* Any]`& x)&]
[s4; `{&]
[s4; -|if(x.[* Is]<String>())&]
[s4; -|-|LOG(`"String: `" << x.[* Get]<String>());&]
[s4; -|if(x.[* Is]<FileIn>()) `{&]
[s4; -|-|LOG(`"`-`-`- File: `");&]
[s4; -|-|LOG(LoadStream(x.[* Get]<FileIn>()));&]
[s4; -|-|LOG(`"`-`-`-`-`-`-`-`-`-`-`");&]
[s4; -|`}&]
[s4; -|if(x.[* IsEmpty]())&]
[s4; -|-|LOG(`"empty`");&]
[s4; `}&]
[s4; &]
[s4; .....&]
[s4; &]
[s4; -|Any x;&]
[s4; -|x.[* Create]<String>() `= `"Hello!`";&]
[s4; -|Do(x);&]
[s4; -|x.[* Create]<FileIn>().Open(GetDataFile(`"Ntl12.cpp`"));&]
[s4; -|Do(x);&]
[s4; -|x.[* Clear]();&]
[s4; -|Do(x);&]
[s3; &]
[s2; 13. InVector, InArray&]
[s3; InVector and InArray are vector types quite similar to Vector/Array,
but they trade the speed of operator`[`] with the ability to
insert or remove elements at any position quickly. You can expect
operator`[`] to be about 10 times slower than in Vector (but
that is still very fast), while Insert at any position scales
well up to hundreds of megabytes of data (e.g. InVector containing
100M of String elements is handled without problems).&]
[s4; -|[* InVector]<int> v;&]
[s4; -|for(int i `= 0; i < 1000000; i`+`+)&]
[s4; -|-|v.Add(i);&]
[s4; -|v.[* Insert](0, `-1); // This is fast&]
[s4; &]
[s3; While the interface of InVector/InArray is almost identical
to Vector/Array, InVector/InArray in addition implements FindLowerBound/FindUpper
Bound functions `- while normal random access algorithms work,
it is possible to provide InVector specific optimization that
basically matches the performace of Find`*Bound on sample Vector.&]
[s4; &]
[s4; -|DUMP(v.[* FindLowerBound](55));&]
[s3; &]
[s2; 14. SortedIndex, SortedVectorMap, SortedArrayMap&]
[s3; SortedIndex is similar to regular Index, but keeps its elements
in sorted order (sorting predicate is a template parameter, defaults
to StdLess). Implementation is using InVector, so it works fine
even with very large number of elements (performance is similar
to tree based std`::set). Unlike Index, SortedIndex provides lower/upper
bounds searches, so it allow range search.&]
[s4; -|[* SortedIndex]<int> x;&]
[s4; -|x.Add(5);&]
[s4; -|x.Add(3);&]
[s4; -|x.Add(7);&]
[s4; -|x.Add(1);&]
[s4; -|&]
[s4; -|DUMPC(x);&]
[s4; -|DUMP(x.[* Find](3));&]
[s4; -|DUMP(x.[* Find](3));&]
[s4; -|DUMP(x.[* FindLowerBound](3));&]
[s4; -|DUMP(x.[* FindUpperBound](6));&]
[s4; &]
[s3; SortedVectorMap and SortedArrayMap are then SortedIndex based
equivalents to VectorMap/ArrayMap `- maps that keep keys sorted:&]
[s4; -|[* SortedVectorMap]<String, int> m;&]
[s4; -|m.Add(`"zulu`", 11);&]
[s4; -|m.Add(`"frank`", 12);&]
[s4; -|m.Add(`"alfa`", 13);&]
[s4; -|&]
[s4; -|DUMPM(m);&]
[s4; -|DUMP(m.[* Get](`"zulu`"));&]
[s3; &]
[s2; 15. Tuples&]
[s3; U`+`+ has template classes Tuple2, Tuple3 and Tuple4 for combining
2`-4 values with different types. These are quite similiar to
std`::tuple class, with some advantages.&]
[s3; To create a Tuple value, you can use the Tuple function. If
correct types canot be deduced from parameters, you can specify
them explicitly:&]
[s4; -|Tuple3<int, String, String> x `= [* Tuple]<int, String, String>(12,
`"hello`", `"world`");&]
[s4; &]
[s3; Individual values are accessible as members a .. d:&]
[s4; &]
[s4; -|DUMP(x.a);&]
[s4; -|DUMP(x.b);&]
[s4; -|DUMP(x.c);&]
[s4; -|&]
[s3; As long as all individual types have conversion to String (AsString),
the tuple also has such conversion and thus can e.g. be easily
logged:&]
[s4; &]
[s4; -|DUMP(x);&]
[s4; &]
[s3; Also, as long as individual types have defined GetHashValue,
so does Tuple:&]
[s4; &]
[s4; -|DUMP(GetHashValue(x));&]
[s4; &]
[s3; As long as individual types have defined operator`=`=, Tuple
has defined operator`=`= and operator!`=&]
[s4; &]
[s4; -|Tuple3<int, String, String> y `= x;&]
[s4; -|DUMP(x !`= y);&]
[s4; &]
[s3; Finally, as long as all individual types have defined SgnCompare
(most U`+`+ types have), Tuple has SgnCompare, Compare method
and operators <, <`=, >, >`=:&]
[s4; &]
[s4; -|DUMP(x.Compare(y));&]
[s4; -|&]
[s4; -|y.b `= `"a`";&]
[s4; -|&]
[s4; -|DUMP(SgnCompare(x, y));&]
[s4; -|DUMP(x < y);&]
[s4; -|&]
[s4; &]
[s3; U`+`+ Tuples are strictly designed as POD type, which allows
POD arrays to be intialized with classic C style:&]
[s4; -|static Tuple2<int, const char `*> map`[`] `= `{&]
[s4; -|-|`{ 1, `"one`" `},&]
[s4; -|-|`{ 2, `"one`" `},&]
[s4; -|-|`{ 3, `"one`" `},&]
[s4; -|`};&]
[s4; -|&]
[s3; &]
[s3; Simple FindTuple template function is provided to search for
tuple based on the first value:&]
[s3; &]
[s2; 15. Sorting&]
[s3; IndexSort is sort variant that is able to sort two random access
container (like Vector or Array) of the same size, based on values
in on of containers:&]
[s4; -|Vector<int> a;&]
[s4; -|Vector<String> b;-|&]
[s4; -|&]
[s4; -|a << 5 << 10 << 2 << 9 << 7 << 3;&]
[s4; -|b << `"five`" << `"ten`" << `"two`" << `"nine`" << `"seven`"
<< `"three`";&]
[s4; -|&]
[s4; -|[* IndexSort](a, b);&]
[s4; &]
[s6; a `= `[2, 3, 5, 7, 9, 10`]&]
[s6; b `= `[two, three, five, seven, nine, ten`]&]
[s4; &]
[s4; -|[* IndexSort](b, a);&]
[s4; &]
[s6; a `= `[5, 9, 7, 10, 3, 2`]&]
[s6; b `= `[five, nine, seven, ten, three, two`]&]
[s3; Order of sorted items is defined by sorting predicate. By default,
operator< comparing items of container is used (this predicate
can be provided by StdLess template), but it is possible to specify
different sorting order, e.g. by using predefined StdGreater
predicate:&]
[s4; -|Sort(a, [* StdGreater]<int>());&]
[s4; &]
[s6; a `= `[10, 9, 7, 5, 3, 2`]&]
[s3; Sometimes, instead of sorting items in the container, it is
useful to know the order of items as sorted, using GetSortOrder:&]
[s4; -|Vector<int> o `= [* GetSortOrder](a);&]
[s4; &]
[s6; o `= `[5, 4, 3, 2, 1, 0`]&]
[s3; FieldRelation predefined predicate can be used to sort container
of structures by specific field:&]
[s4; -|Vector<Point> p;&]
[s4; -|p << Point(5, 10) << Point(7, 2) << Point(4, 8) << Point(1,
0);&]
[s4; -|&]
[s4; -|Sort(p, [* FieldRelation](`&Point`::x, StdLess<int>()));&]
[s4; &]
[s6; p `= `[`[1, 0`], `[4, 8`], `[5, 10`], `[7, 2`]`]&]
[s3; MethodRelation is good for sorting of structures based on constant
method of structure:-|&]
[s4; struct Foo `{&]
[s4; -|String a;&]
[s4; -|&]
[s4; -|int [* Get]() const `{ return atoi(a); `}&]
[s4; -|....&]
[s4; `};&]
[s4; ....&]
[s4; -|Array<Foo> f;&]
[s4; -|f << `"12`" << `"1`" << `"10`" << `"7`" << `"5`";&]
[s4; -|&]
[s4; -|Sort(f, [* MethodRelation](`&Foo`::[* Get], StdLess<int>()));&]
[s4; &]
[s6; f `= `[1, 5, 7, 10, 12`]&]
[s3; Normal Sort is not stable `- equal items can appear in sorted
sequence in random order. If maintaining original order of equal
items is important, use StableSort variant (with slight performance
penalty):&]
[s4; -|Vector<Point> t;&]
[s4; -|t << Point(10, 10) << Point(7, 1) << Point(7, 2) << Point(7,
3) << Point(1, 0);&]
[s4; -|[* StableSort](t, FieldRelation(`&Point`::x, StdLess<int>()));&]
[s4; &]
[s6; t `= `[`[1, 0`], `[7, 1`], `[7, 2`], `[7, 3`], `[10, 10`]`]&]
[s3; &]
[s2; Recommended tutorials:&]
[s3; If you want to learn more, we have several tutorials that you
can find useful:&]
[s3;l160;i150;O0; [^topic`:`/`/Core`/srcdoc`/CoreTutorial`$en`-us^ U`+`+
Core value types] `- if you miss this one`- it it a good occasion
to catch up. Here you will learn about Core basics.&]
[s0; [^topic`:`/`/Sql`/srcdoc`/tutorial`$en`-us^ SQL] `- learn how
to use databases with U`+`+ framework.&]
[s0; ]]

View file

@ -0,0 +1,5 @@
#ifndef _tutorial2_icpp_init_stub
#define _tutorial2_icpp_init_stub
#include "Core/init"
#include "RichEdit/init"
#endif

View file

@ -0,0 +1,41 @@
#include "Tutorial.h"
GUI_APP_MAIN
{
DO(Logging);
DO(StringTutorial);
DO(StringBufferTutorial);
DO(WStringTutorial);
DO(DateTime);
DO(AsStringTutorial);
DO(ValueTutorial);
DO(NullTutorial);
DO(Value2Tutorial);
DO(CombineHashTutorial);
DO(ComparableTutorial);
DO(Vector1);
DO(Vector2);
DO(Transfer);
DO(ContainerClientTypes);
DO(ArrayTutorial);
DO(PolyArray);
DO(Bidirectional);
DO(IndexTutorial);
DO(IndexClient);
DO(Map);
DO(OneTutorial);
DO(AnyTutorial);
DO(InVectorTutorial);
DO(SortedMap);
DO(TupleTutorial);
DO(Range);
DO(Sorting);
DO(FunctionTutorial);
MakeTutorial();
}