2012/10/11


タイトルなげーーーーーーーーーーー!
プロキシ環境下でしか動きませんが、使いたい人は勝手に書き換えて下さい。
boost::bind 使わずラムダだけでやってんの、オレオレルールだから気にした奴は負け。
#define _WIN32_WINNT 0x0501
#include <iostream>
#include <string>
#include <sstream>
#include <system_error>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
#include <boost/foreach.hpp>
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <boost/property_tree/json_parser.hpp>

using namespace boost::asio;
using namespace boost::asio::ip;
using namespace boost::property_tree;
using namespace boost::system;

int
main(int argc, char* argv[]) {
  if (argc == 1) {
    std::cerr << "usage: " << argv[0] << " [words]" << std::endl;
    return -1;
  }

  // リクエストする URL を作る
  std::string get_url =
    "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=";

  for (int i = 1; i < argc; i++) {
    std::string arg = argv[i];
    BOOST_FOREACH(auto c, arg) {
      if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
          (c >= '0' && c <= '9') ||
          c == '-' || c == '_' || c == '.' || c == '~') {
        get_url += c;
      } else if (c == ' ') {
        get_url += '+';
      } else {
        std::stringstream ss;
        ss << "%" << std::hex << std::setw(2) <<
          std::setfill('0') << (int) (c & 0xff);
        get_url += ss.str();
      }
    }
  }

  // HTTP_PROXY 環境変数からホスト名とポート番号を得る
  std::string http_proxy = std::getenv("HTTP_PROXY");
  boost::regex pattern("^(?:http://)?([^/]+):([0-9]+)");
  boost::match_results<std::string::const_iterator> it;
  if (!regex_search(http_proxy, it, pattern)) {
    std::cerr << "プロキシを使っていません" << std::endl;
    return -1;
  }

  io_service service;
  tcp::resolver resolver(service);

  // ホスト名から名前解決を行う
  tcp::resolver::query resolver_query(
    std::string(it[1].first, it[1].second), // ホスト名
    std::string(it[2].first, it[2].second)  // ポート番号
  );

  std::string data;
  resolver.async_resolve(
    resolver_query,
    [&](const error_code& ec, tcp::resolver::iterator it) {
      if (ec) {
        std::cerr << ec.message() << std::endl;
      } else {
        // プロキシに接続する
        tcp::socket socket(service);
        socket.async_connect(
          it->endpoint(),
          [&](const error_code& ec) {
            if (ec) {
              std::cerr << ec.message() << std::endl;
            } else {
              // プロキシに GET 命令を送信する
              std::string query = "GET ";
              query += get_url;
              query += " HTTP/1.0\r\n\r\n";

              async_write(
                socket,
                buffer(query.c_str(), query.size()),
                [&](const error_code& ec, std::size_t len) {
                  if (ec) {
                    std::cerr << ec.message() << std::endl;
                  } else {
                    boost::array<char1024> buf;
                    std::stringstream ss;
                    // HTTP/1.0 なので切断までループ
                    while (socket.is_open()) {
                      socket.async_read_some(
                        buffer(buf),
                        [&](const error_code& ec, std::size_t len) {
                          if (ec) {
                            socket.close();
                          } else if (len == 0) {
                            socket.close();
                          } else {
                            ss << std::string(buf.data(), len);
                            buf.assign(0);
                          }
                        });
                      service.run_one();
                    }
                    data = ss.str();
                    // ヘッダを取り除く
                    std::size_t pos = data.find("\r\n\r\n");
                    if (pos != std::string::npos) {
                      data = data.substr(pos + 4);
                    }
                  }
                });
              service.run_one();
            }
          });
        service.run_one();
      }
    });
  service.run_one();

  if (!data.empty()) {
    // 結果で得られた JSON をパースする
    std::stringstream ss(data);
    ptree pt;
    read_json(ss, pt);

    if (pt.get<int>("responseStatus") == 200) {
      // 結果配列でループしながら表示
      ptree& results = pt.get_child("responseData.results");
      BOOST_FOREACH(const ptree::value_type& result, results) {
        std::string url = result.second.get<std::string>("url");
        std::string title = result.second.get<std::string>("titleNoFormatting");
        std::cout << url << std::endl << "  " << title << std::endl;
      }
    }
  }
  return 0;
}
Posted at by



2012/09/22


ちょっと前に PostgreSQL に関する記事を見た。最近 PostgreSQL がアツイらしい。
PostgreSQLに興味がある人向けにまとめてみた。|PostgreSQL|お仕事メモ|Pictnotes
  • Q. FDWってなによ?
  • A. FDW(Foreign Data Wrappe),外部データラッパっていうやつで、SQL/MED(Management of External Data)の規格の一つで簡単にいうと、PostgreSQLにQUERYを発行したら、あら不思議、外部の(たとえばMySQLとかCSV)データが取得できるという変態機能。
    twitterAPIに変更あるらしいから今後はわからにけど、twitterのデータを取ってくるとかもできる。というかラッパーが用意されてる。(ちなみに使ってみたら楽しかったw)
http://www.pictnotes.jp/memo/archives/6
それはワクテカ過ぎる...
だがしかし SQLite3 でも出来る!きっと出来る!
#include <string>
#include <sstream>
#include <sqlite3.h>
#include <sqlite3ext.h>
#include <curl/curl.h>
#include "picojson.h"

#ifdef _WIN32
# define EXPORT __declspec(dllexport)
#else
# define EXPORT
#endif

SQLITE_EXTENSION_INIT1;

typedef struct {
  char* data;   // response data from server
  size_t size;  // response size of data
} MEMFILE;

MEMFILE*
memfopen() {
  MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
  if (mf) {
    mf->data = NULL;
    mf->size = 0;
  }
  return mf;
}

void
memfclose(MEMFILE* mf) {
  if (mf->data) free(mf->data);
  free(mf);
}

size_t
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
  MEMFILE* mf = (MEMFILE*) stream;
  int block = size * nmemb;
  if (!mf) return block; // through
  if (!mf->data)
    mf->data = (char*) malloc(block);
  else
    mf->data = (char*) realloc(mf->data, mf->size + block);
  if (mf->data) {
    memcpy(mf->data + mf->size, ptr, block);
    mf->size += block;
  }
  return block;
}

char*
memfstrdup(MEMFILE* mf) {
  char* buf;
  if (mf->size == 0return NULL;
  buf = (char*) malloc(mf->size + 1);
  memcpy(buf, mf->data, mf->size);
  buf[mf->size] = 0;
  return buf;
}

static int
my_connect(sqlite3 *db, void *pAux, int argc, const char * const *argv, sqlite3_vtab **ppVTab, char **c) {
  std::stringstream ss;
  ss << "CREATE TABLE " << argv[0]
    << "(id text, screen_name text, tweet text)";
  int rc = sqlite3_declare_vtab(db, ss.str().c_str());
  *ppVTab = (sqlite3_vtab *) sqlite3_malloc(sizeof(sqlite3_vtab));
  memset(*ppVTab, 0sizeof(sqlite3_vtab));
  return rc;
}

static int
my_create(sqlite3 *db, void *pAux, int argc, const char * const * argv, sqlite3_vtab **ppVTab, char **c) {
  return my_connect(db, pAux, argc, argv, ppVTab, c);
}

static int my_disconnect(sqlite3_vtab *pVTab) {
  sqlite3_free(pVTab);
  return SQLITE_OK;
}

static int
my_destroy(sqlite3_vtab *pVTab) {
  sqlite3_free(pVTab);
  return SQLITE_OK;
}

typedef struct {
  sqlite3_vtab_cursor base;
  int index;
  picojson::value* rows;
} cursor;

static int
my_open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
  MEMFILE* mf;
  CURL* curl;
  char* json;

  mf = memfopen();
  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, "http://api.twitter.com/1/statuses/public_timeline.json");
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
  curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  picojson::value* v = new picojson::value;
  std::string err;
  picojson::parse(*v, mf->data, mf->data + mf->size, &err);
  memfclose(mf);

  if (!err.empty()) {
    delete v;
    std::cerr << err << std::endl;
    return SQLITE_FAIL;
  }

  cursor *c = (cursor *)sqlite3_malloc(sizeof(cursor));
  c->rows = v;
  c->index = 0;
  *ppCursor = &c->base;
  return SQLITE_OK;
}

static int
my_close(cursor *c) {
  delete c->rows;
  sqlite3_free(c);
  return SQLITE_OK;
}

static int
my_filter(cursor *c, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) {
  c->index = 0;
  return SQLITE_OK;
}

static int
my_next(cursor *c) {
  c->index++;
  return SQLITE_OK;
}

static int
my_eof(cursor *c) {
  return c->index >= c->rows->get<picojson::array>().size() ? 1 : 0;
}

static int
my_column(cursor *c, sqlite3_context *ctxt, int i) {
  picojson::value v = c->rows->get<picojson::array>()[c->index];
  picojson::object row = v.get<picojson::object>();
  const char* p = NULL;
  switch (i) {
  case 0:
    p = row["id"].to_str().c_str();
    break;
  case 1:
    p = row["user"].get<picojson::object>()["screen_name"].to_str().c_str();
    break;
  case 2:
    p = row["text"].to_str().c_str();
    break;
  }
  sqlite3_result_text(ctxt, strdup(p), strlen(p), free);
  return SQLITE_OK;
}

static int
my_rowid(cursor *c, sqlite3_int64 *pRowid) {
  *pRowid = c->index;
  return SQLITE_OK;
}

static int
my_bestindex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) {
  return SQLITE_OK;
}

static const sqlite3_module module = {
  0,
  my_create,
  my_connect,
  my_bestindex,
  my_disconnect,
  my_destroy,
  my_open,
  (int (*)(sqlite3_vtab_cursor *)) my_close,
  (int (*)(sqlite3_vtab_cursor *, intchar const *, int, sqlite3_value **)) my_filter,
  (int (*)(sqlite3_vtab_cursor *)) my_next,
  (int (*)(sqlite3_vtab_cursor *)) my_eof,
  (int (*)(sqlite3_vtab_cursor *, sqlite3_context *, int)) my_column,
  (int (*)(sqlite3_vtab_cursor *, sqlite3_int64 *)) my_rowid,
  NULL// my_update
  NULL// my_begin
  NULL// my_sync
  NULL// my_commit
  NULL// my_rollback
  NULL// my_findfunction
  NULL// my_rename
};

static void
destructor(void *arg) {
  return;
}


extern "C" {

EXPORT int
sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) {
  SQLITE_EXTENSION_INIT2(api);
  sqlite3_create_module_v2(db, "twitter_public_timeline", &module, NULL, destructor);
  return 0;
}

}
picojson を使うので適当に持ってきて下さい。
このソースを sqlite3_twit.cxx として $ g++ -I. -g -o sqlite3_twit.dll -shared sqlite3_twit.cxx -lcurldll
の様にコンパイルする。unix な人は... 調べて下さい。
さて... $ sqlite3
SQLite version 3.7.14 2012-09-03 15:42:36
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
SQLiteのシェルに入ったら sqlite> select load_extension("sqlite3_twit.dll");
load_extension("sqlite3_twit.dll")
----------------------------------

拡張をロードして sqlite> create virtual table tbl using twitter_public_timeline(id, screen_name, text);
仮想テーブルを作る。あとは...
sqlite> select * from tbl;
                                  
id                  screen_name   tweet                                                                                                   
------------------  ------------  ---------------------------------------------------------------------------------------------           
248056468924928000  sosomustafa2  الحياة بسيطه جدا لدرجة أن الابتسامه قد تجعلك محبوب جدا ♡"
248056468727808000  nurulauliad   Sekalinya dateng, dateng semua. Sekalinya pergi, pergi semua. Itu lah sebab kenapa hidup penuh pilihan. 
248056468421632000  ZaldivarSkat  ¡HOY HOY HOY! Extreno de O.N.I.F.C de @RealWizKhalifa                                                  
248056468291584000  chimpungdila  Ciye! "@putuderism: Hey 1530 QL, i miss you so badly :'))"                                              
248056467767296000  44Velboo      268とかプロボウラー並の人がいる(^_^;)                                                     
248056467154944000  kevintumongg  Karepmu her RT @KurniawanHerdi: Mau banget ga mong? RT @kevintumonggi: @KurniawanHerdi folbeck her      
248056467045888000  rnbanyu       Detik detik                                                                                             
248056466739712000  iiiFoN        [HD] The Voice Thailand : Week2 (Full Version) 16 Sep 2012: http://t.co/mRIaKDuy via @youtube           
248056465582080000  CarbonQ8e     ياكـــم نفـــسٍ عـــن هـــوا الحـــب طابـــت                     
248056465166848000  WhoIsVARRI_   I'm just Boolin how i B Boolin !                                                                        
248056463069696000  iSwiftCyrus   Photo:  http://t.co/qAcDMm1c                                                                            
248056463048704000  IGORNOKALT    Baixei o ringtone do gummy bear :9                                                                      
248056462130176000  meowkikocel   #MentionSomeoneVerySpecial @Mistacey :)                                                                 
248056462109184000  xFatehx       just got back from NVBA , phhewww what a great match :D                                                 
248056461912064000  snags17       「倭」を日本の蔑称として使いたがるのは、「現代では」、中国人より韓国<e4><ba>
248056461803008000  hendraMirvan  RT @gadasianipar: RT @febrinacitra: Ket biyen nonton sketsa kok rak tau ngguyu blas to.... (‾▿‾") 
248056460972544000  _Azrie_       begitu lah gaya nya , online , tapi hanya memerhatikan TL.                                              
248056460557312000  imbaked_      This one of those morning were I smoke a blunt nd watch Kevin Hart                                      
248056459814912000  psanso        +  RT“@damiaborras: El rei diu que hem de remar tots… Deu tenir el Fortuna espatllat.”
sqlite> 
キターーー!
もちろん sqlite> select * from tbl where tweet like '%twitter%';

id                  screen_name   tweet
------------------  ------------  -----------
248059202780672000  Baby_Nandos_  New Twitter
where区だって使えるんだぜ!public timeline速過ぎるから毎回違うテーブル内容なんだぜ!

って事で、データベースは MySQL でも PostgreSQL でもなく、SQLite に決まり!
Posted at by



2012/08/22


C++ だと picojson ばかり使っていて、JSON使うからC++かなという言語の選び方をしていたりしたのですが、今日matsuuさんのブクマ経由で見つけた。

jsmn

The minimalistic portable framework for parsing JSON data format.

http://bitbucket.org/zserge/jsmn
特徴としては
  • C98コンパチブル
  • 動的なメモリアロケーションを行わない
  • 可能な限り最小のオーバーヘッド
  • JSONのパースは1パス
  • libc も含め依存物がない
  • MITライセンス下で配布され、プロプライエタリなプロジェクトでも使える
  • 単純で美しいデザイン
といったところ。C言語で使える JSON パーサは実は以前にも json-c を試した事はあったのですが、あれは .c の拡張子ファイルを C++ 指定でビルドしているだけというゴニョゴニョなあれなので使い物になりませんでした。
そんな中で見つけたこれは、実にシンプルで応用性がありそうなので記事にしたいと思います。ただし使用上の注意があるので使う人は要検討です。
まずこの jsmn が提供する物はパーサのみ。しかもパース結果から得られるのは各トークンの位置と種別、データとして扱う際の開始終了位置のみです。えっそんなので使い物になるの?と思うかもしれませんが、実は用途によっては十分だったりもします。以下に例を示します。
#include <assert.h>
#include <string.h>
#include <jsmn.h>

#define countof(x) (sizeof(x)/sizeof(x[0]))

int
main() {
  jsmn_parser p;
  jsmntok_t tokens[10] = {0};
  char buf[256];
  const char* js = "{\"foo\"\"bar\"\"baz\": [1,true]}";
  int r;
  jsmn_init(&p);

  r = jsmn_parse(&p, js, tokens, countof(tokens));
  assert(r == JSMN_SUCCESS);

  /* 全体の型はOBJECT */
  assert(tokens[0].type == JSMN_OBJECT);

  /* 中のトークン数は4 */
  assert(tokens[0].size == 4);

  /* 1つ目の型はSTRING */
  assert(tokens[1].type == JSMN_STRING);

  /* 1つ目の値は"foo" */
  memset(buf, 0sizeof buf);
  strncpy(buf, js + tokens[1].start, tokens[1].end - tokens[1].start);
  assert(!strcmp(buf, "foo"));

  /* 2つ目の型はSTRING */
  assert(tokens[2].type == JSMN_STRING);

  /* 2つ目の値は"bar" */
  memset(buf, 0sizeof buf);
  strncpy(buf, js + tokens[2].start, tokens[2].end - tokens[2].start);
  assert(!strcmp(buf, "bar"));

  /* 3つ目の型はSTRING */
  assert(tokens[3].type == JSMN_STRING);

  /* 3つ目の値は"baz" */
  memset(buf, 0sizeof buf);
  strncpy(buf, js + tokens[3].start, tokens[3].end - tokens[3].start);
  assert(!strcmp(buf, "baz"));

  /* 4つ目の型はARRAY */
  assert(tokens[4].type == JSMN_ARRAY);

  /* 4つ目のARRAYのトークン数は2 */
  assert(tokens[4].size == 2);

  /* 5つ目の型はPRIMITIVE */
  assert(tokens[5].type == JSMN_PRIMITIVE);

  /* 5つ目の値は"1" */
  memset(buf, 0sizeof buf);
  strncpy(buf, js + tokens[5].start, tokens[5].end - tokens[5].start);
  assert(atoi(buf) == 1);

  /* 6つ目の型はPRIMITIVE */
  assert(tokens[6].type == JSMN_PRIMITIVE);

  /* 6つ目の値は"true" */
  memset(buf, 0sizeof buf);
  strncpy(buf, js + tokens[6].start, tokens[6].end - tokens[6].start);
  assert(!strcmp(buf, "true"));
}
例を追って説明します。まず jsmn_parser を jsmn_init で初期化します。実際には構造体メンバに初期値を代入しているに過ぎません。
初期化した jsmn_parser を jsmn_parse 引数に渡して JSON 文字列をパースします。この時、トークン数を渡す必要があります。このトークンは動的に確保されません。基本的に jsmn は内部でメモリを動的確保しません。これについての解決方法は後で説明します。
パースされた結果はトークンの配列となります。そしてこのトークンは以下の内容で構成されます。
  • 値を示す JSON 文字列の開始位置
  • 値を示す JSON 文字列の終了位置
  • 値が配列の場合のアイテム個数
  • 値がオブジェクトの場合のキーおよび値の個数
たとえば {"foo": "bar", "baz": [1,true]}
この JSON をパースした場合、トークンは
トークン
tokens[0]JSMN_OBJECTオブジェクト
tokens[1]JSMN_STRING"foo"
tokens[2]JSMN_STRING"bar"
tokens[3]JSMN_STRING"baz"
tokens[4]JSMN_ARRAY配列
tokens[5]JSMN_PRIMITIVE1
tokens[6]JSMN_PRIMITIVEtrue
上記の様に、トークンは各識別毎に作られます。オブジェクトのキーおよび値もそれぞれのトークンとして格納されます。
jsmn_parse はトークンの量が不足している場合、エラー JSMN_ERROR_NOMEM を返します。例えば、どれだけの量のトークンが JSON 文字列として与えられるか分からない場合、トークンのサイズを広げる必要があります。この場合、jsmn ではパーサを再初期化する事なしに、トークンを広げて再度 jsmn_parse を実行する事でパースを続行出来る様になっています。
ただしどれだけの量が不足していたかは分からないので、適度な増減を考慮する必要があります。
今日は試しに twitter のパブリックタイムラインをパースしてみました。
#include <assert.h>
#include <string.h>
#include <memory.h>
#include <curl/curl.h>
#include <jsmn.h>

#define countof(x) (sizeof(x)/sizeof(x[0]))

typedef struct {
  char* data;   // response data from server
  size_t size;  // response size of data
} MEMFILE;

MEMFILE*
memfopen() {
  MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
  if (mf) {
    mf->data = NULL;
    mf->size = 0;
  }
  return mf;
}

void
memfclose(MEMFILE* mf) {
  if (mf->data) free(mf->data);
  free(mf);
}

size_t
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
  MEMFILE* mf = (MEMFILE*) stream;
  int block = size * nmemb;
  if (!mf) return block; // through
  if (!mf->data)
    mf->data = (char*) malloc(block);
  else
    mf->data = (char*) realloc(mf->data, mf->size + block);
  if (mf->data) {
    memcpy(mf->data + mf->size, ptr, block);
    mf->size += block;
  }
  return block;
}

char*
memfstrdup(MEMFILE* mf) {
  char* buf;
  if (mf->size == 0return NULL;
  buf = (char*) malloc(mf->size + 1);
  memcpy(buf, mf->data, mf->size);
  buf[mf->size] = 0;
  return buf;
}

int
skip(jsmntok_t* tokens, int off) {
  jsmntype_t t = tokens[off].type;
  if (t == JSMN_ARRAY || t == JSMN_OBJECT) {
    int n, l = tokens[off++].size;
    for (n = 0; n < l; n++)
      off = skip(tokens, off);
  } else
    off++;
  return off;
}

typedef struct {
  char* screen_name;
  char* text;
} tweet;

int
main() {
  jsmn_parser p;
  jsmntok_t *tokens;
  size_t len;
  char buf[1024];
  CURL* curl;
  MEMFILE* mf = NULL;
  char* js = NULL;
  int i, j, k, count, off;
  tweet* tweets = NULL;

  mf = memfopen();

  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, "http://api.twitter.com/1/statuses/public_timeline.json");
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
  curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  js = memfstrdup(mf);
  memfclose(mf);

  jsmn_init(&p);

  len = 5;
  tokens = malloc(sizeof(jsmntok_t) * len);
  if (tokens == NULL) {
    perror("malloc");
    goto leave;
  }
  memset(tokens, 0sizeof(jsmntok_t) * len);
  while (1) {
    int r = jsmn_parse(&p, js, tokens, len);
    if (r == JSMN_SUCCESS) 
      break;
    assert(r == JSMN_ERROR_NOMEM);
    len *= 2;
    tokens = realloc(tokens, sizeof(jsmntok_t) * len);
    if (tokens == NULL) {
      perror("malloc");
      goto leave;
    }
  }

  off = 0;
  count = tokens[off++].size;

  tweets = malloc(sizeof(tweet) * count);
  memset(tweets, 0sizeof(tweet) * count);

  for (i = 0; i < count; i++) {
    int k1, k2;
    k1 = tokens[off++].size;
    for (j = 0; j < k1 / 2; j++) {
      memset(buf, 0sizeof buf);
      strncpy(buf, js + tokens[off].start, tokens[off].end - tokens[off].start);
      off++;
      if (!strcmp(buf, "text")) {
        memset(buf, 0sizeof buf);
        strncpy(buf, js + tokens[off].start, tokens[off].end - tokens[off].start);
        tweets[i].text = strdup(buf);
        off++;
      } else if (!strcmp(buf, "user")) {
        k2 = tokens[off].size;
        off++;
        for (k = 0; k < k2 / 2; k++) {
          memset(buf, 0sizeof buf);
          strncpy(buf, js + tokens[off].start, tokens[off].end - tokens[off].start);
          off++;
          if (!strcmp(buf, "screen_name")) {
            memset(buf, 0sizeof buf);
            strncpy(buf, js + tokens[off].start, tokens[off].end - tokens[off].start);
            tweets[i].screen_name = strdup(buf);
            off++;
          } else
            off = skip(tokens, off);
        }
      } else
        off = skip(tokens, off);
    }
  }

  for (i = 0; i < count; i++) {
    printf("%s%s\n", tweets[i].screen_name, tweets[i].text);
  }

  for (i = 0; i < count; i++) {
    free(tweets[i].screen_name);
    free(tweets[i].text);
  }
  free(tweets);

leave:
  if (js) free(js);
  if (tokens) free(tokens);
}
出力結果から分かる通り、実は jsmn はまだ \uXXXX という文字列リテラルをパース出来ません。よって twitter のタイムライン上に流れるユニコード文字列は全てエスケープされたまま表示されます。
ちょっと癖があるのでフラットな JSON であれば十分役立つのですが、twitter のタイムラインの様にオブジェクト構造がネストされた物になると扱う側が常にメモリを意識しなければならないので途端に難易度があがります。
例えばキーと値がベタに決まっていて、ネストも無いような JSON であれば少しは使い道はあるかもしれませんね。\uXXXX リテラルがサポートされればもう少し使い道が出てくるかもしれません。
Posted at by