\ JSON parser to import Google+
\ Copyright (C) 2018 Bernd Paysan
\ This program is free software: you can redistribute it and/or modify
\ it under the terms of the GNU Affero General Public License as published by
\ the Free Software Foundation, either version 3 of the License, or
\ (at your option) any later version.
\ This program is distributed in the hope that it will be useful,
\ but WITHOUT ANY WARRANTY; without even the implied warranty of
\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\ GNU Affero General Public License for more details.
\ You should have received a copy of the GNU Affero General Public License
\ along with this program. If not, see <http://www.gnu.org/licenses/>.
require ../tools.fs
scope: regexps
require regexp.fs
}scope
also regexps
Charclass [blT] bl +char 'T' +char
: iso-?date ( addr u -- flag )
(( \( \d \d \d \d \) ` - \( \d \d \) ` - \( \d \d \)
{{ [blT] c?
\( \d \d \) ` : \( \d \d \) ` : \( \d \d \)
{{ ` . \( {++ \d \d \d ++} \) || \( \) }}
{{ ` Z \( \) \( \) ||
{{ ` + \( || \( ` - }} \d \d `? : \d \d \)
}} || \( \) \( \) \( \) \( \) \( \) }} \$ )) ;
: iso-date>ticks ( -- ticks )
\1 s>number drop \2 s>number drop \3 s>number drop ymd2day unix-day0 -
#24 *
\4 s>number drop + #60 * \5 s>number drop +
\8 2 umin s>number drop #60 *
\8 dup 2 - /string s>unumber? 2drop over 0< IF - ELSE + THEN -
#60 * \6 s>number drop +
#1000000000 um*
\7 s>unumber? 2drop
case \7 nip
3 of #1000000 um* endof
6 of #1000 um* endof
0 swap
endcase d+
d>64 ;
previous
Defer ?date
Defer date>ticks
: iso-date
['] iso-?date is ?date
['] iso-date>ticks is date>ticks ;
$Variable key$ \ key string
256 cells buffer: json-tokens
5 stack: jsons-recognizer
1 stack: json-recognizer
' noop ' lit, dup rectype: rectype-bool
' noop ' lit, dup rectype: rectype-nil
s" JSON error" exception Value json-throw
s" JSON key not found" exception Value json-key-throw
s" JSON class not found" exception Value json-class-throw
: json-err cr order json-throw throw ;
0 Value schema-scope
0 Value outer-class
0 Value schema-wid
Defer process-element ' noop is process-element
Defer process-elements ' noop is process-elements
require g+-schema.fs
require fb-schema.fs
require twitter-schema.fs
require diaspora-schema.fs
$10 stack: element-stack
$10 stack: key-stack
$10 stack: array-stack
0 Value array-item
0 Value last-type
0 Value previous-type
: set-val ( value -- )
key$ $@ find-name ?dup-IF (int-to) EXIT THEN
json-err ;
: set-int ( value -- )
key$ $@ find-name ?dup-IF (int-to) EXIT THEN
'%' key$ $@ + 1- c! key$ $@ find-name ?dup-IF
>r s>f r> (int-to) EXIT THEN
'!' key$ $@ + 1- c! key$ $@ find-name ?dup-IF
>r #1000000000 um* d>64 r> (int-to) EXIT THEN
json-err ;
Defer next-element
: next-element# ( element -- )
array-item ?dup-IF >r
case previous-type
rectype-name of endof
rectype-num of r@ >stack endof
rectype-dnum of drop r@ >stack endof
rectype-string of s>number? 0= IF json-err THEN
drop r@ >stack endof
rectype-float of f>s r@ >stack endof
rectype-bool of r@ >stack endof
rectype-nil of r@ >stack endof
endcase rdrop
THEN ;
: f>stack ( r stack -- )
{ f^ r } r 1 floats rot $+! ;
: next-element% ( element -- )
array-item ?dup-IF >r
case previous-type
rectype-name of endof
rectype-float of r@ f>stack endof
rectype-string of over >r >float r> free throw
0= IF json-err THEN r@ f>stack endof
rectype-num of s>f r@ f>stack endof
rectype-dnum of d>f r@ f>stack endof
rectype-bool of s>f r@ f>stack endof
rectype-nil of s>f r@ f>stack endof
endcase rdrop
THEN ;
: next-element$ ( element -- )
array-item ?dup-IF >r
case previous-type
rectype-name of endof
rectype-string of over >r $make r> free throw r@ >stack endof
rectype-num of [: 0 .r ;] $tmp $make r@ >stack endof
rectype-dnum of [: 0 d.r ;] $tmp $make r@ >stack endof
rectype-float of ['] f. $tmp -trailing $make r@ >stack endof
rectype-bool of IF "true" ELSE "false" THEN $make r@ >stack endof
rectype-nil of r@ >stack endof
endcase rdrop
THEN ;
' next-element$ is next-element
: begin-element ( -- )
\ '"' emit key$ $. .\" \": {" cr
key$ $@ schema-scope find-name-in
?dup-IF name>int >body >r
[: key$ $. ." -class" ;] $tmp schema-scope find-name-in
?dup-IF
name>int execute new
dup array-item ?dup-IF
>stack
ELSE
s" {}" key$ $+! set-val
THEN
>o r> element-stack >stack
key$ @ key-stack >stack key$ off
get-order r> swap 1+ set-order
array-item array-stack >stack 0 to array-item
ELSE
cr key$ $. json-class-throw throw
THEN
ELSE
key$ $@len IF
cr key$ $. json-key-throw throw
THEN
THEN ;
: end-array ( -- )
next-element
array-stack stack> to array-item ;
: end-element ( -- )
key$ $free key-stack stack> key$ !
previous element-stack stack> >o rdrop end-array ;
: begin-array ( -- )
array-item array-stack >stack
[: key$ $. ." []" ;] $tmp find-name ?dup-IF
name>int execute to array-item
['] next-element$ is next-element EXIT THEN
[: key$ $. ." []#" ;] $tmp find-name ?dup-IF
name>int execute to array-item
['] next-element# is next-element EXIT THEN
[: key$ $. ." []%" ;] $tmp find-name ?dup-IF
name>int execute to array-item
['] next-element% is next-element EXIT THEN
json-err ;
: key-find? ( char -- nt )
key$ $@ + 1- c! key$ $@ find-name ;
: json-string! ( addr u -- )
over >r
'$' key$ c$+! key$ $@ find-name ?dup-IF (int-to) r> free throw EXIT THEN
\ workaround if you mean number but wrote string
'&' key-find? ?dup-IF
>r s>number? IF r> (int-to) r> free throw EXIT THEN json-err THEN
'#' key-find? ?dup-IF
>r s>number? IF drop r> (int-to) r> free throw EXIT THEN json-err THEN
'!' key-find? ?dup-IF drop
?date IF date>ticks set-val r> free throw EXIT THEN json-err THEN
'%' key-find? ?dup-IF drop
>float IF set-val r> free throw EXIT THEN json-err THEN
r> free throw json-err ;
: eval-json ( .. tag -- )
case
rectype-name of name?int execute endof
rectype-string of json-string! endof
rectype-num of '#' key$ c$+! set-int endof
rectype-dnum of '&' key$ c$+! set-val endof
rectype-float of '%' key$ c$+! set-val endof
rectype-bool of '?' key$ c$+! set-val endof
rectype-nil of drop endof \ default is null, anyhow
json-err
endcase ;
: key-value ( addr u -- ) over >r key$ $! r> free throw
parse-name jsons-recognizer recognize eval-json ;
' begin-element '{' cells json-tokens + !
' end-element '}' cells json-tokens + !
' next-element ',' cells json-tokens + !
' begin-array '[' cells json-tokens + !
' end-array ']' cells json-tokens + !
' key-value ':' cells json-tokens + !
: rec-json ( addr u -- )
1 = IF
c@ cells json-tokens + @
dup IF rectype-name EXIT THEN
THEN
drop rectype-null ;
256 buffer: stop-chars
bl 1+ 0 [do] 1 stop-chars [i] + c! [loop]
"{}[],:\"" bounds [do] 1 stop-chars [i] c@ + c! [loop]
: parse-json ( -- addr u )
source >in @ safe/string
dup 0 U+DO over c@ bl u> ?LEAVE 1 safe/string LOOP
over c@ stop-chars + c@ IF 1 umin ELSE
dup 1 U+DO over I + c@ stop-chars + c@ IF drop I LEAVE THEN LOOP
THEN
2dup + source drop - >in ! 2dup input-lexeme! ;
cs-scope: bools
false rectype-bool 2constant false
true rectype-bool 2constant true
0 rectype-nil 2constant null
}scope
: rec-bool ( addr u -- ... )
['] bools >body find-name-in ?dup-IF
name>int execute
ELSE rectype-null THEN ;
' rec-bool ' rec-num ' rec-float ' rec-string ' rec-json
5 jsons-recognizer set-stack
: rec-jsons ( addr u -- ... json-type )
last-type to previous-type
jsons-recognizer recognize dup to last-type ;
' rec-jsons 1 json-recognizer set-stack
: json-load ( addr u -- o )
outer-class new >o
o element-stack >stack 0 key-stack >stack 0 array-stack >stack
get-order n>r schema-wid 1 set-order
forth-recognizer >r json-recognizer to forth-recognizer
action-of parse-name >r ['] parse-json is parse-name
['] included catch
r> is parse-name r> to forth-recognizer nr> set-order
throw process-element o o> ;
: json-load-dir ( addr u -- )
2dup open-dir throw { dd } fpath dup $@len >r also-path dd
[: { dd | nn } !time
BEGIN
pad $100 dd read-dir throw WHILE
pad swap 2dup "*.json" filename-match IF
json-load entries[] >stack 1 +to nn
nn #37 mod 0= IF
nn [: ." read " 6 .r ." postings" ;]
warning-color color-execute
#-20 0 at-deltaxy
THEN
ELSE 2drop THEN
REPEAT drop
nn [: ." read " 6 .r ." postings in " .time ;]
success-color color-execute cr ;] catch
r> fpath $!len dd close-dir throw throw ;