• JSON reader/writer in PostScript

    From news@zzo38computer.org.invalid@21:1/5 to All on Wed Sep 4 01:19:41 2019
    I wrote a program in PostScript for reading/writing JSON data. You can use
    the following functions:

    * JSON.read ( file -- object ) Read JSON data from the file. An object will become a dictionary, with names (rather than strings) as keys. See also JSON.utf8 below.

    * JSON.write ( file object -- ) Write JSON data to the file. The object
    must be a null, boolean, string, name, integer, real, array, or dictionary.
    The JSON.utf8 option is unused; characters outside of the ASCII range are always written exactly as in the string. (ASCII control characters, and any character not allowed unescaped, will be escaped, though.)

    * JSON.utf8 - You can redefine this (like "/JSON.utf8 true def"); it is
    set to false by default. If false, only the low 8-bits of any character
    code specified by \u escapes are used. If true, then \u escapes will be converted into UTF-8. Bytes that are not escaped will just be passed
    through as is, regardless of this setting.

    Currently, the JSON.utf8 option is not implemented, so it must always be
    false, and it will not work if true. If I fix that later, then I will post
    a follow-up with the corrected program.

    You can also write a follow-up message if you have a comment of it, please.

    The below (between "===BEGIN CODE===" and "===END CODE===") is the
    PostScript program.

    ===BEGIN CODE===
    % JSON implementation in PostScript
    % (public domain)

    currentpacking true setpacking
    /JSON.utf8 false def
    /.JSON.file null def
    /.JSON.str 65535 string def
    /.JSON.hex (16#????) def
    /.JSON.backchar 0 def
    /.JSON.char 0 def

    /.JSON.namech 256 string def
    .JSON.namech 43 1 put %+
    JSON.namech 45 1 put %-
    .JSON.namech 46 1 put %.
    8 1 57 {.JSON.namech exch 1 put} for %0-9
    .JSON.namech 69 1 put %E
    7 1 122 {.JSON.namech exch 1 put} for %a-z

    /.JSON.escape 256 string def
    0 1 255 {.JSON.escape exch dup put} for
    .JSON.escape 98 8 put
    JSON.escape 102 12 put
    .JSON.escape 110 10 put
    JSON.escape 114 13 put
    .JSON.escape 116 9 put
    JSON.escape 117 0 put

    /.JSON.readnum {
    .JSON.str 0 .JSON.char put
    1
    {.JSON.file read {
    .JSON.namech 1 index get 0 eq {
    /.JSON.backchar exch store
    .JSON.str exch 0 exch getinterval cvr
    exit
    } {
    .JSON.str exch 2 index exch put
    1 add
    } ifelse
    } {JSON.badinput} ifelse} loop
    } bind def

    /.JSON.readword {
    {.JSON.file read {
    .JSON.namech 1 index get 0 eq {
    /.JSON.backchar exch store
    exit
    } {pop} ifelse
    } {JSON.badinput} ifelse} loop
    } bind def

    /.JSON.readstring {
    0 {
    .JSON.file read pop
    dup 34 eq {
    % End of string
    pop dup string dup 2 index
    .JSON.str exch 0 exch getinterval
    0 exch putinterval exch pop
    exit
    } {
    % Character
    .JSON.str 2 index 2 index put
    92 eq {
    .JSON.str 1 index .JSON.escape .JSON.file read pop get put
    .JSON.str 1 index get 0 eq {
    .JSON.file .JSON.hex 3 4 getinterval readstring pop pop
    /.JSON.char .JSON.hex cvi store
    .JSON.char 127 gt JSON.utf8 and {
    % Not yet implemented
    } {
    .JSON.str 1 index .JSON.char 255 and put
    } ifelse
    } if
    } if
    1 add
    } ifelse
    } loop
    } bind def

    /.JSON.parsech 256 array def
    33 1 255 {.JSON.parsech exch /JSON.badinput cvx put} for
    0 1 32 {.JSON.parsech exch null cvx put} for %spaces
    .JSON.parsech 34 /.JSON.readstring load put %"
    JSON.parsech 43 /.JSON.readnum load put %+
    .JSON.parsech 44 null cvx put %,
    JSON.parsech 45 /.JSON.readnum load put %-
    48 1 57 {.JSON.parsech exch /.JSON.readnum load put} for %numbers
    .JSON.parsech 58 /cvn load put %:
    JSON.parsech 91 mark put %[
    .JSON.parsech 93 (]) cvn load put %]
    JSON.parsech 102 {false .JSON.readword} put %f
    .JSON.parsech 110 {null .JSON.readword} put %n
    JSON.parsech 116 {true .JSON.readword} put %t
    .JSON.parsech 123 mark put %{
    JSON.parsech 125 (>>) cvn load put %}

    /.JSON.parse { % ( -- object )
    .JSON.backchar 0 eq {
    .JSON.file read
    } {
    .JSON.backchar /.JSON.backchar 0 store true
    } ifelse {
    /.JSON.char exch store
    .JSON.parsech .JSON.char get exec
    .JSON.parse
    } if
    } bind def

    /.JSON.charx [32 {true} repeat 224 {false} repeat] def
    .JSON.charx 34 true put
    JSON.charx 92 true put
    /.JSON.writechar {
    .JSON.charx 1 index get {
    .JSON.file (\\u00) writestring
    dup 16 lt {.JSON.file 48 write} if
    16 .JSON.str cvrs .JSON.file exch writestring
    } {
    .JSON.file exch write
    } ifelse
    } def

    /.JSON.writedict <<
    /arraytype {.JSON.file 91 write false exch {exch {.JSON.file 44 write} if .JSON.write true} forall pop .JSON.file 93 write}
    /booleantype {.JSON.str cvs .JSON.file exch writestring}
    /dicttype {.JSON.file 123 write false exch {3 -1 roll {.JSON.file 44 write} if exch .JSON.write .JSON.file 58 write .JSON.write true} forall pop .JSON.file 125 write}
    /integertype {.JSON.str cvs .JSON.file exch writestring}
    /nametype {.JSON.str cvs .JSON.write}
    /nulltype {.JSON.file (null) writestring}
    /realtype {.JSON.str cvs .JSON.file exch writestring}
    /stringtype {.JSON.file 34 write {.JSON.writechar} forall .JSON.file 34 write}
    def

    /JSON.read {/.JSON.file exch store .JSON.parse} bind def
    /.JSON.write {dup type .JSON.writedict exch get exec} bind def
    /JSON.write {exch /.JSON.file exch store .JSON.write .JSON.file 10 write} bind def

    setpacking
    ===END CODE===


    --
    Note: I am not always able to read/post messages during Monday-Friday.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to ne...@zzo38computer.org.invalid on Thu Sep 5 23:05:50 2019
    On Tuesday, September 3, 2019 at 8:20:26 PM UTC-5, ne...@zzo38computer.org.invalid wrote:
    I wrote a program in PostScript for reading/writing JSON data. You can use the following functions:
    /.JSON.namech 256 string def
    .JSON.namech 43 1 put %+
    JSON.namech 45 1 put %-
    .JSON.namech 46 1 put %.

    I'd be tempted to write '(+) 0 get' instead of '43'. A little less
    efficient perhaps, but this part is one-time setup code so not
    critical. But using a string makes the comments unnecessary.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Fri Sep 6 22:06:07 2019
    On Friday, September 6, 2019 at 1:05:51 AM UTC-5, luser droog wrote:
    On Tuesday, September 3, 2019 at 8:20:26 PM UTC-5, ne...@zzo38computer.org.invalid wrote:
    I wrote a program in PostScript for reading/writing JSON data. You can use the following functions:
    /.JSON.namech 256 string def
    .JSON.namech 43 1 put %+
    JSON.namech 45 1 put %-
    .JSON.namech 46 1 put %.

    I'd be tempted to write '(+) 0 get' instead of '43'. A little less
    efficient perhaps, but this part is one-time setup code so not
    critical. But using a string makes the comments unnecessary.

    And you could put them all in a single string and loop through it.

    /.JSON.namech 256 string def
    (+-.) { .JSON.namech exch 1 put } forall

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From news@zzo38computer.org.invalid@21:1/5 to All on Sun Sep 8 21:32:25 2019
    JSON.utf8 is now implemented (although it doesn't currently convert
    surrogate pairs into a single UTF-8 codepoint), and another change as
    suggested by some of the other follow-up messages to my first message
    have also been implemented. Also, the "dot stuffing" of this message is (hopefully) correct now; it was a bug in the NNTP software I was using,
    but I have now corrected that bug (hopefully).

    Also, I would like to know if you have any use for this, and especially if
    you have found any bugs in it. (This is not mandatory though; it is public domain, so you are allowed to do whatever you want with it.)

    The below (between "===BEGIN CODE===" and "===END CODE===") is the
    PostScript program.

    ===BEGIN CODE===
    % JSON implementation in PostScript
    % (public domain)

    currentpacking true setpacking
    /JSON.utf8 false def
    /.JSON.file null def
    /.JSON.str 65535 string def
    /.JSON.hex (16#????) def
    /.JSON.backchar 0 def
    /.JSON.char 0 def

    /.JSON.namech 256 string def
    48 1 57 {.JSON.namech exch 1 put} for %0-9
    97 1 122 {.JSON.namech exch 1 put} for %a-z
    (+-.E) {.JSON.namech exch 1 put} forall

    /.JSON.escape 256 string def
    0 1 255 {.JSON.escape exch dup put} for
    .JSON.escape 98 8 put
    .JSON.escape 102 12 put
    .JSON.escape 110 10 put
    .JSON.escape 114 13 put
    .JSON.escape 116 9 put
    .JSON.escape 117 0 put

    /.JSON.readnum {
    .JSON.str 0 .JSON.char put
    1
    {.JSON.file read {
    .JSON.namech 1 index get 0 eq {
    /.JSON.backchar exch store
    .JSON.str exch 0 exch getinterval cvr
    exit
    } {
    .JSON.str exch 2 index exch put
    1 add
    } ifelse
    } {JSON.badinput} ifelse} loop
    } bind def

    /.JSON.readword {
    {.JSON.file read {
    .JSON.namech 1 index get 0 eq {
    /.JSON.backchar exch store
    exit
    } {pop} ifelse
    } {JSON.badinput} ifelse} loop
    } bind def

    /.JSON.readstring {
    0 {
    .JSON.file read pop
    dup 34 eq {
    % End of string
    pop dup string dup 2 index
    .JSON.str exch 0 exch getinterval
    0 exch putinterval exch pop
    exit
    } {
    % Character
    .JSON.str 2 index 2 index put
    92 eq {
    .JSON.str 1 index .JSON.escape .JSON.file read pop get put
    .JSON.str 1 index get 0 eq {
    .JSON.file .JSON.hex 3 4 getinterval readstring pop pop
    /.JSON.char .JSON.hex cvi store
    .JSON.char 127 gt JSON.utf8 and {
    .JSON.char 16#0800 lt {
    % Two byte UTF-8
    .JSON.str 1 index .JSON.char -6 bitshift 16#C0 or put
    1 add
    .JSON.str 1 index .JSON.char 16#3F and 16#80 or put
    } {
    % Three byte UTF-8
    .JSON.str 1 index .JSON.char -12 bitshift 16#E0 or put
    1 add
    .JSON.str 1 index .JSON.char -6 bitshift 16#3F and 16#80 or put
    1 add
    .JSON.str 1 index .JSON.char 16#3F and 16#80 or put
    } ifelse
    } {
    .JSON.str 1 index .JSON.char 255 and put
    } ifelse
    } if
    } if
    1 add
    } ifelse
    } loop
    } bind def

    /.JSON.parsech 256 array def
    33 1 255 {.JSON.parsech exch /JSON.badinput cvx put} for
    0 1 32 {.JSON.parsech exch null cvx put} for %spaces
    .JSON.parsech 34 /.JSON.readstring load put %"
    .JSON.parsech 43 /.JSON.readnum load put %+
    .JSON.parsech 44 null cvx put %,
    .JSON.parsech 45 /.JSON.readnum load put %-
    48 1 57 {.JSON.parsech exch /.JSON.readnum load put} for %numbers
    .JSON.parsech 58 /cvn load put %:
    .JSON.parsech 91 mark put %[
    .JSON.parsech 93 (]) cvn load put %]
    .JSON.parsech 102 {false .JSON.readword} put %f
    .JSON.parsech 110 {null .JSON.readword} put %n
    .JSON.parsech 116 {true .JSON.readword} put %t
    .JSON.parsech 123 mark put %{
    .JSON.parsech 125 (>>) cvn load put %}

    /.JSON.parse { % ( -- object )
    .JSON.backchar 0 eq {
    .JSON.file read
    } {
    .JSON.backchar /.JSON.backchar 0 store true
    } ifelse {
    /.JSON.char exch store
    .JSON.parsech .JSON.char get exec
    .JSON.parse
    } if
    } bind def

    /.JSON.charx [32 {true} repeat 224 {false} repeat] def
    .JSON.charx 34 true put
    .JSON.charx 92 true put
    /.JSON.writechar {
    .JSON.charx 1 index get {
    .JSON.file (\\u00) writestring
    dup 16 lt {.JSON.file 48 write} if
    16 .JSON.str cvrs .JSON.file exch writestring
    } {
    .JSON.file exch write
    } ifelse
    } def

    /.JSON.writedict <<
    /arraytype {.JSON.file 91 write false exch {exch {.JSON.file 44 write} if .JSON.write true} forall pop .JSON.file 93 write}
    /booleantype {.JSON.str cvs .JSON.file exch writestring}
    /dicttype {.JSON.file 123 write false exch {3 -1 roll {.JSON.file 44 write} if exch .JSON.write .JSON.file 58 write .JSON.write true} forall pop .JSON.file 125 write}
    /integertype {.JSON.str cvs .JSON.file exch writestring}
    /nametype {.JSON.str cvs .JSON.write}
    /nulltype {.JSON.file (null) writestring}
    /realtype {.JSON.str cvs .JSON.file exch writestring}
    /stringtype {.JSON.file 34 write {.JSON.writechar} forall .JSON.file 34 write}
    def

    /JSON.read {/.JSON.file exch store .JSON.parse} bind def
    /.JSON.write {dup type .JSON.writedict exch get exec} bind def
    /JSON.write {exch /.JSON.file exch store .JSON.write .JSON.file 10 write} bind def

    setpacking
    ===END CODE===

    --
    Note: I am not always able to read/post messages during Monday-Friday.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to ne...@zzo38computer.org.invalid on Thu Sep 12 19:11:02 2019
    On Sunday, September 8, 2019 at 4:33:23 PM UTC-5, ne...@zzo38computer.org.invalid wrote:
    JSON.utf8 is now implemented (although it doesn't currently convert
    surrogate pairs into a single UTF-8 codepoint), and another change as suggested by some of the other follow-up messages to my first message
    have also been implemented. Also, the "dot stuffing" of this message is (hopefully) correct now; it was a bug in the NNTP software I was using,
    but I have now corrected that bug (hopefully).

    Also, I would like to know if you have any use for this, and especially if you have found any bugs in it. (This is not mandatory though; it is public domain, so you are allowed to do whatever you want with it.)


    I'm not sure how useful it is. Whenever I've needed to send data to postscript, it has always been fairly easy to output valid postscript code. But I think this *is* a fun little exercise. I spent this afternoon/evening writing my
    own using the parser combinators. Probably some bugs in edge cases I haven't considered. But simple tests appear to be working! It doesn't do any utf conversion. It doesn't remove whitespace characters.

    I'd be curious to see a speed comparison between our two programs (if only
    to see how much slower mine probably is). But I'm not quite curious enough
    to do it myself.

    Additional files at: https://github.com/luser-dr00g/pcomb/tree/369a1a508ab16aaa0a707f0a10f8391303f61397/ps


    %!
    %cf. https://tools.ietf.org/html/rfc7159
    (pc9re2.ps)run
    %errordict/typecheck{/typecheck = pstack / = countexecstack array execstack == quit}put
    /SEQ {{seq}reduce} def
    /PLUS {{plus}reduce} def
    /str {{lit}map SEQ} def
    /snip-ends { 1 1 index length 2 sub getinterval } def

    /ws ( \t\n\r) anyof many def
    /begin-array [ //ws ([) char //ws ] SEQ def
    /begin-object [ //ws ({) char //ws ] SEQ def
    /end-array [ //ws (]) char //ws ] SEQ def
    /end-object [ //ws (}) char //ws ] SEQ def
    /name-separator [ //ws (:) char //ws ] SEQ def
    /value-separator [ //ws (,) char //ws ] SEQ def
    /value {-777 exec} def

    /decimal-point (.) char def
    /digit1-9 (19) spill range def
    /digit (09) spill range def
    /Jzero (0) char def
    /e (eE) anyof def
    /Jplus (+) char def
    /minus (-) char def
    /expn [ //e //minus //Jplus plus maybe //digit some ] SEQ def
    /frac [ //decimal-point //digit some ] SEQ def
    /int //Jzero //digit1-9 //digit many seq plus def
    /number [ //minus maybe //int //frac maybe //expn maybe ] SEQ
    {
    to-string
    dup (.) search { pop pop pop cvr }{
    pop dup (e) search { pop pop pop cvr }{
    pop dup (E) search { pop pop pop cvr }{ pop cvi } ifelse
    } ifelse
    } ifelse
    } using def

    /quotation-mark (") char def
    /unescaped [ 16#20 16#21 range 16#23 16#5B range 16#5D 16#10FFFF range ] PLUS def
    /escape (\\) char def
    /4hexdig (u) char //digit (AF) spill range plus 3{dup seq}repeat seq def
    /escape-sequence //escape [ ("\\/bfnrt) anyof //4hexdig ] PLUS seq def
    /Jchar //unescaped //escape-sequence plus def
    /Jstring [ //quotation-mark //Jchar many //quotation-mark ] SEQ
    { to-string snip-ends } using def

    /member [ //Jstring //name-separator //value ] SEQ def
    /Jobject [ //begin-object
    //member //value-separator //member seq many seq maybe
    //end-object ] SEQ
    {
    1 dict begin
    flatten snip-ends
    3 { spill exch pop def } fortuple
    currentdict end
    } using def

    /Jarray [ //begin-array
    //value //value-separator //value seq many seq maybe
    //end-array ] SEQ
    { flatten snip-ends } using def

    //value 0 [ (false) str (null) str (true) str
    //Jobject //Jarray //number //Jstring ] PLUS put
    /JSON-text [ //ws //value //ws ] SEQ def

    /JSON-parse { JSON-text first first second } def

    (4) string-input JSON-parse pc
    (4.0) string-input JSON-parse pc
    (4e3) string-input JSON-parse pc
    ("4") string-input JSON-parse pc
    ([4]) string-input JSON-parse pc
    ({"a":4}) string-input JSON-parse ps ===
    quit


    Output:
    $ make
    gsnd -dNOSAFER pc9json.ps
    GPL Ghostscript 9.27 (2019-04-04)
    Copyright (C) 2018 Artifex Software, Inc. All rights reserved.
    This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
    see the file COPYING for details.
    stack:
    4
    stack:
    4.0
    stack:
    4000.0
    stack:
    (4)
    stack:
    [4]
    stack:
    -dict-
    << /a 4 >>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to ne...@zzo38computer.org.invalid on Fri Sep 13 19:53:29 2019
    On Sunday, September 8, 2019 at 4:33:23 PM UTC-5, ne...@zzo38computer.org.invalid wrote:
    JSON.utf8 is now implemented (although it doesn't currently convert
    surrogate pairs into a single UTF-8 codepoint), and another change as suggested by some of the other follow-up messages to my first message
    have also been implemented. Also, the "dot stuffing" of this message is (hopefully) correct now; it was a bug in the NNTP software I was using,
    but I have now corrected that bug (hopefully).

    Also, I would like to know if you have any use for this, and especially if you have found any bugs in it. (This is not mandatory though; it is public domain, so you are allowed to do whatever you want with it.)

    No bugs found (in your code, mine's another story) but I do have a few comments.

    The below (between "===BEGIN CODE===" and "===END CODE===") is the
    PostScript program.

    ===BEGIN CODE===
    [snip]
    .JSON.parsech 43 /.JSON.readnum load put %+

    This is probably a good idea. But the spec I read actually only
    specifies an optional minus sign. Apparently numbers ought not
    to begin with a plus sign. But I'll bet tons of code(rs) out there
    expect this work.

    [snip]
    .JSON.parsech 125 (>>) cvn load put %}

    The 'cvn' here is not strictly necessary. Strings will automatically
    be converted into names when used as dict keys.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From luser droog@21:1/5 to luser droog on Sat Sep 14 19:50:44 2019
    On Thursday, September 12, 2019 at 9:11:03 PM UTC-5, luser droog wrote:
    On Sunday, September 8, 2019 at 4:33:23 PM UTC-5, ne...@zzo38computer.org.invalid wrote:
    JSON.utf8 is now implemented (although it doesn't currently convert surrogate pairs into a single UTF-8 codepoint), and another change as suggested by some of the other follow-up messages to my first message
    have also been implemented. Also, the "dot stuffing" of this message is (hopefully) correct now; it was a bug in the NNTP software I was using,
    but I have now corrected that bug (hopefully).

    Also, I would like to know if you have any use for this, and especially if you have found any bugs in it. (This is not mandatory though; it is public domain, so you are allowed to do whatever you want with it.)


    I'm not sure how useful it is. Whenever I've needed to send data to postscript,
    it has always been fairly easy to output valid postscript code. But I think this *is* a fun little exercise. I spent this afternoon/evening writing my own using the parser combinators. Probably some bugs in edge cases I haven't considered. But simple tests appear to be working! It doesn't do any utf conversion. It doesn't remove whitespace characters.

    I'd be curious to see a speed comparison between our two programs (if only
    to see how much slower mine probably is). But I'm not quite curious enough
    to do it myself.

    <snip>

    Improved my code to handle spaces and nested structures. I had to
    debug and modify part of the parser combinator library to get it
    to work.

    The problem I was having was the result from a parser
    produced by the 'maybe' combinator. A maybe parser has to succeed
    regardless of the success or failure of its child parser. So in the
    case where the child parser fails, 'maybe' has to return *something*.
    Earlier I had it set up to return an empty array [], but the problem
    now was that some other part of the machinery (haven't tracked it
    down precisely yet) was interpreting that as failure instead of
    success with an empty result.

    I changed it now to return the name /0. Since I'm not using names
    anywhere in the data (relying on the automatic behavior of 'def')
    I can detect these as part of the scaffolding of the parse tree.
    I can't use 'null' because json data may contain the keyword null.
    This version doesn't actually convert null (or true or false) but
    it's a trivial change.

    The next issue is: I don't understand what to do with unicode
    characters if they are discovered. It appears that OP's code
    reads in the multibyte sequences, constructs the codepoint in
    an int, and then truncates that to 8 bits and stores it in a
    string. That doesn't seem right, but I can't really think of
    anything better. Maybe an option either to leave the utf8 alone,
    or convert to arrays of integers? It's not clear to me what
    a PostScript program could hope to do with unicode data.

    So I haven't written any utf8 handling. If I do add it, I think
    it should be added to the parser library itself as an input
    filter. The C version has these already.

    Additional referenced files at latest commit: https://github.com/luser-dr00g/pcomb/tree/d4cc78d862c84873fb05a593df2ea5d878988b81/ps

    %!
    %cf. https://tools.ietf.org/html/rfc7159
    (pc9re2.ps)run
    %errordict/typecheck{/typecheck = pstack / = countexecstack array execstack == quit}put
    %errordict/rangecheck{/rangecheck = pstack / = countexecstack array execstack == quit}put
    /SEQ {{seq}reduce} def
    /PLUS {{plus}reduce} def
    /str {{lit}map SEQ} def
    /filter-zeros { { dup /0 eq {pop} if } map } def
    /snip-ends { 1 1 index length 2 sub getinterval } def
    /! { { dup /0 eq { pop [] } if } using } def
    /maybe! { maybe ! } def
    /many! { many ! } def
    /dump { dup first exch
    second dup type /nametype eq 1 index /0 eq and { pop }{ dump } ifelse } def
    /to-array { dup type /arraytype ne { one }{ [ exch dump ] } ifelse } def /to-pairs {
    dup length 1 eq { first pairs }{
    [ exch dup first exch second dump ]
    {spill spill} map pairs
    } ifelse
    } def

    /ws ( \t\n\r) anyof many def
    /begin-array //ws ([) char xthen //ws thenx def
    /begin-object //ws ({) char xthen //ws thenx def
    /end-array //ws (]) char xthen //ws thenx def
    /end-object //ws (}) char xthen //ws thenx def
    /name-separator //ws (:) char xthen //ws thenx def
    /value-separator //ws (,) char xthen //ws thenx def
    /value {-777 exec} def

    /decimal-point (.) char def
    /digit1-9 (19) spill range def
    /digit (09) spill range def
    /Jzero (0) char def
    /e (eE) anyof def
    /Jplus (+) char def
    /minus (-) char def
    /frac //decimal-point //digit some seq def
    /expn [ //e //minus //Jplus plus maybe! //digit some ] SEQ def
    /int //Jzero //digit1-9 //digit many seq plus def
    /number [ //minus maybe! //int //frac maybe! //expn maybe! ] SEQ
    {
    flatten filter-zeros to-string
    dup (.) search { pop pop pop cvr }{
    pop dup (e) search { pop pop pop cvr }{
    pop dup (E) search { pop pop pop cvr }{ pop cvi } ifelse
    } ifelse
    } ifelse
    } using
    def

    /quotation-mark (") char def
    /unescaped [ 16#20 16#21 range 16#23 16#5B range 16#5D 16#10FFFF range ] PLUS def
    /escape (\\) char def
    /4hexdig (u) char //digit (AF) spill range plus 3{dup seq}repeat seq def
    /escape-sequence //escape [ ("\\/bfnrt) anyof //4hexdig ] PLUS seq def
    /Jchar //unescaped //escape-sequence plus def
    /Jstring [ //quotation-mark //Jchar many! //quotation-mark ] SEQ
    { flatten filter-zeros snip-ends to-string } using
    def

    /member //Jstring //name-separator thenx //value seq
    { one } using
    def
    /Jobject //begin-object
    //member xthen
    //value-separator //member xthen many! seq maybe!
    //end-object thenx
    { to-pairs } using
    def

    /Jarray //begin-array
    //value xthen
    //value-separator //value xthen many! seq maybe!
    //end-array thenx
    { to-array } using
    def

    //value 0 [ (false) str (null) str (true) str
    //Jobject //Jarray //number //Jstring ] PLUS put
    /JSON-text //ws //value xthen //ws thenx def

    /JSON-parse {
    JSON-text first first
    } def

    %(4) dup string-input JSON-parse pc
    %( 4) dup string-input JSON-parse pc
    %(4 ) dup string-input JSON-parse pc
    %( 4 ) dup string-input JSON-parse pc
    %( 4.0 ) dup string-input JSON-parse pc
    ( 4e3 ) dup string-input JSON-parse pc
    ( "4" ) dup string-input JSON-parse pc
    ( [ 4 ] ) dup string-input JSON-parse pc
    ( [ 4, 5 ] ) dup string-input JSON-parse pc
    ( [ 3, [ 4, [ 5 ] ] ] ) dup string-input JSON-parse pc
    ( {"a":4,"b":5} ) dup string-input JSON-parse ps === clear
    ( { "a" : 4 , "b" : 5 } ) dup string-input JSON-parse ps === clear
    ( [ {"a":4,"b":5}, 6, {"c":"7"}] ) dup string-input JSON-parse ps === clear quit

    Output:
    $ make
    gsnd -dNOSAFER pc9json.ps
    GPL Ghostscript 9.27 (2019-04-04)
    Copyright (C) 2018 Artifex Software, Inc. All rights reserved.
    This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
    see the file COPYING for details.
    stack:
    4000.0
    ( 4e3 )
    stack:
    (4)
    ( "4" )
    stack:
    [4]
    ( [ 4 ] )
    stack:
    [4 5]
    ( [ 4, 5 ] )
    stack:
    [3 [4 [5]]]
    ( [ 3, [ 4, [ 5 ] ] ] )
    stack:
    -dict-
    ( {"a":4,"b":5} )
    << /a 4 /b 5 >>
    stack:
    -dict-
    ( { "a" : 4 , "b" : 5 } )
    << /a 4 /b 5 >>
    stack:
    [-dict- 6 -dict-]
    ( [ {"a":4,"b":5}, 6, {"c":"7"}] )
    [<< /a 4 /b 5 >> 6 << /c (7) >>]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From news@zzo38computer.org.invalid@21:1/5 to luser droog on Sun Sep 15 18:34:51 2019
    luser droog <luser.droog@gmail.com> wrote:

    The next issue is: I don't understand what to do with unicode
    characters if they are discovered. It appears that OP's code
    reads in the multibyte sequences, constructs the codepoint in
    an int, and then truncates that to 8 bits and stores it in a
    string. That doesn't seem right, but I can't really think of
    anything better. Maybe an option either to leave the utf8 alone,
    or convert to arrays of integers? It's not clear to me what
    a PostScript program could hope to do with unicode data.

    So I haven't written any utf8 handling. If I do add it, I think
    it should be added to the parser library itself as an input
    filter. The C version has these already.

    The first version of my program will treat \u escapes in the way you
    mention; only the low 8 bits of the codepoints are used.

    The second version of my program has an option to instead convert any \u escapes into UTF-8 encoding. (However, it will not convert surrogate pairs
    into astral characters.)

    Regardless of the version and of the option, if it reads any unescaped non-ASCII characters, they will be passed through as is; it will not
    interpret UTF-8 input at all, but just passes it through.

    You might be able to write UTF-8 text on the page with codespace ranges,
    so maybe there is the possibility to use Unicode data in that way.

    If you want to add UTF-8 handling in your own program though, you can do
    it whichever way you think is good, I think.

    --
    Note: I am not always able to read/post messages during Monday-Friday.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)