2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CrystalAdvent Calendar 2018

Day 24

Crystal binding for yajl JSON library

Last updated at Posted at 2018-12-24

この記事は Crystal Advent Calendar 2018 の24日目の記事です。

今回は Crystal の C Bindings に関してあるライブラリをCrystalから呼び出せるようにしたいと思います。

対象のライブラリ

C Bindings を生成するために利用するツール

環境

  • debian
  • osx (未確認)

Libgen 用にコンフィグファイルを作成します。

  • lib_yajl.yml
---
name: LibYajl
ldflags: ""
cflags: "-I/usr/include/yajl"
packages: "yajl"
destdir: src/lib_yajl/
includes:
- include/yajl/*.yml
  • include/yajl/yajl.yml

---
description: Yajl
includes:
- /usr/include/yajl/yajl_common.h
- /usr/include/yajl/yajl_gen.h
- /usr/include/yajl/yajl_parse.h
- /usr/include/yajl/yajl_tree.h
- /usr/include/yajl/yajl_version.h
prefixes:
- yajl

今回 apt-get install yajl したため、 /usr/include に yajl のヘッダファイルなどが入っています。
brew install yajl の場合は /usr/local/include になるかと思います。

  • libgen を実行します
% libgen lib_yajl.yml
loading library from lib_yajl.yml
loading lib_yajl definition from include/yajl/lib_yajl.yml
/usr/include/yajl/yajl_common.h:20:10: fatal error: 'stddef.h' file not found
generate src/lib_yajl/lib_yajl.cr
  • src/lib_yajl/lib_yajl.cr が生成されることが確認できます

lib LibYajl
  YajlAllowComments         =  1
  YajlAllowMultipleValues   =  8
  YajlAllowPartialValues    = 16
  YajlAllowTrailingGarbage  =  4
  YajlDontValidateStrings   =  2
  YajlGenBeautify           =  1
  YajlGenEscapeSolidus      = 16
  YajlGenGenerationComplete =  4
  YajlGenInErrorState       =  3
  YajlGenIndentString       =  2
  YajlGenInvalidNumber      =  5
  YajlGenInvalidString      =  7
  YajlGenKeysMustBeStrings  =  1
  YajlGenNoBuf              =  6
  YajlGenPrintCallback      =  4
  YajlGenStatusOk           =  0
  YajlGenValidateUtf8       =  8
  YajlMaxDepthExceeded      =  2
  YajlStatusClientCanceled  =  1
  YajlStatusError           =  2
  YajlStatusOk              =  0
  YajlTAny                  =  8
  YajlTArray                =  4
  YajlTFalse                =  6
  YajlTNull                 =  7
  YajlTNumber               =  2
  YajlTObject               =  3
  YajlTString               =  1
  YajlTTrue                 =  5
  alias YajlFreeFunc = (Void*, Void* -> Void)
  alias YajlGenT = Void
  alias YajlHandleT = Void
  alias YajlMallocFunc = (Void*, LibC::Int -> Void*)
  alias YajlReallocFunc = (Void*, Void*, LibC::Int -> Void*)
  alias YajlVal = YajlValS*
  enum YajlGenOption
    YajlGenBeautify      =  1
    YajlGenIndentString  =  2
    YajlGenPrintCallback =  4
    YajlGenValidateUtf8  =  8
    YajlGenEscapeSolidus = 16
  end
  enum YajlGenStatus
    YajlGenStatusOk           = 0
    YajlGenKeysMustBeStrings  = 1
    YajlMaxDepthExceeded      = 2
    YajlGenInErrorState       = 3
    YajlGenGenerationComplete = 4
    YajlGenInvalidNumber      = 5
    YajlGenNoBuf              = 6
    YajlGenInvalidString      = 7
  end
  enum YajlOption
    YajlAllowComments        =  1
    YajlDontValidateStrings  =  2
    YajlAllowTrailingGarbage =  4
    YajlAllowMultipleValues  =  8
    YajlAllowPartialValues   = 16
  end
  enum YajlStatus
    YajlStatusOk             = 0
    YajlStatusClientCanceled = 1
    YajlStatusError          = 2
  end
  enum YajlType
    YajlTString = 1
    YajlTNumber = 2
    YajlTObject = 3
    YajlTArray  = 4
    YajlTTrue   = 5
    YajlTFalse  = 6
    YajlTNull   = 7
    YajlTAny    = 8
  end
  fun yajl_alloc(callbacks : YajlCallbacks*, afs : YajlAllocFuncs*, ctx : Void*) : YajlHandle
  fun yajl_complete_parse(hand : YajlHandle) : YajlStatus
  fun yajl_config(h : YajlHandle, opt : YajlOption, ...) : LibC::Int
  fun yajl_free(handle : YajlHandle)
  fun yajl_free_error(hand : YajlHandle, str : UInt8*)
  fun yajl_gen_alloc(alloc_funcs : YajlAllocFuncs*) : YajlGen
  fun yajl_gen_array_close(hand : YajlGen) : YajlGenStatus
  fun yajl_gen_array_open(hand : YajlGen) : YajlGenStatus
  fun yajl_gen_bool(hand : YajlGen, boolean : LibC::Int) : YajlGenStatus
  fun yajl_gen_clear(hand : YajlGen)
  fun yajl_gen_config(g : YajlGen, opt : YajlGenOption, ...) : LibC::Int
  fun yajl_gen_double(hand : YajlGen, number : LibC::Double) : YajlGenStatus
  fun yajl_gen_free(handle : YajlGen)
  fun yajl_gen_get_buf(hand : YajlGen, buf : UInt8**, len : LibC::Int*) : YajlGenStatus
  fun yajl_gen_integer(hand : YajlGen, number : LibC::LongLong) : YajlGenStatus
  fun yajl_gen_map_close(hand : YajlGen) : YajlGenStatus
  fun yajl_gen_map_open(hand : YajlGen) : YajlGenStatus
  fun yajl_gen_null(hand : YajlGen) : YajlGenStatus
  fun yajl_gen_number(hand : YajlGen, num : LibC::Char*, len : LibC::Int) : YajlGenStatus
  fun yajl_gen_reset(hand : YajlGen, sep : LibC::Char*)
  fun yajl_gen_string(hand : YajlGen, str : UInt8*, len : LibC::Int) : YajlGenStatus
  fun yajl_get_bytes_consumed : LibC::Int
  fun yajl_get_error(hand : YajlHandle, verbose : LibC::Int, json_text : UInt8*, json_text_length : LibC::Int) : UInt8*
  fun yajl_parse(hand : YajlHandle, json_text : UInt8*, json_text_length : LibC::Int) : YajlStatus
  fun yajl_status_to_string(code : YajlStatus) : LibC::Char*
  fun yajl_tree_free(v : YajlVal)
  fun yajl_tree_get(parent : YajlVal, path : LibC::Char**, type : YajlType) : YajlVal
  fun yajl_tree_parse(input : LibC::Char*, error_buffer : LibC::Char*, error_buffer_size : LibC::Int) : YajlVal
  fun yajl_version : LibC::Int

  struct YajlAllocFuncs
    malloc : YajlMallocFunc
    realloc : YajlReallocFunc
    free : YajlFreeFunc
    ctx : Void*
  end

  struct YajlCallbacks
    yajl_null : (Void* -> LibC::Int)
    yajl_boolean : (Void*, LibC::Int -> LibC::Int)
    yajl_integer : (Void*, LibC::LongLong -> LibC::Int)
    yajl_double : (Void*, LibC::Double -> LibC::Int)
    yajl_number : (Void*, LibC::Char*, LibC::Int -> LibC::Int)
    yajl_string : (Void*, UInt8*, LibC::Int -> LibC::Int)
    yajl_start_map : (Void* -> LibC::Int)
    yajl_map_key : (Void*, UInt8*, LibC::Int -> LibC::Int)
    yajl_end_map : (Void* -> LibC::Int)
    yajl_start_array : (Void* -> LibC::Int)
    yajl_end_array : (Void* -> LibC::Int)
  end

  struct YajlValS
    type : YajlType
    u : YajlValSU
  end

  struct YajlValSUArray
    values : YajlVal*
    len : LibC::Int
  end

  struct YajlValSUNumber
    i : LibC::LongLong
    d : LibC::Double
    r : LibC::Char*
    flags : LibC::UInt
  end

  struct YajlValSUObject
    keys : LibC::Char**
    values : YajlVal*
    len : LibC::Int
  end

  type YajlGen = Void*
  type YajlHandle = Void*

  union YajlValSU
    string : LibC::Char*
    number : YajlValSUNumber
    object : YajlValSUObject
    array : YajlValSUArray
  end
end

LibC の stdin などを使えるために簡単なラッパを作ります。

  • src/yajl.cr
require "./lib_yajl"

@[Link(ldflags: "-lyajl")]
lib LibYajl
  $stderr : Void*
  $stdout : Void*
  $stdin : Void*

  fun fread(ptr : Void*, size : Int32, nmemb : Int32, stream : Void*) : Int32
  fun fprintf(stream : Void*, format : UInt8*, ...) : Int32
end

class Yajl
  NULL = nil
  alias YajlType = LibYajl::YajlType

  def yajl_is_string(v)
    v ? v.value.type == YajlType::YajlTString : false
  end

  def yajl_get_string(v)
    yajl_is_string(v) ? v.value.u.string : ""
  end

  forward_missing_to LibYajl
end

以下の C のサンプルを Crystal に移植します

require "yajl"

class YajlTest < Yajl
  def run : Int32
    fileData = uninitialized UInt8[65536]
    errbuf = uninitialized UInt8[1024]

    rd = fread(fileData.to_unsafe, 1, fileData.size - 1, stdin)

    node = yajl_tree_parse(fileData.to_unsafe, errbuf.to_unsafe, errbuf.size)

    unless node
      fprintf(stderr, "parse_error: ")
      return 1
    end

    path = ["Logging".to_unsafe, "timeFormat".to_unsafe]
    v = yajl_tree_get(node, path.to_unsafe, YajlType::YajlTString)
    if v
      printf("%s/%s: %s\n", path[0], path[1], yajl_get_string(v))
    else
      printf("no such node: %s/%s\n", path[0], path[1])
    end

    yajl_tree_free(node)

    0
  end
end

fun main(argc : Int32, argv : UInt8**) : Int32
  YajlTest.new.run
end

コンパイルします。

crystal build --no-debug --release example/parse_config.cr

実行します。

% echo '{"Logging":{"timeFormat":"utc"}}' | ./parse_config
Logging/timeFormat: utc

% echo '{"Logging":{"":"utc"}}' | ./parse_config 
no such node: Logging/timeFormat

リポジトリ

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?