{"id":7,"date":"2008-04-09T13:30:00","date_gmt":"2008-04-09T18:30:00","guid":{"rendered":"http:\/\/coherentpdf.com\/blog\/?p=7"},"modified":"2008-06-30T04:34:58","modified_gmt":"2008-06-30T09:34:58","slug":"a-simple-parser-with-genlex","status":"publish","type":"post","link":"https:\/\/coherentpdf.com\/blog\/?p=7","title":{"rendered":"A Simple Parser with Genlex"},"content":{"rendered":"<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">(If you&#8217;re reading this via the OCaml planet, click the post title to see it with proper formatting &#8211; I shall fix this problem for the next post, I hope.)<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">When calling our <a href=\"http:\/\/www.coherentpdf.com\/\">command line PDF Tools<\/a> to, for instance, merge files, one can use page specifiers with a simple grammar. For instance:<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">cpdf file.pdf 1,2,6-end -o out.pdf<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\"><br \/><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">cpdf in.pdf 1,all -o fax.pdf<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">(The second example is useful to duplicate the first page of a document as a fax cover sheet)<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">If the grammar is simple, OCaml&#8217;s Genlex plus a small recursive parser is sufficient.<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">Here&#8217;s an informal description of the mini language:<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\">\u2022<span style=\"font: 12.0px Helvetica\"> <\/span>A dash (-) de\ufb01nes ranges, e.g.<span style=\"font: 12.0px Helvetica\"> <\/span>1-5<span style=\"font: 12.0px Helvetica\"> <\/span>or<span style=\"font: 12.0px Helvetica\"> <\/span>6-3.<span style=\"font: 12.0px Helvetica\">\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\">\u2022<span style=\"font: 12.0px Helvetica\"> <\/span>A comma (,) allows one to specify several ranges, e.g.<span style=\"font: 12.0px Helvetica\"> <\/span>1-2,4-5.<span style=\"font: 12.0px Helvetica\">\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\">\u2022<span style=\"font: 12.0px Helvetica\"> <\/span>The word<span style=\"font: 12.0px Helvetica\">\u00a0&#8220;<\/span>end&#8221;<span style=\"font: 12.0px Helvetica\"> <\/span>represents the last page number.<span style=\"font: 12.0px Helvetica\">\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\">\u2022<span style=\"font: 12.0px Helvetica\"> <\/span>The words<span style=\"font: 12.0px Helvetica\">\u00a0&#8220;<\/span>odd&#8221;<span style=\"font: 12.0px Helvetica\"> <\/span>and<span style=\"font: 12.0px Helvetica\">\u00a0&#8220;<\/span>even&#8221;<span style=\"font: 12.0px Helvetica\"> <\/span>represent the odd and even pages.<span style=\"font: 12.0px Helvetica\">\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\">\u2022<span style=\"font: 12.0px Helvetica\"> <\/span>The word<span style=\"font: 12.0px Helvetica\">\u00a0&#8220;<\/span>reverse&#8221;<span style=\"font: 12.0px Helvetica\"> <\/span>is the same as<span style=\"font: 12.0px Helvetica\"> <\/span>end-1.<span style=\"font: 12.0px Helvetica\">\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\">\u2022<span style=\"font: 12.0px Helvetica\"> <\/span>The word<span style=\"font: 12.0px Helvetica\">\u00a0&#8220;<\/span>all&#8221;<span style=\"font: 12.0px Helvetica\">\u00a0<\/span>is the same as<span style=\"font: 12.0px Helvetica\"> <\/span>1-end.<span style=\"font: 12.0px Helvetica\">\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\">\u2022<span style=\"font: 12.0px Helvetica\"> <\/span>A range must contain no spaces.<span style=\"font: 12.0px Helvetica\">\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 10.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-size:12px;\"><br \/><\/span><\/p>\n<div><span class=\"Apple-style-span\"   style=\" ;font-family:Helvetica;font-size:12px;\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">Our input is the string in this language, our output an ordered list of page numbers. (I&#8217;m using some functions from the Utility module available with CamlPDF)<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">First build a lexer:<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">let lexer =<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 Genlex.make_lexer [&#8220;-&#8220;; &#8220;,&#8221;; &#8220;all&#8221;; &#8220;reverse&#8221;; &#8220;even&#8221;; &#8220;odd&#8221;; &#8220;end&#8221;]<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">and define a utility function which lexes a string to a list of Genlex lexemes (no need to be lazy here):<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">let lexwith s =<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 list_of_stream (lexer (Stream.of_string s))<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">Now, this language is quite simple to deal with. We&#8217;ll pattern-match for all the simple cases &#8211; anything which doesn&#8217;t contain a comma is finite. If nothing matches, we assume the string contains one or more commas, and attempt to split it up.<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">Here is the main function, which is given the end page of the PDF file to which the range applies (the start page is always 1), and a list of tokens to match against:<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">let rec mk_numbers endpage = function<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Int n] -> [n]<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Int n; Genlex.Kwd &#8220;-&#8220;; Genlex.Int n&#8217;] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 if n > n&#8217; then rev (ilist n&#8217; n) else ilist n n&#8217;<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Kwd &#8220;end&#8221;; Genlex.Kwd &#8220;-&#8220;; Genlex.Int n] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 if n <= endpage<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 \u00a0 then rev (ilist n endpage)<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 \u00a0 else failwith &#8220;n > endpage&#8221;<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Int n; Genlex.Kwd &#8220;-&#8220;; Genlex.Kwd &#8220;end&#8221;] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 if n <= endpage<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 \u00a0 then ilist n endpage<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 \u00a0 else failwith &#8220;n > endpage2&#8221;<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Kwd &#8220;end&#8221;; Genlex.Kwd &#8220;-&#8220;; Genlex.Kwd &#8220;end&#8221;] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 [endpage]<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Kwd &#8220;even&#8221;] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 drop_odds (ilist 1 endpage)<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Kwd &#8220;odd&#8221;] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 really_drop_evens (ilist 1 endpage)<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Kwd &#8220;all&#8221;] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 ilist 1 endpage<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [Genlex.Kwd &#8220;reverse&#8221;] -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 rev (ilist 1 endpage)<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | toks -><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 let ranges = splitat_commas toks in<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 \u00a0(* Check we&#8217;ve made progress *)<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 \u00a0if ranges = [toks] then error &#8220;Bad page range&#8221; else\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 flatten (map (mk_numbers endpage) ranges)<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">(<span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">ilist x y<\/span>\u00a0 produces the list <span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">[x, x + 1, &#8230; y &#8211; 1, y]<\/span>, \u00a0<span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">drop_odds [1;2;3;4;5;6;7]<\/span> is <span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">[2;4;6]<\/span>, <span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">really_drop_evens [1;2;3;4;5;6]<\/span> is <span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">[1;3;5]<\/span>)<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">The auxilliary function to split a token list at commas into a list of token lists:<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">let rec splitat_commas toks =<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 match cleavewhile (neq (Genlex.Kwd &#8220;,&#8221;)) toks with<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | [], _ -> []<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\" ;font-family:'courier new';\">\u00a0\u00a0| some, [] -> [some]<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 | _::_ as before, _::rest -> before::splitat_commas rest\u00a0<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">(<span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">cleavewhile<\/span> returns, in order, the elements at the beginning of a list until a predicate is false, paired with the rest of the elements, in order. \u00a0<span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">neq <\/span>is <span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">( <> )<\/span>)<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\">And here&#8217;s the wrapper function, which unifies the error handling and tests for page numbers not within range.<\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px\"><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">let parse_pagespec pdf spec =<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 let endpage = endpage_of_pdf pdf in<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 let numbers =<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 try mk_numbers endpage (lexwith spec) with<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 \u00a0_ -> error (&#8220;Bad page specification &#8221; ^ spec)<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 in<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 iter<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 \u00a0 (fun n -> if n <> endpage then<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 error (&#8220;Page &#8221; ^ string_of_int n ^ &#8221; does not exist.&#8221;))<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 \u00a0 numbers;<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\">\u00a0 \u00a0 \u00a0 numbers<\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:'courier new';\"><br \/><\/span><\/p>\n<p style=\"margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica\"><span class=\"Apple-style-span\"  style=\"font-family:arial;\">This is often easier to write and debug than using a separate tool intended for highly complex grammars.<\/span><\/p>\n<div><\/div>\n<p><\/span><\/div>\n","protected":false},"excerpt":{"rendered":"<p>(If you&#8217;re reading this via the OCaml planet, click the post title to see it with proper formatting &#8211; I shall fix this problem for the next post, I hope.) When calling our command line PDF Tools to, for instance, &hellip; <a href=\"https:\/\/coherentpdf.com\/blog\/?p=7\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[11,12,5],"class_list":["post-7","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-genlex","tag-lexing","tag-ocaml"],"_links":{"self":[{"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/7","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7"}],"version-history":[{"count":0,"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/7\/revisions"}],"wp:attachment":[{"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coherentpdf.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}