- Replaced old Tree-based parser with a faster, leaner parser.
  Currently lacking good error handling and a large test suite.
- Integrated new parser into Task object, for encode/decode.
- Replicated same basic unit tests.  Needs more.
- Fixed bug in handleShell that failed to call the new Context::initialize2.
- Removed debugging code from CmdInstall.
- Implemented format() that does not require width and precision args.
This commit is contained in:
Paul Beckingham 2011-05-20 00:18:36 -04:00
parent 05b3fa0bb6
commit 690fa6e206
8 changed files with 517 additions and 297 deletions

View file

@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// taskwarrior - a command line task list manager.
//
// Copyright 2010 - 2011, Paul Beckingham, Federico Hernandez.
// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@ -25,35 +25,361 @@
//
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <sstream>
#include <iostream> // TODO Remove.
#include <text.h>
#include <utf8.h>
#include <JSON.h>
////////////////////////////////////////////////////////////////////////////////
JSON::JSON ()
: root ("root")
json::value* json::value::parse (Nibbler& nibbler)
{
json::value* v;
if ((v = json::object::parse (nibbler)) ||
(v = json::array::parse (nibbler)) ||
(v = json::string::parse (nibbler)) ||
(v = json::number::parse (nibbler)) ||
(v = json::literal::parse (nibbler)))
return v;
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
JSON::JSON (const std::string& input)
: root ("root")
json::jtype json::value::type ()
{
return json::j_value;
}
////////////////////////////////////////////////////////////////////////////////
std::string json::value::dump ()
{
return "<value>";
}
////////////////////////////////////////////////////////////////////////////////
json::string::string (const std::string& other)
{
*this = other;
}
////////////////////////////////////////////////////////////////////////////////
json::string* json::string::parse (Nibbler& nibbler)
{
std::string value;
if (nibbler.getQuoted ('"', value, false))
{
json::string* s = new json::string ();
*(std::string*)s = value;
return s;
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
json::jtype json::string::type ()
{
return json::j_string;
}
////////////////////////////////////////////////////////////////////////////////
std::string json::string::dump ()
{
return std::string ("\"") + (std::string) *this + "\"";
}
////////////////////////////////////////////////////////////////////////////////
json::number* json::number::parse (Nibbler& nibbler)
{
int i;
double d;
if (nibbler.getNumber (d))
{
json::number* s = new json::number ();
s->_dvalue = d;
return s;
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
json::jtype json::number::type ()
{
return json::j_number;
}
////////////////////////////////////////////////////////////////////////////////
std::string json::number::dump ()
{
return format (_dvalue);
}
////////////////////////////////////////////////////////////////////////////////
json::number::operator double () const
{
return _dvalue;
}
////////////////////////////////////////////////////////////////////////////////
json::literal* json::literal::parse (Nibbler& nibbler)
{
if (nibbler.getLiteral ("null"))
{
json::literal* s = new json::literal ();
s->_lvalue = nullvalue;
return s;
}
else if (nibbler.getLiteral ("false"))
{
json::literal* s = new json::literal ();
s->_lvalue = falsevalue;
return s;
}
else if (nibbler.getLiteral ("true"))
{
json::literal* s = new json::literal ();
s->_lvalue = truevalue;
return s;
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
json::jtype json::literal::type ()
{
return json::j_literal;
}
////////////////////////////////////////////////////////////////////////////////
std::string json::literal::dump ()
{
if (_lvalue == nullvalue) return "null";
else if (_lvalue == falsevalue) return "false";
else return "true";
}
////////////////////////////////////////////////////////////////////////////////
json::array::~array ()
{
std::vector <json::value*>::iterator i;
for (i = ((std::vector <json::value*>*)this)->begin ();
i != ((std::vector <json::value*>*)this)->end ();
++i)
delete *i;
}
////////////////////////////////////////////////////////////////////////////////
json::array* json::array::parse (Nibbler& nibbler)
{
Nibbler n (nibbler);
n.skipWS ();
if (n.skip ('['))
{
n.skipWS ();
json::array* arr = new json::array ();
json::value* value;
if (value = json::value::parse (n))
{
arr->push_back (value);
value = NULL; // Not a leak. Looks like a leak.
n.skipWS ();
while (n.skip (','))
{
n.skipWS ();
if (value = json::value::parse (n))
{
arr->push_back (value);
n.skipWS ();
}
else
{
delete arr;
return NULL;
}
}
}
if (n.skip (']'))
{
nibbler = n;
return arr;
}
delete arr;
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
json::jtype json::array::type ()
{
return json::j_array;
}
////////////////////////////////////////////////////////////////////////////////
std::string json::array::dump ()
{
std::string output;
output += "[";
std::vector <json::value*>::iterator i;
for (i = ((std::vector <json::value*>*)this)->begin ();
i != ((std::vector <json::value*>*)this)->end ();
++i)
{
if (i != ((std::vector <json::value*>*)this)->begin ())
output += ",";
output += (*i)->dump ();
}
output += "]";
return output;
}
////////////////////////////////////////////////////////////////////////////////
json::object::~object ()
{
std::map <std::string, json::value*>::iterator i;
for (i = ((std::map <std::string, json::value*>*)this)->begin ();
i != ((std::map <std::string, json::value*>*)this)->end ();
++i)
delete i->second;
}
////////////////////////////////////////////////////////////////////////////////
json::object* json::object::parse (Nibbler& nibbler)
{
Nibbler n (nibbler);
n.skipWS ();
if (n.skip ('{'))
{
n.skipWS ();
json::object* obj = new json::object ();
std::string name;
json::value* value;
if (json::object::parse_pair (n, name, value))
{
obj->insert (std::pair <std::string, json::value*> (name, value));
value = NULL; // Not a leak. Looks like a leak.
n.skipWS ();
while (n.skip (','))
{
n.skipWS ();
if (json::object::parse_pair (n, name, value))
{
obj->insert (std::pair <std::string, json::value*> (name, value));
n.skipWS ();
}
else
{
delete obj;
return NULL;
}
}
}
if (n.skip ('}'))
{
nibbler = n;
return obj;
}
delete obj;
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
bool json::object::parse_pair (
Nibbler& nibbler,
std::string& name,
json::value*& val)
{
Nibbler n (nibbler);
if (n.getQuoted ('"', name, false))
{
n.skipWS ();
if (n.skip (':'))
{
n.skipWS ();
if (val = json::value::parse (n))
{
nibbler = n;
return true;
}
}
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
json::jtype json::object::type ()
{
return json::j_object;
}
////////////////////////////////////////////////////////////////////////////////
std::string json::object::dump ()
{
std::string output;
output += "{";
std::map <std::string, json::value*>::iterator i;
for (i = ((std::map <std::string, json::value*>*)this)->begin ();
i != ((std::map <std::string, json::value*>*)this)->end ();
++i)
{
if (i != ((std::map <std::string, json::value*>*)this)->begin ())
output += ",";
output += "\"" + i->first + "\":";
output += i->second->dump ();
}
output += "}";
return output;
}
////////////////////////////////////////////////////////////////////////////////
json::value* json::parse (const std::string& input)
{
json::value* root = NULL;
Nibbler n (input);
if (!parseObject (&root, n))
throw std::string ("Syntax error in request.");
n.skipWS ();
if (n.next () == '{') root = json::object::parse (n);
else if (n.next () == '[') root = json::array::parse (n);
else
throw std::string ("Error: expected '{' or '[' at position ") +
format ((int)n.cursor ());
// Check for end condition.
n.skipWS ();
if (!n.depleted ())
{
delete root;
throw std::string ("Error: extra characters found: ") + n.dump ();
}
return root;
}
////////////////////////////////////////////////////////////////////////////////
JSON::~JSON ()
{
}
////////////////////////////////////////////////////////////////////////////////
// \n -> "\\n"
// \t -> "\\t"
std::string JSON::encode (const std::string& input)
std::string json::encode (const std::string& input)
{
std::string output;
@ -80,7 +406,7 @@ std::string JSON::encode (const std::string& input)
}
////////////////////////////////////////////////////////////////////////////////
std::string JSON::decode (const std::string& input)
std::string json::decode (const std::string& input)
{
std::string output;
@ -122,217 +448,3 @@ std::string JSON::decode (const std::string& input)
}
////////////////////////////////////////////////////////////////////////////////
Tree* JSON::tree ()
{
return &root;
}
////////////////////////////////////////////////////////////////////////////////
// object
// {}
// { pair , ... }
bool JSON::parseObject (Tree* t, Nibbler& nibbler)
{
Nibbler n (nibbler);
n.skipWS ();
if (n.skip ('{'))
{
n.skipWS ();
Tree* node = new Tree ("node");
if (parsePair (node, n))
{
t->addBranch (node);
n.skipWS ();
while (n.skip (','))
{
n.skipWS ();
node = new Tree ("node");
if (!parsePair (node, n))
{
delete node;
return false;
}
t->addBranch (node);
n.skipWS ();
}
}
else
delete node;
if (n.skip ('}'))
{
n.skipWS ();
nibbler = n;
t->attribute ("type", "collection");
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// pair
// string : value
bool JSON::parsePair (Tree* t, Nibbler& nibbler)
{
Nibbler n (nibbler);
std::string value;
if (n.getQuoted ('"', value))
{
n.skipWS ();
if (n.skip (':'))
{
n.skipWS ();
if (parseValue (t, n))
{
nibbler = n;
t->name (value);
return true;
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// array
// []
// [ value , ... ]
bool JSON::parseArray (Tree* t, Nibbler& nibbler)
{
Nibbler n (nibbler);
n.skipWS ();
if (n.skip ('['))
{
n.skipWS ();
Tree* node = new Tree ("node");
if (parseValue (node, n))
{
t->addBranch (node);
n.skipWS ();
while (n.skip (','))
{
n.skipWS ();
node = new Tree ("node");
if (!parseValue (node, n))
{
delete node;
return false;
}
t->addBranch (node);
n.skipWS ();
}
}
else
delete node;
if (n.skip (']'))
{
n.skipWS ();
nibbler = n;
t->attribute ("type", "list");
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// value
// string
// number
// object
// array
// true
// false
// null
bool JSON::parseValue (Tree* t, Nibbler& nibbler)
{
if (parseString (t, nibbler) ||
parseNumber (t, nibbler) ||
parseObject (t, nibbler) ||
parseArray (t, nibbler) ||
nibbler.getLiteral ("true") ||
nibbler.getLiteral ("false") ||
nibbler.getLiteral ("null"))
{
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// string
// ""
// " chars "
//
// chars
// char
// char chars
//
// char
// any-Unicode-character-except-"-or-\-or-control-character
// \"
// \\ [extra text to de-confuse gcc]
// \/
// \b
// \f
// \n
// \r
// \t
// \u four-hex-digits
bool JSON::parseString (Tree* t, Nibbler& nibbler)
{
std::string value;
if (nibbler.getQuoted ('"', value, false))
{
t->attribute ("type", "string");
t->attribute ("value", value);
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// number
// int frac exp
// int frac
// int exp
// int
bool JSON::parseNumber (Tree* t, Nibbler& nibbler)
{
int i;
double d;
if (nibbler.getNumber (d))
{
t->attribute ("type", "number");
t->attribute ("value", d);
return true;
}
else if (nibbler.getInt (i))
{
t->attribute ("type", "number");
t->attribute ("value", i);
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -1,7 +1,7 @@
////////////////////////////////////////////////////////////////////////////////
// taskwarrior - a command line task list manager.
//
// Copyright 2010 - 2011, Paul Beckingham, Federico Hernandez.
// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez.
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify it under
@ -24,38 +24,102 @@
// USA
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_JSON
#define INCLUDED_JSON
#include <map>
#include <vector>
#include <string>
#include <Tree.h>
#include <Nibbler.h>
class JSON
namespace json
{
public:
JSON (); // Default constructor
JSON (const std::string&); // Constructor
JSON (const JSON&); // Copy constructor
JSON& operator= (const JSON&); // Assignment operator
~JSON (); // Destructor
enum jtype
{
j_value,
j_object,
j_array,
j_string,
j_number,
j_literal
};
static std::string encode (const std::string&);
static std::string decode (const std::string&);
class value
{
public:
value () {}
virtual ~value () {}
static value* parse (Nibbler&);
virtual jtype type ();
virtual std::string dump ();
};
Tree* tree ();
class string : public value, public std::string
{
public:
string () {}
string (const std::string&);
~string () {}
static string* parse (Nibbler&);
jtype type ();
std::string dump ();
};
private:
bool parseObject (Tree*, Nibbler&);
bool parsePair (Tree*, Nibbler&);
bool parseArray (Tree*, Nibbler&);
bool parseValue (Tree*, Nibbler&);
bool parseString (Tree*, Nibbler&);
bool parseNumber (Tree*, Nibbler&);
class number : public value, public std::string
{
public:
number () : _dvalue (0.0) {}
~number () {}
static number* parse (Nibbler&);
jtype type ();
std::string dump ();
operator double () const;
private:
Tree root;
};
double _dvalue;
};
class literal : public value
{
public:
literal () : _lvalue (none) {}
~literal () {}
static literal* parse (Nibbler&);
jtype type ();
std::string dump ();
enum literal_value {none, nullvalue, falsevalue, truevalue};
literal_value _lvalue;
};
class array : public value, public std::vector <value*>
{
public:
array () {}
~array ();
static array* parse (Nibbler&);
jtype type ();
std::string dump ();
};
class object : public value, public std::map <std::string, value*>
{
public:
object () {}
~object ();
static object* parse (Nibbler&);
static bool parse_pair (Nibbler&, std::string&, value*&);
jtype type ();
std::string dump ();
};
// Parser entry point.
value* parse (const std::string&);
// Encode/decode for JSON entities.
std::string encode (const std::string&);
std::string decode (const std::string&);
}
#endif
////////////////////////////////////////////////////////////////////////////////

View file

@ -505,7 +505,7 @@ std::string Task::composeJSON (bool include_id /*= false*/) const
out << "\""
<< i->second.name ()
<< "\":\""
<< JSON::encode (i->second.value ())
<< json::encode (i->second.value ())
<< "\"";
++attributes_written;
@ -530,7 +530,7 @@ std::string Task::composeJSON (bool include_id /*= false*/) const
out << "{\"entry\":\""
<< d.toISO ()
<< "\",\"description\":\""
<< JSON::encode (i->second.value ())
<< json::encode (i->second.value ())
<< "\"}";
++annotations_written;

View file

@ -2250,6 +2250,7 @@ int handleIds (std::string& outs)
}
////////////////////////////////////////////////////////////////////////////////
// TODO Obsolete.
void handleShell ()
{
// Display some kind of welcome message.
@ -2291,11 +2292,11 @@ void handleShell ()
try
{
context.clear ();
std::vector <std::string> args;
split (args, decoratedCommand, ' ');
foreach (arg, args) context.args.push_back (*arg);
context.initialize2 (0, NULL);
context.initialize ();
context.run ();
}

View file

@ -43,7 +43,6 @@ CmdInstall::CmdInstall ()
////////////////////////////////////////////////////////////////////////////////
bool CmdInstall::implements (const std::string& command_line)
{
std::cout << "# CmdInstall::implements '" << command_line << "'\n";
return false;
}
@ -55,7 +54,6 @@ bool CmdInstall::implements (const std::string& command_line)
// extension.<uuid>=<JSON>
int CmdInstall::execute (const std::string& commandLine, std::string& output)
{
std::cout << "# CmdInstall::execute\n";
return 1;
}

View file

@ -760,6 +760,14 @@ std::string format (double value, int width, int precision)
return s.str ();
}
////////////////////////////////////////////////////////////////////////////////
std::string format (double value)
{
std::stringstream s;
s << value;
return s.str ();
}
////////////////////////////////////////////////////////////////////////////////
std::string leftJustify (const int input, const int width)
{

View file

@ -68,6 +68,7 @@ std::string format (int);
std::string formatHex (int);
std::string format (float, int, int);
std::string format (double, int, int);
std::string format (double);
std::string leftJustify (const int, const int);
std::string leftJustify (const std::string&, const int);
std::string rightJustify (const int, const int);

View file

@ -35,30 +35,53 @@ Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (14);
UnitTest t (19);
try
{
// Basic parsing tests.
// j1
std::string input = "{}";
std::cout << "-- j1 -------------------\n"
<< "input: " << input << "\n";
JSON j1 (input);
j1.tree ()->dump ();
std::cout << "# -- j1 -------------------\n"
<< "# input: " << input << "\n";
json::value* root = json::parse (input);
t.ok (root, "j1 parse ok");
if (root)
{
t.diag ("output: " + root->dump ());
delete root;
}
else
t.fail ("j1 parse error");
// j2
input = "{\"name\":123}";
std::cout << "-- j2 -------------------\n"
<< "input: " << input << "\n";
JSON j2 (input);
j2.tree ()->dump ();
std::cout << "# -- j2 -------------------\n"
<< "# input: " << input << "\n";
root = json::parse (input);
t.ok (root, "j2 parse ok");
if (root)
{
t.diag ("output: " + root->dump ());
delete root;
}
else
t.fail ("j2 parse error");
// j3
input = "{\"name\":123, \"array\":[1,2,3.4], \"map\":{\"m1\":\"v1\", \"m2\":\"v2\"}}";
std::cout << "-- j3 -------------------\n"
<< "input: " << input << "\n";
JSON j3 (input);
j3.tree ()->dump ();
std::cout << "# -- j3 -------------------\n"
<< "# input: " << input << "\n";
root = json::parse (input);
t.ok (root, "j3 parse ok");
if (root)
{
t.diag ("output: " + root->dump ());
delete root;
}
else
t.fail ("j3 parse error");
// Sample ticket as a parsing test.
// j4
input = "{\n"
"\"ticket\": { \"type\":\"add\", \"client\":\"taskwarrior 2.x\"},\n"
"\"auth\": { \"user\":\"paul\", \"org\":\"gbf\", \"key\":\".........\",\n"
@ -68,33 +91,39 @@ int main (int argc, char** argv)
" \"project\":\"home\",\n"
" \"due\":\"20101101T000000Z\" }\n"
"}";
std::cout << "-- j4 -------------------\n"
<< "input: " << input << "\n";
JSON j4 (input);
j4.tree ()->dump ();
std::cout << "-------------------------\n";
std::cout << "# -- j4 -------------------\n"
<< "# input: " << input << "\n";
root = json::parse (input);
t.ok (root, "j4 parse ok");
if (root)
{
t.diag ("output: " + root->dump ());
delete root;
}
else
t.fail ("j4 parse error");
// Regular unit tests.
t.is (JSON::encode ("1\b2"), "1\\b2", "JSON::encode \\b -> \\\\b");
t.is (JSON::decode ("1\\b2"), "1\b2", "JSON::decode \\\\b -> \\b");
t.is (json::encode ("1\b2"), "1\\b2", "json::encode \\b -> \\\\b");
t.is (json::decode ("1\\b2"), "1\b2", "json::decode \\\\b -> \\b");
t.is (JSON::encode ("1\n2"), "1\\n2", "JSON::encode \\n -> \\\\n");
t.is (JSON::decode ("1\\n2"), "1\n2", "JSON::decode \\\\n -> \\n");
t.is (json::encode ("1\n2"), "1\\n2", "json::encode \\n -> \\\\n");
t.is (json::decode ("1\\n2"), "1\n2", "json::decode \\\\n -> \\n");
t.is (JSON::encode ("1\r2"), "1\\r2", "JSON::encode \\r -> \\\\r");
t.is (JSON::decode ("1\\r2"), "1\r2", "JSON::decode \\\\r -> \\r");
t.is (json::encode ("1\r2"), "1\\r2", "json::encode \\r -> \\\\r");
t.is (json::decode ("1\\r2"), "1\r2", "json::decode \\\\r -> \\r");
t.is (JSON::encode ("1\t2"), "1\\t2", "JSON::encode \\t -> \\\\t");
t.is (JSON::decode ("1\\t2"), "1\t2", "JSON::decode \\\\t -> \\t");
t.is (json::encode ("1\t2"), "1\\t2", "json::encode \\t -> \\\\t");
t.is (json::decode ("1\\t2"), "1\t2", "json::decode \\\\t -> \\t");
t.is (JSON::encode ("1\\2"), "1\\\\2", "JSON::encode \\ -> \\\\");
t.is (JSON::decode ("1\\\\2"), "1\\2", "JSON::decode \\\\ -> \\");
t.is (json::encode ("1\\2"), "1\\\\2", "json::encode \\ -> \\\\");
t.is (json::decode ("1\\\\2"), "1\\2", "json::decode \\\\ -> \\");
t.is (JSON::encode ("1\x2"), "1\x2", "JSON::encode \\x -> \\x (NOP)");
t.is (JSON::decode ("1\x2"), "1\x2", "JSON::decode \\x -> \\x (NOP)");
t.is (json::encode ("1\x2"), "1\x2", "json::encode \\x -> \\x (NOP)");
t.is (json::decode ("1\x2"), "1\x2", "json::decode \\x -> \\x (NOP)");
t.is (JSON::encode ("1€2"), "1€2", "JSON::encode € -> €");
t.is (JSON::decode ("1\\u20ac2"), "1€2", "JSON::decode \\u20ac -> €");
t.is (json::encode ("1€2"), "1€2", "json::encode € -> €");
t.is (json::decode ("1\\u20ac2"), "1€2", "json::decode \\u20ac -> €");
/*
{
@ -131,10 +160,17 @@ int main (int argc, char** argv)
}
*/
input = "{\"ticket\":{\"type\":\"synch\",\"client\":\"taskd-test-suite 1.0\"},\"synch\":{\"user\":{\"data\":[{\"uuid\":\"11111111-1111-1111-1111-111111111111\",\"status\":\"pending\",\"description\":\"This is a test\",\"entry\":\"20110111T124000Z\"}],\"synch\":\"key\"}},\"auth\":{\"org\":\"gbf\",\"user\":\"Paul Beckingham\",\"key\":\"K\",\"locale\":\"en-US\"}}";
std::cout << "-- j4 -------------------\n"
<< "input: " << input << "\n";
JSON j5 (input);
j5.tree ()->dump ();
std::cout << "# -- j5 -------------------\n"
<< "# input: " << input << "\n";
root = json::parse (input);
t.ok (root, "j5 parse ok");
if (root)
{
t.diag ("output: " + root->dump ());
delete root;
}
else
t.fail ("j5 parse error");
}
catch (std::string& e) {t.diag (e);}