| 1 | :- module( json, [ json_parse/2, json_parse/3, | |
| 2 | json_parse_file/2 | |
| 3 | ] ). | |
| 4 | % based on https://github.com/yoroto/pl-json | |
| 5 | % have added line counting and error reporting, reading directly from file | |
| 6 | % fixed a few issues with whitespace and avoid backtracking | |
| 7 | % return strings using string(.) wrapper to avoid ambiguity with arrays of integers | |
| 8 | ||
| 9 | % parse a JSON file | |
| 10 | json_parse_file(File, Json) :- | |
| 11 | read_file(File,Codes), | |
| 12 | %format('Read:~n~s~n',[Codes]), | |
| 13 | json_parse(Codes,Json). | |
| 14 | ||
| 15 | read_file(Filename,Codes) :- | |
| 16 | open(Filename,read,S,[encoding('UTF-8')]), | |
| 17 | read_codes(S,Codes), | |
| 18 | close(S). | |
| 19 | read_codes(S,Codes) :- | |
| 20 | get_code(S,Code), | |
| 21 | ( Code < 0 -> | |
| 22 | Codes = [] | |
| 23 | ; otherwise -> | |
| 24 | Codes = [Code|Rest], | |
| 25 | read_codes(S,Rest)). | |
| 26 | ||
| 27 | % -------------- | |
| 28 | ||
| 29 | json_parse( Chars, Json ) :- | |
| 30 | json_parse( Chars, Json, [] ). | |
| 31 | ||
| 32 | json_parse( Chars, Json, _Options ) :- | |
| 33 | reset_line_nr, | |
| 34 | json(Json, Chars, _). | |
| 35 | ||
| 36 | json(Json) --> ws,!,json(Json). | |
| 37 | json(Json) --> | |
| 38 | json1(Json), | |
| 39 | !, | |
| 40 | spaces. | |
| 41 | json(Json) --> {var(Json)},check_string("?"), {fail}. % generate error message | |
| 42 | %json(Json) --> {ground(Json)}, json(JSon2), {format('### Mismatch:~n ~w~n~w~n',[Json,Json2]),fail}. | |
| 43 | ||
| 44 | % a version that does not print an error message | |
| 45 | try_json(Json) --> ws,!,try_json(Json). | |
| 46 | try_json(Json) --> | |
| 47 | json1(Json), | |
| 48 | !, | |
| 49 | spaces. | |
| 50 | ||
| 51 | json1(null) --> "null",!. | |
| 52 | json1(true) --> "true",!. | |
| 53 | json1(false) --> "false",!. | |
| 54 | ||
| 55 | json1(Number) --> number(Number),!. | |
| 56 | ||
| 57 | json1(string(Codes)) --> | |
| 58 | """",!, | |
| 59 | %{print_info(start_string)}, | |
| 60 | string(Codes). %, {atom_codes(String,Codes)}. | |
| 61 | ||
| 62 | json1(Array) --> | |
| 63 | "[",!, | |
| 64 | %{print_info(start_array)}, | |
| 65 | array(Array), | |
| 66 | check_string("]"). | |
| 67 | ||
| 68 | json1(obj(Pairs)) --> | |
| 69 | "{",!, | |
| 70 | %{print_info(start_object)}, | |
| 71 | spaces, | |
| 72 | pairs(Pairs), | |
| 73 | spaces, | |
| 74 | check_string("}"). | |
| 75 | ||
| 76 | number(Number) --> | |
| 77 | nm_token(NmCodes), | |
| 78 | { number_codes(Number, NmCodes) }. | |
| 79 | ||
| 80 | nm_token([H|T]) --> | |
| 81 | [H], | |
| 82 | { minus(H);digit_table(H) }, | |
| 83 | nm_token1(T). | |
| 84 | ||
| 85 | nm_token1([0'\x2E\|T]) --> | |
| 86 | ".",!, | |
| 87 | nm_frac(T). | |
| 88 | ||
| 89 | nm_token1([H|T]) --> | |
| 90 | [H], | |
| 91 | { digit_table(H) }, !, | |
| 92 | nm_token1(T). | |
| 93 | ||
| 94 | nm_token1([]) --> []. | |
| 95 | ||
| 96 | ||
| 97 | nm_frac([0'\x45\,H|T]) --> | |
| 98 | ("e";"E"),!, | |
| 99 | [H], {minus(H);plus(H)}, | |
| 100 | nm_exp(T). | |
| 101 | ||
| 102 | nm_frac([H|T]) --> | |
| 103 | [H], | |
| 104 | { digit_table(H) }, !, | |
| 105 | nm_frac(T). | |
| 106 | ||
| 107 | nm_frac([]) --> []. | |
| 108 | ||
| 109 | nm_exp([H|T]) --> | |
| 110 | [H], | |
| 111 | { digit_table(H) }, !, | |
| 112 | nm_exp1(T). | |
| 113 | ||
| 114 | nm_exp1([H|T]) --> | |
| 115 | [H], | |
| 116 | { digit_table(H) }, !, | |
| 117 | nm_exp1(T). | |
| 118 | ||
| 119 | nm_exp1([]) --> []. | |
| 120 | ||
| 121 | string(X) --> string2(X). | |
| 122 | ||
| 123 | string2([]) --> [0'\x22\], | |
| 124 | !. | |
| 125 | string2([EscapedChar|T]) --> | |
| 126 | [0'\x5C\],!, | |
| 127 | escape_char(EscapedChar), | |
| 128 | string2(T). | |
| 129 | string2([H|T]) --> | |
| 130 | [H], | |
| 131 | string2(T). | |
| 132 | ||
| 133 | escape_char( 0'\x22\ ) --> [0'\x22\]. | |
| 134 | escape_char( 0'\x5C\ ) --> [0'\x5C\]. | |
| 135 | escape_char( 0'\x2F\ ) --> [0'\x2F\]. | |
| 136 | escape_char( 0'\x08\ ) --> [0'\x62\]. %b | |
| 137 | escape_char( 0'\x0C\ ) --> [0'\x66\]. %f | |
| 138 | escape_char( 0'\x0A\ ) --> [0'\x6E\]. %n | |
| 139 | escape_char( 0'\x0D\ ) --> [0'\x72\]. %r | |
| 140 | escape_char( 0'\x09\ ) --> [0'\x74\]. %t | |
| 141 | ||
| 142 | escape_char( Code ) --> | |
| 143 | "u", | |
| 144 | hex_digit_char( H1 ), | |
| 145 | hex_digit_char( H2 ), | |
| 146 | hex_digit_char( H3 ), | |
| 147 | hex_digit_char( H4 ), | |
| 148 | { Code is (((H1 << 4 + H2) << 4 + H3) << 4 + H4) }. | |
| 149 | ||
| 150 | hex_digit_char( 0 ) --> "0". | |
| 151 | hex_digit_char( 1 ) --> "1". | |
| 152 | hex_digit_char( 2 ) --> "2". | |
| 153 | hex_digit_char( 3 ) --> "3". | |
| 154 | hex_digit_char( 4 ) --> "4". | |
| 155 | hex_digit_char( 5 ) --> "5". | |
| 156 | hex_digit_char( 6 ) --> "6". | |
| 157 | hex_digit_char( 7 ) --> "7". | |
| 158 | hex_digit_char( 8 ) --> "8". | |
| 159 | hex_digit_char( 9 ) --> "9". | |
| 160 | hex_digit_char( 10 ) --> "A". | |
| 161 | hex_digit_char( 11 ) --> "B". | |
| 162 | hex_digit_char( 12 ) --> "C". | |
| 163 | hex_digit_char( 13 ) --> "D". | |
| 164 | hex_digit_char( 14 ) --> "E". | |
| 165 | hex_digit_char( 15 ) --> "F". | |
| 166 | hex_digit_char( 10 ) --> "a". | |
| 167 | hex_digit_char( 11 ) --> "b". | |
| 168 | hex_digit_char( 12 ) --> "c". | |
| 169 | hex_digit_char( 13 ) --> "d". | |
| 170 | hex_digit_char( 14 ) --> "e". | |
| 171 | hex_digit_char( 15 ) --> "f". | |
| 172 | ||
| 173 | array([H|T]) --> | |
| 174 | try_json(H), !,{print_info(first_array)}, | |
| 175 | array1(T). | |
| 176 | array([]) --> [], {print_info(end_array)}. | |
| 177 | ||
| 178 | array1([H|T]) --> | |
| 179 | ",", !, | |
| 180 | json(H),!, {print_info(next_array)}, | |
| 181 | array1(T). | |
| 182 | array1([]) --> [], {print_info(empty_array)}. | |
| 183 | ||
| 184 | pair(pair(Name, Value),Optional) --> | |
| 185 | spaces, | |
| 186 | ({Optional=optional} -> opt_pair_name(Codes) ; pair_name(Codes)), | |
| 187 | check_string(":"), | |
| 188 | { atom_codes(Name, Codes) }, | |
| 189 | {print_info(pair_value_for(Name))}, | |
| 190 | json(Value), | |
| 191 | {print_info(found_value_vor(Name,Value))}. | |
| 192 | ||
| 193 | opt_pair_name(Name) --> """", string(Name), spaces. | |
| 194 | pair_name(Name) --> check_string(""""), string(Name), spaces. | |
| 195 | ||
| 196 | ||
| 197 | pairs(List) --> ws,!,pairs(List). | |
| 198 | pairs([H|T]) --> | |
| 199 | pair(H,optional), !, | |
| 200 | pairs1(T). | |
| 201 | pairs([]) --> [], {print_info(empty_pairs)}. | |
| 202 | ||
| 203 | pairs1(List) --> ws,!,pairs1(List). | |
| 204 | pairs1([H|T]) --> | |
| 205 | ",", !, | |
| 206 | pair(H,required),!, | |
| 207 | pairs1(T). | |
| 208 | pairs1([]) --> [], {print_info(end_pairs)}. | |
| 209 | ||
| 210 | check_string(List) --> ws,!,check_string(List). | |
| 211 | check_string([Char]) --> [Char],!. | |
| 212 | check_string(String, [Char|_],_) :- | |
| 213 | cur_line(LineNr), | |
| 214 | format(user_error,'! Error on line ~w: expecting ~s obtained ~s~n',[LineNr,String,[Char]]), | |
| 215 | %trace, | |
| 216 | fail. | |
| 217 | ||
| 218 | minus( 0'- ). | |
| 219 | plus( 0'+ ). | |
| 220 | digit_table( 0'0 ). | |
| 221 | digit_table( 0'1 ). | |
| 222 | digit_table( 0'2 ). | |
| 223 | digit_table( 0'3 ). | |
| 224 | digit_table( 0'4 ). | |
| 225 | digit_table( 0'5 ). | |
| 226 | digit_table( 0'6 ). | |
| 227 | digit_table( 0'7 ). | |
| 228 | digit_table( 0'8 ). | |
| 229 | digit_table( 0'9 ). | |
| 230 | ||
| 231 | ||
| 232 | spaces --> ws,!,spaces. | |
| 233 | spaces --> []. | |
| 234 | ||
| 235 | % whitespace | |
| 236 | ws --> new_line,!. | |
| 237 | ws --> " "; "\t" ; [10] ; [13]. | |
| 238 | ||
| 239 | new_line --> "\n",{inc_line_nr}. | |
| 240 | ||
| 241 | % use a fact to keep track of line numbers | |
| 242 | :- dynamic cur_line/1. | |
| 243 | cur_line(1). | |
| 244 | inc_line_nr :- retract(cur_line(N)), N1 is N+1, assert(cur_line(N1)). | |
| 245 | reset_line_nr :- retract(cur_line(_)), assert(cur_line(1)). | |
| 246 | ||
| 247 | print_info(_) :- !. | |
| 248 | print_info(Error) :- print_error(Error). | |
| 249 | print_error(Error) :- | |
| 250 | cur_line(LineNr), | |
| 251 | current_output(X), | |
| 252 | set_output(user_error), | |
| 253 | nl, | |
| 254 | write('! Line: '),write_term(LineNr,[]),nl, | |
| 255 | (var(Error) -> print_message(error,'_') | |
| 256 | ; write('! '),write_term(Error,[max_depth(20),numbervars(true)]),nl), | |
| 257 | %% flush_output(user_error), %% | |
| 258 | set_output(X). |