2.1 json number 的語法規則與解釋。
json number 的語法規則是這樣的:
/*
number 以十進位制表示,它主要由 4 部分組成:負號、整數、小數、指數。只有整數是必需部分。注意:+號是不合法的。
int 整數部分如果是 0 開始,只能是單個 0;而由 1-9 開始的話,可以加任意數量的數字(0-9)。也就是說,0123 不是乙個合法的 json 數字。
frac 小數部分比較直觀,就是小數點後是一或多個數字(0-9)。
json 可使用科學記數法,exp 指數部分由大寫 e 或小寫 e 開始,然後可有正負號,之後是一或多個數字(0-9)。
*/json
-text = ws value ws
ws =*(
%x20 /
%x09 /
%x0a /
%x0d)
value = number
number =
["-"
] int [ frac ]
[ exp ]
int =
"0"/ digit1-
9*digit
frac =
"."1
*digit
exp =
("e"
/"e")[
"-"/
"+"]
1*digit
json 標準 ecma-404 採用圖的形式表示語法,也可以更直觀地看到解析時可能經過的路徑:
2.2 設計標頭檔案
json 是乙個樹形結構,我們設定每個節點使用結構體 lept_value表示,對於null、false、true 在解析後,我們只需要儲存它的資料型別為lept_null、lept_false、lept_true;但對於數字,不僅要儲存結點的型別,還要儲存資料本身。
從 json 數字的語法,我們可能直觀地會認為它應該表示為乙個浮點數,因為它帶有小數和指數部分。然而,標準中並沒有限制數字的範圍或精度。為簡單起見,選擇以雙精度浮點數來儲存 json 數字。
所以,此時 lept_value 組成就為:
typedef
struct
lept_value;
api設計
/*
函式目的:獲取json結點裡的數值
引數:1. lept_value* v :傳入儲存解析後json樹形結構的根節點指標
返回值:double
*/double
lept_get_number
(const lept_value* v)
;
api 實現
/*
函式目的:獲取json結點裡的數值
引數:1. lept_value* v :傳入儲存解析後json樹形結構的根節點指標
返回值:double
實現思路:僅當 type == lept_number時,才返回數值
*/double
lept_get_number
(const lept_value* v)
2.3 tdd設計理念
先寫測試函式,寫測試函式時考慮的情況越多,最後在設計解析器的時候就越完善。
#define expect_eq_double(expect, actual) expect_eq_base((expect) == (actual),expect,actual, "%.17g")
#define test_number(expect, json)\
do while(0)
static
void
test_parse_number()
除了這些上面這些合法的測試用例,根據json數字解析規則我們來設計一些不合語法的用例:
#define test_error(error, json, lept_type)\
do while (0);
static
void
test_parse_invalid_value()
static
void
test_parse_root_not_singular()
static
void
test_parse_number_too_big()
整數部分全為1,小數部分全為0表示無窮大,即inf。根據符號位的不同可以分為+inf / -inf
整數部分全為1,小數部分不全為0表示nan。即not a number。
小數部分最高位為1的nan稱為qnan;最高位為0的nan稱為snan。通常用qnan表示不確定的操作,snan表示無效的操作。
浮點數0000 0000 0000 0001(十六進製制表示法)的小數部分(後13位)是0 0000 0000 0001,即1/252, 對應的數是1/(252) *2(-1022),即4.9406564584124654e-324
2.4 實現json_number解析器
根據 api 與 單元測試 ,我們來實現解析器,首先是lept_parse函式
static
intlept_parse_value
(lept_context* c, lept_value* v)}/*
注:這個函式因為邏輯沒有變,所以本身不用改變,需要改變的是它所呼叫的 lept_parse_value 函式。
*/int
lept_parse
(lept_value* v,
const
char
* json)
根據json的數字規則寫 lept_parse_number()
#include
/* errno, erange */
#include
/* huge_val */
#include
/* null, strtod() */
#define isdigit(ch) ((ch) >= '0' && (ch) <= '9')
#define isdigit1to9(ch) ((ch) >= '1' && (ch) <= '9')
/*函式目的: 對number進行解析
思路:1. 判斷是不是負號,是:跳過
2. 整數部分:判斷是否為單個0的情況,是便跳過;如果不是第一種情況,則整數部分第乙個字元必須為1~9,否則返回錯誤碼;然後有多少個digit就跳過多少。
3. 小數部分:如果出現小數點,我們跳過該小數點,然後檢查它至少應有乙個 digit,不是 digit 就返回錯誤碼。跳過首個 digit,我們再檢查有沒有 digit,有多少個跳過多少個。
4. 指數部分:如果出現大小寫 e,就表示有指數部分。跳過那個 e 之後,可以有乙個正或負號,有的話就跳過。然後和小數的邏輯是一樣的
*/static
intlept_parse_number
(lept_context* c, lept_value* v)
/* 小數 ... */if(
*p ==
'.')
/* 指數 ... */if(
*p ==
'e'||
*p ==
'e')
errno =0;
v->n =
strtod
(c->json,
null);
if(errno == erange &&
(v->n == huge_val || v->n ==
-huge_val)
)return lept_parse_number_too_big;
v->type = lept_number;
c->json = p;
return lept_parse_ok;
}
上面的**,要再說兩點:
strtod函式,將字串轉換成浮點數,這個函式的定義是 double strtod(const char str, char
**endprt) 其中 str為要轉化為雙精度浮點數的字串; endptr對char的物件的引用,其值由函式設定為str中數值後的下乙個字元。
在math.h中, 當函式的結果不可以表示為浮點數時。如果是因為結果的幅度太大以致於無法表示,則函式會設定 errno 為 erange 來表示範圍錯誤,並返回乙個由巨集 huge_val 或者它的否定(- huge_val)命名的乙個特定的很大的值; 如果結果的幅度太小,則會返回零值。在這種情況下,error 可能會被設定為 erange,也有可能不會被設定為 erange。
linux開源專案 cJSON
cjson是c語言中的乙個json編解碼器,非常輕量級,c檔案只有500多行,速度也非常理想。cjson也存在幾個弱點,雖然功能不是非常強大,但cjson的小身闆和速度是最值得讚賞的。其 被非常好地維護著,結構也簡單易懂,可以作為乙個非常好的c語言專案進行學習。專案主頁 json介紹 英文 中文 j...
Github上的開源專案2
krvideoplayer kxmovie vkvideoplayer ijkplayer android ios video player based on ffmpeg n2.8,with mediacodec,videotoolbox support.eleven eleven player ...
開源雷射SLAM專案BLAM 2
接上一章節提到的 processpointcloudmessage m msg 函式,它傳入乙個const pointcloud constptr 型別,即點雲常指標的引用,程式源 如下 void blamslam processpointcloudmessage const pointcloud ...