(def match-json-string ()
(match-char #\"
(liststr:accum a
(while (~match-char #\")
(atend-err "missing close quote")
(a (or (match-json-backslash)
(readc (stdin))))))))
I took shader's suggestion to use an input-port with peekc and readc. I figured that using a parameter to keep track of the parsing state was the way to go, and then realized that we already had a parameter (stdin) which did everything I needed it to. And taking Adlai's suggestion I made match-char a macro which avoids the extra fn's I had in the original.
Getting the parse position out of the function really broke the logjam. The old version was doing two things at once -- keeping track of the parse position and matching characters -- and that made it really hard to pull out bits of functionality into their own functions to make the main function shorter and clearer. Now it's much easier to tell what the function is doing! :)
Here's the rest of the code:
(def hexdigit (c)
(and (isa c 'char)
(or (<= #\a c #\f) (<= #\A c #\F) (<= #\0 c #\9))))
(def json-unicode-digits ()
(let u (n-of 4 (readc (stdin)))
(unless (all hexdigit u) (err "need 4 hexadecimal digits after \\u"))
(coerce (int (coerce u 'string) 16) 'char)))
; json-backslash-char is the same
(mac match-char (c . body)
`(when (is (peekc (stdin)) ,c)
(readc (stdin))
,@body))
(def atend-err (msg)
(unless (peekc (stdin))
(err msg)))
(def match-json-unicode-escape ()
(match-char #\u (json-unicode-digits)))
(def match-json-backslash ()
(match-char #\\
(atend-err "missing char after backslash")
(or (match-json-unicode-escape)
(json-backslash-char (readc (stdin))))))
(def liststr (l)
(coerce l 'string))
As you can see there's some more that could be done: a next-char function for (readc (stdin)) perhaps, and match-json-unicode-escape is so simple now that it could be inlined. That will be easy to work on :-)
According to arcfn.com and the code that it references, it looks like it's supposed to default to stdin, but it's written in such a way to require at least one argument. If you pass nil, it reads from stdin because of the default.
Which requires at least on argument. It should probably be changed so that that argument is actually optional, but I don't know how. I should read up on my mzscheme ;)
I'm not certain - I haven't tested it, but it looks right. Unless scheme and arc have different ideas of false, which could cause more problems. Maybe it should just be:
hmmm. It seems that mzscheme (at least the version that I'm running arc on, 360) doesn't support optional args. I guess I'll have to do it some other way.
It's fairly tedious to be doing this in Scheme, isn't it? We might let Scheme handle implementing the low level readc, and then in Arc redefine readc to be a more advanced function that can take an optional argument:
(mac redef (name parms . body)
" Redefine a function. The old function definition may be used within
`body' as the name `old'. "
`(do (tostring
(let old (varif ,name nilfn)
(= ,name (fn ,parms ,@body))))
,name))
It's the same as yours, except (a) it suppresses the warning on re-assigning an identifier, and (b) it calls the original function old.
btw, why is there no peekb? Or any other functions that work on bytes?
As part of the design process of finding the shortest Arc implementation, pg is careful not to include functions that he isn't actually using for news. This leads to some surprises (car, cdr, cadr, cddr, but no cdar?) but also removes cruft that builds up implementing things that people might need some day but turn out not to.
It turns out not to be a problem, since Arc is so concise it's really easy to extend, and so people quickly implement the things they need that pg happens not to be using for news.
I find I prefer the Arc approach, since libraries that try to provide everything I might need often have so much stuff that ironically they make it harder to implement what I actually need.
If you need the change right away you can patch ac.scm yourself, or, if you don't mind waiting, pg will eventually have a new arc3.tar containing the update.
Side note: compare the "profiles" of this version, and the earlier versions -- this one has a much more "functional" profile.
I'm a bit confused what you mean about atend:err. Do you basically mean that there would be function composition between a macro, and a call to, for example, (err "missing char after backslash")? Something like
Do you basically mean that there would be function composition between a macro, and a call
Yes, a composition, though not a function composition. Because the Arc compiler rewrites (a:b ...) as (a (b ...)) when a:b appears in the first position in an expression, it works for macros also.
Thus
(atend:err "missing close quote")
expands into
(atend (err "missing close quote"))
which macro expands into
(unless (peekc (stdin))
(err "missing close quote"))