/*************** bson CPP Declares Source Code File (.H) ***************/ /* Name: bson.cpp Version 1.0 */ /* */ /* (C) Copyright to the author Olivier BERTRAND 2020 */ /* */ /* This file contains the BJSON classes functions. */ /***********************************************************************/
/***********************************************************************/ /* Include relevant sections of the MariaDB header file. */ /***********************************************************************/ #include <my_global.h> #include <m_string.h>
/***********************************************************************/ /* Include application header files: */ /* global.h is header containing all global declarations. */ /* plgdbsem.h is header containing the DB application declarations. */ /* bson.h is header containing the BSON classes declarations. */ /***********************************************************************/ #include"global.h" #include"plgdbsem.h" #include"bson.h"
#ifdefined(_WIN32) #define EL "\r\n" #else #define EL "\n" #undef SE_CATCH // Does not work for Linux #endif
int GetJsonDefPrec(void);
#ifdefined(SE_CATCH) /**************************************************************************/ /* This is the support of catching C interrupts to prevent crashes. */ /**************************************************************************/ #include <eh.h>
class SE_Exception { public:
SE_Exception(unsignedint n, PEXCEPTION_RECORD p) : nSE(n), eRec(p) {}
~SE_Exception() {}
unsignedint nSE;
PEXCEPTION_RECORD eRec;
}; // end of class SE_Exception
void trans_func(unsignedint u, _EXCEPTION_POINTERS* pExp) { throw SE_Exception(u, pExp->ExceptionRecord);
} // end of trans_func
/* --------------------------- Class BDOC ---------------------------- */
/***********************************************************************/ /* BDOC constructor. */ /***********************************************************************/
BDOC::BDOC(PGLOBAL G) : BJSON(G, NULL)
{
jp = NULL;
s = NULL;
len = 0;
pretty = 3;
pty[0] = pty[1] = pty[2] = true;
comma = false;
} // end of BDOC constructor
/***********************************************************************/ /* Parse a json string. */ /* Note: when pretty is not known, the caller set pretty to 3. */ /***********************************************************************/
PBVAL BDOC::ParseJson(PGLOBAL g, char* js, size_t lng)
{
size_t i; bool b = false;
PBVAL bvp = NULL;
s = js;
len = lng;
xtrc(1, "BDOC::ParseJson: s=%.10s len=%zd\n", s, len);
if (!s || !len) {
strcpy(g->Message, "Void JSON object"); return NULL;
} // endif s
// Trying to guess the pretty format if (s[0] == '[' && (s[1] == '\n' || (s[1] == '\r' && s[2] == '\n')))
pty[0] = false;
try {
bvp = NewVal();
bvp->Type = TYPE_UNKNOWN;
for (i = 0; i < len; i++) switch (s[i]) { case'[': if (bvp->Type != TYPE_UNKNOWN)
bvp->To_Val = ParseAsArray(i); else
bvp->To_Val = ParseArray(++i);
snprintf(g->Message, sizeof(g->Message), "Unexpected ',' (pretty=%d)", pretty); throw3; case'(':
b = true; break; case')': if (b) {
b = false; break;
} // endif b /* fall through */ default: if (bvp->Type != TYPE_UNKNOWN) {
bvp->To_Val = ParseAsArray(i);
bvp->Type = TYPE_JAR;
} elseif ((bvp->To_Val = MOF(ParseValue(i, NewVal()))))
bvp->Type = TYPE_JVAL; else throw4;
break;
}; // endswitch s[i]
if (bvp->Type == TYPE_UNKNOWN)
snprintf(g->Message, sizeof(g->Message), "Invalid Json string '%.*s'", MY_MIN((int)len, 50), s); elseif (pretty == 3) { for (i = 0; i < 3; i++) if (pty[i]) {
pretty = i; break;
} // endif pty
} // endif ptyp
} catch (int n) { if (trace(1))
htrc("Exception %d: %s\n", n, G->Message);
GetMsg(g);
bvp = NULL;
} catch (constchar* msg) {
strcpy(g->Message, msg);
bvp = NULL;
} // end catch
return bvp;
} // end of ParseJson
/***********************************************************************/ /* Parse several items as being in an array. */ /***********************************************************************/
OFFSET BDOC::ParseAsArray(size_t& i) { if (pty[0] && (!pretty || pretty > 2)) {
OFFSET jsp;
err:
snprintf(G->Message, sizeof(G->Message), "Unexpected character '%c' near %.*s", s[i], (int) ARGS); throw3;
} // end of ParseValue
/***********************************************************************/ /* Unescape and parse a JSON string. */ /***********************************************************************/
OFFSET BDOC::ParseString(size_t& i)
{
uchar* p; int n = 0;
// Be sure of memory availability if (((size_t)len + 1 - i) > ((PPOOLHEADER)G->Sarea)->FreeBlk) throw("ParseString: Out of memory");
// The size to allocate is not known yet
p = (uchar*)BsonSubAlloc(0);
for (; i < len; i++) switch (s[i]) { case'"':
p[n++] = 0;
BsonSubAlloc(n); return MOF(p); case'\\': if (++i < len) { if (s[i] == 'u') { if (len - i > 5) { // if (charset == utf8) { char xs[5];
uint hex;
return jp->WriteChr('}');
} // end of SerializeObject
/***********************************************************************/ /* Serialize a JSON Value. */ /***********************************************************************/ bool BDOC::SerializeValue(PBVAL jvp, bool b)
{ char buf[64];
if (jvp) switch (jvp->Type) { case TYPE_JAR: return SerializeArray(jvp->To_Val, false); case TYPE_JOB: return SerializeObject(jvp->To_Val); case TYPE_BOOL: return jp->WriteStr(jvp->B ? "true" : "false"); case TYPE_STRG: case TYPE_DTM: if (b) { return jp->WriteStr(MZP(jvp->To_Val));
} else return jp->Escape(MZP(jvp->To_Val));
case TYPE_INTG:
snprintf(buf, sizeof(buf), "%d", jvp->N); return jp->WriteStr(buf); case TYPE_BINT:
snprintf(buf, sizeof(buf), "%lld", *(longlong*)MakePtr(Base, jvp->To_Val)); return jp->WriteStr(buf); case TYPE_FLOAT:
snprintf(buf, sizeof(buf), "%.*f", jvp->Nd, jvp->F); return jp->WriteStr(buf); case TYPE_DBL:
snprintf(buf, sizeof(buf), "%.*lf", jvp->Nd, *(double*)MakePtr(Base, jvp->To_Val)); return jp->WriteStr(buf); case TYPE_NULL: return jp->WriteStr("null"); case TYPE_JVAL: return SerializeValue(MVP(jvp->To_Val)); default: return jp->WriteStr("???"); // TODO
} // endswitch Type
return jp->WriteStr("null");
} // end of SerializeValue
/* --------------------------- Class BJSON --------------------------- */
/***********************************************************************/ /* Program for sub-allocating Bjson structures. */ /***********************************************************************/ void* BJSON::BsonSubAlloc(size_t size)
{
PPOOLHEADER pph; /* Points on area header. */ void* memp = G->Sarea;
size = ((size + 3) / 4) * 4; /* Round up size to multiple of 4 */
pph = (PPOOLHEADER)memp;
if (size > pph->FreeBlk) { /* Not enough memory left in pool */
snprintf(G->Message, sizeof(G->Message), "Not enough memory for request of %zd (used=%zd free=%zd)",
size, pph->To_Free, pph->FreeBlk);
xtrc(1, "BsonSubAlloc: %s\n", G->Message);
if (Throw) throw(1234); else return NULL;
} /* endif size OS32 code */
// Do the suballocation the simplest way
memp = MakePtr(memp, pph->To_Free); /* Points to suballocated block */
pph->To_Free += size; /* New offset of pool free block */
pph->FreeBlk -= size; /* New size of pool free block */
xtrc(16, "Done memp=%p used=%zd free=%zd\n",
memp, pph->To_Free, pph->FreeBlk); return memp;
} // end of BsonSubAlloc
/*********************************************************************************/ /* Program for SubSet re-initialization of the memory pool. */ /*********************************************************************************/
PSZ BJSON::NewStr(PSZ str)
{ if (str) {
PSZ sm = (PSZ)BsonSubAlloc(strlen(str) + 1);
strcpy(sm, str); return sm;
} else return NULL;
} // end of NewStr
/*********************************************************************************/ /* Program for SubSet re-initialization of the memory pool. */ /*********************************************************************************/ void BJSON::SubSet(bool b)
{
PPOOLHEADER pph = (PPOOLHEADER)G->Sarea;
/***********************************************************************/ /* Return the number of pairs in this object. */ /***********************************************************************/ int BJSON::GetObjectSize(PBVAL bop, bool b)
{
CheckType(bop, TYPE_JOB); int n = 0;
for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) // If b return only non null pairs if (!b || (brp->Vlp.To_Val && brp->Vlp.Type != TYPE_NULL))
n++;
return n;
} // end of GetObjectSize
/***********************************************************************/ /* Add a new pair to an Object and return it. */ /***********************************************************************/
PBVAL BJSON::AddPair(PBVAL bop, PSZ key, int type)
{
CheckType(bop, TYPE_JOB);
PBPR brp;
OFFSET nrp = NewPair(key, type);
if (bop->To_Val) { for (brp = GetObject(bop); brp->Vlp.Next; brp = GetNext(brp));
brp->Vlp.Next = nrp;
} else
bop->To_Val = nrp;
bop->Nd++; return GetVlp(MPP(nrp));
} // end of AddPair
/***********************************************************************/ /* Return all object keys as an array. */ /***********************************************************************/
PBVAL BJSON::GetKeyList(PBVAL bop)
{
CheckType(bop, TYPE_JOB);
PBVAL arp = NewVal(TYPE_JAR);
/***********************************************************************/ /* Get the value corresponding to the given key. */ /***********************************************************************/
PBVAL BJSON::GetKeyValue(PBVAL bop, PSZ key)
{
CheckType(bop, TYPE_JOB);
for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) if (!strcmp(GetKey(brp), key)) return GetVlp(brp);
return NULL;
} // end of GetKeyValue;
/***********************************************************************/ /* Return the text corresponding to all keys (XML like). */ /***********************************************************************/
PSZ BJSON::GetObjectText(PGLOBAL g, PBVAL bop, PSTRG text)
{
CheckType(bop, TYPE_JOB);
PBPR brp = GetObject(bop);
if (brp) { bool b;
if (!text) {
text = new(g) STRING(g, 256);
b = true;
} else { if (text->GetLastChar() != ' ')
text->Append(' ');
b = false;
} // endif text
if (b && !brp->Vlp.Next && !strcmp(MZP(brp->Key), "$date")) { int i;
PSZ s;
GetValueText(g, GetVlp(brp), text);
s = text->GetStr();
i = (s[1] == '-' ? 2 : 1);
if (IsNum(s + i)) { // Date is in milliseconds int j = text->GetLength();
if (j >= 4 + i) {
s[j - 3] = 0; // Change it to seconds
text->SetLength((uint)strlen(s));
} else
text->Set(" 0");
if (b) {
text->Trim(); return text->GetStr();
} // endif b
} // endif bop
return NULL;
} // end of GetObjectText;
/***********************************************************************/ /* Set or add a value corresponding to the given key. */ /***********************************************************************/ void BJSON::SetKeyValue(PBVAL bop, OFFSET bvp, PSZ key)
{
CheckType(bop, TYPE_JOB);
PBPR brp, prp = NULL;
if (bop->To_Val) { for (brp = GetObject(bop); brp; brp = GetNext(brp)) if (!strcmp(GetKey(brp), key)) break; else
prp = brp;
if (!brp)
brp = MPP(prp->Vlp.Next = NewPair(key));
} else
brp = MPP(bop->To_Val = NewPair(key));
SetPairValue(brp, MVP(bvp));
bop->Nd++;
} // end of SetKeyValue
/***********************************************************************/ /* Delete a value corresponding to the given key. */ /***********************************************************************/ bool BJSON::DeleteKey(PBVAL bop, PCSZ key)
{
CheckType(bop, TYPE_JOB);
PBPR brp, pbrp = NULL;
for (brp = GetObject(bop); brp; brp = GetNext(brp)) if (!strcmp(MZP(brp->Key), key)) { if (pbrp) {
pbrp->Vlp.Next = brp->Vlp.Next;
} else
bop->To_Val = brp->Vlp.Next;
bop->Nd--; returntrue;;
} else
pbrp = brp;
returnfalse;
} // end of DeleteKey
/***********************************************************************/ /* True if void or if all members are nulls. */ /***********************************************************************/ bool BJSON::IsObjectNull(PBVAL bop)
{
CheckType(bop, TYPE_JOB);
for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) if (brp->Vlp.To_Val && brp->Vlp.Type != TYPE_NULL) returnfalse;
/***********************************************************************/ /* Return the number of values in this object. */ /***********************************************************************/ int BJSON::GetArraySize(PBVAL bap, bool b)
{
CheckType(bap, TYPE_JAR); int n = 0;
for (PBVAL bvp = GetArray(bap); bvp; bvp = GetNext(bvp)) // If b, return only non null values if (!b || bvp->Type != TYPE_NULL)
n++;
return n;
} // end of GetArraySize
/***********************************************************************/ /* Get the Nth value of an Array. */ /***********************************************************************/
PBVAL BJSON::GetArrayValue(PBVAL bap, int n)
{
CheckType(bap, TYPE_JAR); int i = 0;
if (n < 0)
n += GetArraySize(bap);
for (PBVAL bvp = GetArray(bap); bvp; bvp = GetNext(bvp), i++) if (i == n) return bvp;
return NULL;
} // end of GetArrayValue
/***********************************************************************/ /* Add a Value to the Array Value list. */ /***********************************************************************/ void BJSON::AddArrayValue(PBVAL bap, OFFSET nbv, int* x)
{
CheckType(bap, TYPE_JAR); int i = 0;
PBVAL bvp, lbp = NULL;
if (!nbv)
nbv = MOF(NewVal());
for (bvp = GetArray(bap); bvp; bvp = GetNext(bvp), i++) if (x && i == *x) break; else
lbp = bvp;
/***********************************************************************/ /* Set the nth Value of the Array Value list or add it. */ /***********************************************************************/ void BJSON::SetArrayValue(PBVAL bap, PBVAL nvp, int n)
{
CheckType(bap, TYPE_JAR); int i = 0;
PBVAL bvp = NULL;
for (bvp = GetArray(bap); i < n; i++, bvp = bvp ? GetNext(bvp) : NULL) if (!bvp)
AddArrayValue(bap, NewVal());
if (!bvp)
AddArrayValue(bap, MOF(nvp)); else
SetValueVal(bvp, nvp);
} // end of SetValue
/***********************************************************************/ /* Return the text corresponding to all values. */ /***********************************************************************/
PSZ BJSON::GetArrayText(PGLOBAL g, PBVAL bap, PSTRG text)
{
CheckType(bap, TYPE_JAR);
if (bap->To_Val) { bool b;
if (!text) {
text = new(g) STRING(g, 256);
b = true;
} else { if (text->GetLastChar() != ' ')
text->Append(" ("); else
text->Append('(');
if (bvp->Next)
text->Append(", "); elseif (!b)
text->Append(')');
} // endfor bvp
if (b) {
text->Trim(); return text->GetStr();
} // endif b
} // endif To_Val
return NULL;
} // end of GetText;
/***********************************************************************/ /* Delete a Value from the Arrays Value list. */ /***********************************************************************/ bool BJSON::DeleteValue(PBVAL bap, int n)
{
CheckType(bap, TYPE_JAR); int i = 0;
PBVAL bvp, pvp = NULL;
for (bvp = GetArray(bap); bvp; i++, bvp = GetNext(bvp)) if (i == n) { if (pvp)
pvp->Next = bvp->Next; else
bap->To_Val = bvp->Next;
bap->Nd--; returntrue;;
} else
pvp = bvp;
returnfalse;
} // end of DeleteValue
/***********************************************************************/ /* True if void or if all members are nulls. */ /***********************************************************************/ bool BJSON::IsArrayNull(PBVAL bap)
{
CheckType(bap, TYPE_JAR);
for (PBVAL bvp = GetArray(bap); bvp; bvp = GetNext(bvp)) if (bvp->Type != TYPE_NULL) returnfalse;
/***********************************************************************/ /* Sub-allocate and clear a BVAL. */ /***********************************************************************/
PBVAL BJSON::NewVal(int type)
{
PBVAL bvp = (PBVAL)BsonSubAlloc(sizeof(BVAL));
bvp->To_Val = 0;
bvp->Nd = 0;
bvp->Type = type;
bvp->Next = 0; return bvp;
} // end of SubAllocVal
/***********************************************************************/ /* Sub-allocate and initialize a BVAL as type. */ /***********************************************************************/
PBVAL BJSON::SubAllocVal(OFFSET toval, int type, short nd)
{
PBVAL bvp = NewVal(type);
bvp->To_Val = toval;
bvp->Nd = nd; return bvp;
} // end of SubAllocVal
/***********************************************************************/ /* Sub-allocate and initialize a BVAL as string. */ /***********************************************************************/
PBVAL BJSON::SubAllocStr(OFFSET toval, short nd)
{
PBVAL bvp = NewVal(TYPE_STRG);
bvp->To_Val = toval;
bvp->Nd = nd; return bvp;
} // end of SubAllocStr
/***********************************************************************/ /* Allocate a BVALUE with a given string or numeric value. */ /***********************************************************************/
PBVAL BJSON::NewVal(PVAL valp)
{
PBVAL vlp = NewVal();
SetValue(vlp, valp); return vlp;
} // end of SubAllocVal
/***********************************************************************/ /* Sub-allocate and initialize a BVAL from another BVAL. */ /***********************************************************************/
PBVAL BJSON::DupVal(PBVAL bvlp)
{ if (bvlp) {
PBVAL bvp = NewVal();
/***********************************************************************/ /* Return the size of value's value. */ /***********************************************************************/ int BJSON::GetSize(PBVAL vlp, bool b)
{ switch (vlp->Type) { case TYPE_JAR: return GetArraySize(vlp); case TYPE_JOB: return GetObjectSize(vlp); default: return1;
} // enswitch Type
} // end of GetSize
PBVAL BJSON::GetBson(PBVAL bvp)
{
PBVAL bp = NULL;
switch (bvp->Type) { case TYPE_JAR:
bp = MVP(bvp->To_Val); break; case TYPE_JOB:
bp = GetVlp(MPP(bvp->To_Val)); break; default:
bp = bvp; break;
} // endswitch Type
return bp;
} // end of GetBson
/***********************************************************************/ /* Return the Value's as a Value struct. */ /***********************************************************************/
PVAL BJSON::GetValue(PGLOBAL g, PBVAL vp)
{ double d;
PVAL valp;
PBVAL vlp = vp->Type == TYPE_JVAL ? MVP(vp->To_Val) : vp;
switch (vlp->Type) { case TYPE_STRG: case TYPE_DBL: case TYPE_BINT:
valp = AllocateValue(g, MP(vlp->To_Val), vlp->Type, vlp->Nd); break; case TYPE_INTG: case TYPE_BOOL:
valp = AllocateValue(g, vlp, vlp->Type); break; case TYPE_FLOAT:
d = (double)vlp->F;
valp = AllocateValue(g, &d, TYPE_DOUBLE, vlp->Nd); break; default:
valp = NULL; break;
} // endswitch Type
return valp;
} // end of GetValue
/***********************************************************************/ /* Return the Value's Integer value. */ /***********************************************************************/ int BJSON::GetInteger(PBVAL vp) { int n;
PBVAL vlp = (vp->Type == TYPE_JVAL) ? MVP(vp->To_Val) : vp;
switch (vlp->Type) { case TYPE_INTG:
n = vlp->N; break; case TYPE_FLOAT:
n = (int)vlp->F; break; case TYPE_DTM: case TYPE_STRG:
n = atoi(MZP(vlp->To_Val)); break; case TYPE_BOOL:
n = (vlp->B) ? 1 : 0; break; case TYPE_BINT:
n = (int)*(longlong*)MP(vlp->To_Val); break; case TYPE_DBL:
n = (int)*(double*)MP(vlp->To_Val); break; default:
n = 0;
} // endswitch Type
switch (vlp->Type) { case TYPE_DBL:
d = *(double*)MP(vlp->To_Val); break; case TYPE_BINT:
d = (double)*(longlong*)MP(vlp->To_Val); break; case TYPE_INTG:
d = (double)vlp->N; break; case TYPE_FLOAT:
d = (double)vlp->F; break; case TYPE_DTM: case TYPE_STRG:
d = atof(MZP(vlp->To_Val)); break; case TYPE_BOOL:
d = (vlp->B) ? 1.0 : 0.0; break; default:
d = 0.0;
} // endswitch Type
switch (vlp->Type) { case TYPE_DTM: case TYPE_STRG:
p = MZP(vlp->To_Val); break; case TYPE_INTG:
snprintf(p, 32, "%d", vlp->N); break; case TYPE_FLOAT:
snprintf(p, 32, "%.*f", vlp->Nd, vlp->F); break; case TYPE_BINT:
snprintf(p, 32, "%lld", *(longlong*)MP(vlp->To_Val)); break; case TYPE_DBL:
snprintf(p, 32, "%.*lf", vlp->Nd, *(double*)MP(vlp->To_Val)); break; case TYPE_BOOL:
p = (PSZ)((vlp->B) ? "true" : "false"); break; case TYPE_NULL:
p = (PSZ)"null"; break; default:
p = NULL;
} // endswitch Type
return (p == buf) ? (PSZ)PlugDup(G, buf) : p;
} // end of GetString
break; default:
snprintf(G->Message, sizeof(G->Message), "Unsupported typ %d\n", valp->GetType()); throw(777);
} // endswitch Type
return vlp;
} // end of SetValue
/***********************************************************************/ /* Set the Value's value as the given integer. */ /***********************************************************************/ void BJSON::SetInteger(PBVAL vlp, int n)
{
vlp->N = n;
vlp->Type = TYPE_INTG;
} // end of SetInteger
/***********************************************************************/ /* Set the Value's Boolean value as a tiny integer. */ /***********************************************************************/ void BJSON::SetBool(PBVAL vlp, bool b)
{
vlp->B = b;
vlp->Type = TYPE_BOOL;
} // end of SetTiny
/***********************************************************************/ /* Set the Value's value as the given big integer. */ /***********************************************************************/ void BJSON::SetBigint(PBVAL vlp, longlong ll)
{ if (ll >= INT_MIN32 && ll <= INT_MAX32) {
vlp->N = (int)ll;
vlp->Type = TYPE_INTG;
} else {
longlong* llp = (longlong*)PlugSubAlloc(G, NULL, sizeof(longlong));
/***********************************************************************/ /* Set the Value's value as the given DOUBLE. */ /***********************************************************************/ void BJSON::SetFloat(PBVAL vlp, double d, int prec)
{ int nd = MY_MIN((prec < 0) ? GetJsonDefPrec() : prec, 16);
if (nd < 6 && d >= FLT_MIN && d <= FLT_MAX) {
vlp->F = (float)d;
vlp->Type = TYPE_FLOAT;
} else { double* dp = (double*)BsonSubAlloc(sizeof(double));
/***********************************************************************/ /* Set the Value's value as the given DOUBLE representation. */ /***********************************************************************/ void BJSON::SetFloat(PBVAL vlp, PSZ s)
{ char *p = strchr(s, '.'); int nd = 0; double d = atof(s);
if (p) { for (++p; isdigit(*p); nd++, p++); for (--p; *p == '0'; nd--, p--);
} // endif p
SetFloat(vlp, d, nd);
} // end of SetFloat
/***********************************************************************/ /* Set the Value's value as the given string. */ /***********************************************************************/ void BJSON::SetString(PBVAL vlp, PSZ s, int ci)
{
vlp->To_Val = MOF(s);
vlp->Nd = ci;
vlp->Type = TYPE_STRG;
} // end of SetString
/***********************************************************************/ /* True when its JSON or normal value is null. */ /***********************************************************************/ bool BJSON::IsValueNull(PBVAL vlp)
{ bool b;
switch (vlp->Type) { case TYPE_NULL:
b = true; break; case TYPE_JOB:
b = IsObjectNull(vlp); break; case TYPE_JAR:
b = IsArrayNull(vlp); break; default:
b = false;
} // endswitch Type
return b;
} // end of IsNull
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.