Brucey 2 лет назад
Родитель
Сommit
51892e6778
100 измененных файлов с 29747 добавлено и 0 удалено
  1. 29 0
      pikchr.mod/common.bmx
  2. 18 0
      pikchr.mod/examples/example_01.bmx
  3. 66 0
      pikchr.mod/pikchr.bmx
  4. 47 0
      pikchr.mod/pikchr/Makefile
  5. 124 0
      pikchr.mod/pikchr/Makefile.msc
  6. 12 0
      pikchr.mod/pikchr/README.md
  7. 11 0
      pikchr.mod/pikchr/doc/annotate.md
  8. 41 0
      pikchr.mod/pikchr/doc/arrowdir.md
  9. 79 0
      pikchr.mod/pikchr/doc/behind.md
  10. 67 0
      pikchr.mod/pikchr/doc/boxobj.md
  11. 92 0
      pikchr.mod/pikchr/doc/build.md
  12. 32 0
      pikchr.mod/pikchr/doc/chop.md
  13. 50 0
      pikchr.mod/pikchr/doc/circleobj.md
  14. 28 0
      pikchr.mod/pikchr/doc/colorexpr.md
  15. 19 0
      pikchr.mod/pikchr/doc/compassangle.md
  16. 43 0
      pikchr.mod/pikchr/doc/cylinderobj.md
  17. 486 0
      pikchr.mod/pikchr/doc/differences.md
  18. 65 0
      pikchr.mod/pikchr/doc/download.md
  19. 37 0
      pikchr.mod/pikchr/doc/ellipseobj.md
  20. 660 0
      pikchr.mod/pikchr/doc/examples.md
  21. 42 0
      pikchr.mod/pikchr/doc/fileobj.md
  22. 48 0
      pikchr.mod/pikchr/doc/fit.md
  23. 379 0
      pikchr.mod/pikchr/doc/grammar.md
  24. 114 0
      pikchr.mod/pikchr/doc/integrate.md
  25. 8 0
      pikchr.mod/pikchr/doc/invis.md
  26. 24 0
      pikchr.mod/pikchr/doc/linelen.md
  27. 31 0
      pikchr.mod/pikchr/doc/locattr.md
  28. 69 0
      pikchr.mod/pikchr/doc/macro.md
  29. 19 0
      pikchr.mod/pikchr/doc/newpropval.md
  30. 89 0
      pikchr.mod/pikchr/doc/numprop.md
  31. 65 0
      pikchr.mod/pikchr/doc/ovalobj.md
  32. 183 0
      pikchr.mod/pikchr/doc/pathattr.md
  33. 217 0
      pikchr.mod/pikchr/doc/place.md
  34. 77 0
      pikchr.mod/pikchr/doc/position.md
  35. 43 0
      pikchr.mod/pikchr/doc/purpose.md
  36. 86 0
      pikchr.mod/pikchr/doc/sqlitesyntax.md
  37. 180 0
      pikchr.mod/pikchr/doc/stmt.md
  38. 94 0
      pikchr.mod/pikchr/doc/stmtlist.md
  39. 296 0
      pikchr.mod/pikchr/doc/teardown01.md
  40. 154 0
      pikchr.mod/pikchr/doc/textattr.md
  41. 10 0
      pikchr.mod/pikchr/doc/thickthin.md
  42. 122 0
      pikchr.mod/pikchr/doc/usepikchr.md
  43. 1337 0
      pikchr.mod/pikchr/doc/userman.md
  44. 91 0
      pikchr.mod/pikchr/examples/_txt2js.bash
  45. 20 0
      pikchr.mod/pikchr/examples/headings01.pikchr
  46. 52 0
      pikchr.mod/pikchr/examples/objects.pikchr
  47. 64 0
      pikchr.mod/pikchr/examples/swimlane.pikchr
  48. 66 0
      pikchr.mod/pikchr/fiddle/GNUmakefile
  49. 110 0
      pikchr.mod/pikchr/fiddle/README.md
  50. 24 0
      pikchr.mod/pikchr/fiddle/emscripten.css
  51. 264 0
      pikchr.mod/pikchr/fiddle/fiddle.html
  52. 691 0
      pikchr.mod/pikchr/fiddle/fiddle.js
  53. 221 0
      pikchr.mod/pikchr/fiddle/pikchr-worker.js
  54. 32 0
      pikchr.mod/pikchr/fuzzcases/divzero.pikchr
  55. 1 0
      pikchr.mod/pikchr/fuzzcases/nan.pikchr
  56. 43 0
      pikchr.mod/pikchr/grammar/gram01.pikchr
  57. 89 0
      pikchr.mod/pikchr/grammar/gram02.pikchr
  58. 33 0
      pikchr.mod/pikchr/grammar/gram03.pikchr
  59. 50 0
      pikchr.mod/pikchr/grammar/gram04.pikchr
  60. 112 0
      pikchr.mod/pikchr/homepage.md
  61. 5893 0
      pikchr.mod/pikchr/lemon.c
  62. 1068 0
      pikchr.mod/pikchr/lempar.c
  63. 8127 0
      pikchr.mod/pikchr/pikchr.c
  64. 33 0
      pikchr.mod/pikchr/pikchr.h
  65. 33 0
      pikchr.mod/pikchr/pikchr.h.in
  66. 5525 0
      pikchr.mod/pikchr/pikchr.y
  67. 38 0
      pikchr.mod/pikchr/tests/autochop01.pikchr
  68. 38 0
      pikchr.mod/pikchr/tests/autochop02.pikchr
  69. 38 0
      pikchr.mod/pikchr/tests/autochop03.pikchr
  70. 38 0
      pikchr.mod/pikchr/tests/autochop04.pikchr
  71. 38 0
      pikchr.mod/pikchr/tests/autochop05.pikchr
  72. 38 0
      pikchr.mod/pikchr/tests/autochop06.pikchr
  73. 38 0
      pikchr.mod/pikchr/tests/autochop07.pikchr
  74. 11 0
      pikchr.mod/pikchr/tests/autochop08.pikchr
  75. 17 0
      pikchr.mod/pikchr/tests/autochop09.pikchr
  76. 23 0
      pikchr.mod/pikchr/tests/colortest1.pikchr
  77. 0 0
      pikchr.mod/pikchr/tests/empty.pikchr
  78. 400 0
      pikchr.mod/pikchr/tests/expr.pikchr
  79. 402 0
      pikchr.mod/pikchr/tests/expr.txt
  80. 21 0
      pikchr.mod/pikchr/tests/gridlines1.pikchr
  81. 37 0
      pikchr.mod/pikchr/tests/test01.pikchr
  82. 34 0
      pikchr.mod/pikchr/tests/test02.pikchr
  83. 18 0
      pikchr.mod/pikchr/tests/test03.pikchr
  84. 4 0
      pikchr.mod/pikchr/tests/test04.pikchr
  85. 15 0
      pikchr.mod/pikchr/tests/test05.pikchr
  86. 9 0
      pikchr.mod/pikchr/tests/test06.pikchr
  87. 15 0
      pikchr.mod/pikchr/tests/test07.pikchr
  88. 28 0
      pikchr.mod/pikchr/tests/test08.pikchr
  89. 3 0
      pikchr.mod/pikchr/tests/test09.pikchr
  90. 58 0
      pikchr.mod/pikchr/tests/test10.pikchr
  91. 7 0
      pikchr.mod/pikchr/tests/test12.pikchr
  92. 6 0
      pikchr.mod/pikchr/tests/test13.pikchr
  93. 20 0
      pikchr.mod/pikchr/tests/test14.pikchr
  94. 9 0
      pikchr.mod/pikchr/tests/test15.pikchr
  95. 1 0
      pikchr.mod/pikchr/tests/test16.pikchr
  96. 1 0
      pikchr.mod/pikchr/tests/test17.pikchr
  97. 1 0
      pikchr.mod/pikchr/tests/test18.pikchr
  98. 1 0
      pikchr.mod/pikchr/tests/test19.pikchr
  99. 1 0
      pikchr.mod/pikchr/tests/test20.pikchr
  100. 4 0
      pikchr.mod/pikchr/tests/test21.pikchr

+ 29 - 0
pikchr.mod/common.bmx

@@ -0,0 +1,29 @@
+' Copyright (c) 2023 Bruce A Henderson
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgment in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+Import "pikchr/*.h"
+Import "pikchr/pikchr.c"
+
+
+Enum EPikChrFlags:UInt Flags
+	NONE
+	PLAINTEXT_ERRORS = $0001
+	DARK_MODE = $0002
+End Enum

+ 18 - 0
pikchr.mod/examples/example_01.bmx

@@ -0,0 +1,18 @@
+SuperStrict
+
+Framework brl.standardio
+Import Text.Pikchr
+
+
+Local source:String = """
+arrow right 200% "Markdown" "Source"
+box rad 10px "Markdown" "Formatter" "(markdown.c)" fit
+arrow right 200% "HTML+SVG" "Output"
+arrow <-> down 70% from last box.s
+box same "Pikchr" "Formatter" "(pikchr.c)" fit
+"""
+
+Local width:Int, height:Int
+Local out:String = Pikchr(source, Null, EPikChrFlags.NONE, width, height)
+
+SaveText(out, "example_01.svg")

+ 66 - 0
pikchr.mod/pikchr.bmx

@@ -0,0 +1,66 @@
+' Copyright (c) 2023 Bruce A Henderson
+' 
+' This software is provided 'as-is', without any express or implied
+' warranty. In no event will the authors be held liable for any damages
+' arising from the use of this software.
+' 
+' Permission is granted to anyone to use this software for any purpose,
+' including commercial applications, and to alter it and redistribute it
+' freely, subject to the following restrictions:
+' 
+' 1. The origin of this software must not be misrepresented; you must not
+'    claim that you wrote the original software. If you use this software
+'    in a product, an acknowledgment in the product documentation would be
+'    appreciated but is not required.
+' 2. Altered source versions must be plainly marked as such, and must not be
+'    misrepresented as being the original software.
+' 3. This notice may not be removed or altered from any source distribution.
+' 
+SuperStrict
+
+Rem
+bbdoc: A PIC-like markup language for diagrams in technical documentation.
+End Rem
+Module Text.Pikchr
+
+ModuleInfo "Version: 1.00"
+ModuleInfo "Author: Bruce A Henderson"
+ModuleInfo "License: zlib/libpng"
+ModuleInfo "pikchr - Copyright (C) 2020-09-01 by D. Richard Hipp <[email protected]>"
+ModuleInfo "Copyright: 2023 Bruce A Henderson"
+
+ModuleInfo "History: 1.00"
+ModuleInfo "History: Initial Release"
+
+Import "common.bmx"
+
+Rem
+bbdoc: Converts a #String of Pikchr markup, returning an SVG #String.
+End Rem
+Function PikChr:String(txt:String, class:String, flags:EPikChrFlags, width:Int Var, height:Int Var)
+	Local t:Byte Ptr = txt.ToUTF8String()
+	Local c:Byte Ptr
+	If class Then
+		c = class.ToUTF8String()
+	End If
+
+	Local w:Int, h:Int
+	Local s:Byte Ptr = pikchr_(t, c, flags, VarPtr w, VarPtr h)
+
+	width = w
+	height = h
+
+	MemFree(c)
+	MemFree(t)
+
+	Local res:String = String.FromUTF8String(s)
+	free_(s)
+	Return res
+End Function
+
+Private
+
+Extern
+	Function pikchr_:Byte Ptr(zText:Byte Ptr, zClass:Byte Ptr, mFlags:EPikChrFlags, pnWidth:Int Ptr, pnHeight:Int Ptr)="pikchr"
+	Function free_( buf:Byte Ptr )="void free( void * ) !"
+End Extern

+ 47 - 0
pikchr.mod/pikchr/Makefile

@@ -0,0 +1,47 @@
+CC = gcc
+CFLAGS = -O0 -g -Wall -Wextra
+LIBS = -lm
+
+TCL_PACKAGE_NAME    = pikchr
+TCL_PACKAGE_VERSION = 1.0
+TCL_PACKAGE_FLAGS = \
+  -DPACKAGE_NAME="\"$(TCL_PACKAGE_NAME)\"" \
+  -DPACKAGE_VERSION="\"$(TCL_PACKAGE_VERSION)\""
+
+all:	pikchr
+
+pikchr:	pikchr.c
+	$(CC) $(CFLAGS) -DPIKCHR_SHELL pikchr.c -o pikchr $(LIBS)
+
+pikchrfuzz:	pikchr.c
+	clang -g -O3 -fsanitize=fuzzer,undefined,address -o pikchrfuzz \
+	  -DPIKCHR_FUZZ pikchr.c $(LIBS)
+
+pikchr.c:	pikchr.y pikchr.h.in lempar.c lemon
+	./lemon pikchr.y
+	cat pikchr.h.in >pikchr.h
+
+piktcl: pikchr.c
+	mkdir -p piktcl
+	$(CC) -shared -fPIC -o piktcl/libpikchr.so \
+	      $(CFLAGS) \
+	      -DPIKCHR_TCL $(TCL_PACKAGE_FLAGS) \
+	      -I /usr/include/tcl8.6 \
+	      pikchr.c \
+	      -lm \
+	      -L /usr/lib/x86_64-linux-gnu/ \
+	      -ltcl8.6
+	echo >> piktcl/pkgIndex.tcl \
+	      "package ifneeded $(TCL_PACKAGE_NAME) $(TCL_PACKAGE_VERSION) [list load [file join" '$$dir' "libpikchr.so]]"
+
+lemon:	lemon.c
+	$(CC) $(CFLAGS) lemon.c -o lemon
+
+test:	pikchr
+	./pikchr tests/*.pikchr >out.html || true
+	open out.html || true
+	./pikchr --dark-mode */*.pikchr >darkmode.html || true
+	open darkmode.html
+
+clean:	
+	rm -f pikchr pikchr.c pikchr.h pikchr.out lemon out.html

+ 124 - 0
pikchr.mod/pikchr/Makefile.msc

@@ -0,0 +1,124 @@
+CC = cl
+CFLAGS = /Os
+LIBS = 
+TESTS = \
+  examples\headings01.pikchr \
+  examples\objects.pikchr \
+  examples\swimlane.pikchr \
+  grammar\gram01.pikchr \
+  grammar\gram02.pikchr \
+  grammar\gram03.pikchr \
+  grammar\gram04.pikchr \
+  tests\autochop01.pikchr \
+  tests\autochop02.pikchr \
+  tests\autochop03.pikchr \
+  tests\autochop04.pikchr \
+  tests\autochop05.pikchr \
+  tests\autochop06.pikchr \
+  tests\autochop07.pikchr \
+  tests\colortest1.pikchr \
+  tests\empty.pikchr \
+  tests\expr.pikchr \
+  tests\gridlines1.pikchr \
+  tests\test01.pikchr \
+  tests\test02.pikchr \
+  tests\test03.pikchr \
+  tests\test04.pikchr \
+  tests\test05.pikchr \
+  tests\test06.pikchr \
+  tests\test07.pikchr \
+  tests\test08.pikchr \
+  tests\test09.pikchr \
+  tests\test10.pikchr \
+  tests\test12.pikchr \
+  tests\test13.pikchr \
+  tests\test14.pikchr \
+  tests\test15.pikchr \
+  tests\test16.pikchr \
+  tests\test17.pikchr \
+  tests\test18.pikchr \
+  tests\test19.pikchr \
+  tests\test20.pikchr \
+  tests\test21.pikchr \
+  tests\test22.pikchr \
+  tests\test23.pikchr \
+  tests\test23b.pikchr \
+  tests\test23c.pikchr \
+  tests\test24.pikchr \
+  tests\test25.pikchr \
+  tests\test26.pikchr \
+  tests\test27.pikchr \
+  tests\test28.pikchr \
+  tests\test29.pikchr \
+  tests\test30.pikchr \
+  tests\test31.pikchr \
+  tests\test32.pikchr \
+  tests\test33.pikchr \
+  tests\test34.pikchr \
+  tests\test35.pikchr \
+  tests\test36.pikchr \
+  tests\test37.pikchr \
+  tests\test38.pikchr \
+  tests\test38b.pikchr \
+  tests\test40.pikchr \
+  tests\test41.pikchr \
+  tests\test42.pikchr \
+  tests\test43.pikchr \
+  tests\test44.pikchr \
+  tests\test45.pikchr \
+  tests\test46.pikchr \
+  tests\test47.pikchr \
+  tests\test47b.pikchr \
+  tests\test48.pikchr \
+  tests\test49.pikchr \
+  tests\test50.pikchr \
+  tests\test51.pikchr \
+  tests\test52.pikchr \
+  tests\test53.pikchr \
+  tests\test54.pikchr \
+  tests\test55.pikchr \
+  tests\test56.pikchr \
+  tests\test57a.pikchr \
+  tests\test57b.pikchr \
+  tests\test57c.pikchr \
+  tests\test58.pikchr \
+  tests\test59.pikchr \
+  tests\test60.pikchr \
+  tests\test61.pikchr \
+  tests\test62.pikchr \
+  tests\test63.pikchr \
+  tests\test64.pikchr \
+  tests\test65.pikchr \
+  tests\test66.pikchr \
+  tests\test67.pikchr \
+  tests\test68.pikchr \
+  tests\test69.pikchr \
+  tests\test70.pikchr \
+  tests\test71.pikchr \
+  tests\test72.pikchr \
+  tests\test73.pikchr \
+  tests\test74.pikchr \
+  tests\test75.pikchr
+  
+
+all:	pikchr.exe
+
+pikchr.exe:	pikchr.c
+	$(CC) $(CFLAGS) -DPIKCHR_SHELL pikchr.c $(LIBS)
+
+
+pikchr.c:	pikchr.y pikchr.h.in lempar.c lemon.exe
+	.\lemon.exe pikchr.y
+	cat pikchr.h.in >pikchr.h
+
+lemon.exe:	lemon.c
+	$(CC) $(CFLAGS) lemon.c
+
+test:	pikchr.exe
+	.\pikchr.exe $(TESTS) >out.html || true
+	start out.html
+	.\pikchr.exe --dark-mode $(TESTS) >darkmode.html || true
+	start darkmode.html
+
+clean:	
+	del pikchr.exe pikchr.c pikchr.h pikchr.out lemon.exe out.html

+ 12 - 0
pikchr.mod/pikchr/README.md

@@ -0,0 +1,12 @@
+Pikchr (pronounced like "picture") is a [PIC][1]-like markup
+language for diagrams in technical documentation.  Pikchr is
+designed to be embedded in [fenced code blocks][2] of
+Markdown (or in similar mechanisms in other markup languages)
+to provide a convenient means of showing diagrams.
+
+[1]: https://en.wikipedia.org/wiki/Pic_language
+[2]: https://spec.commonmark.org/0.29/#fenced-code-blocks
+
+Visit the [home page][3] for more information
+
+[3]: https://pikchr.org/home/doc/trunk/homepage.md

+ 11 - 0
pikchr.mod/pikchr/doc/annotate.md

@@ -0,0 +1,11 @@
+# Text annotations
+
+Objects can have up to 5 separate text annotations.  Each annotation
+can have multiple *[text-attributes](./textattr.md)*.
+
+The annotations normally appear stacked above and below the center of the
+object.  However, this can be controlled through the use of
+various *[text-attributes](./textattr.md)*.
+
+Text annotations are drawn even if the object is marked 
+"[`invis`](./invis.md)"

+ 41 - 0
pikchr.mod/pikchr/doc/arrowdir.md

@@ -0,0 +1,41 @@
+# Arrowheads
+
+Line objects ("line", "arrow", "spline", and "arc") can have one
+of the following attributes to specify which ends of the line contain
+arrowheads:
+
+  *  **-&gt;**
+  *  **&lt;-**
+  *  **&lt;-&gt;**
+
+The first form (**-&gt;**) means that there is an arrowhead at the end.
+This is the default for "arrow".  The second form (**&lt;-**) means that
+there is an arrowhead at the beginning only.  The third form means that
+there are arrowheads at both ends.
+
+Note that "`arrow`" and "`line ->`" look identical to one another.
+
+If there are multiple occurrences of these attributes on a single object,
+then the last one is the one that matters.
+
+## Enhancement 2021-06-11
+
+To make it easier to embed pikchr scripts inside of larger HTML documents,
+the arrow direction tokens now have alternative spellings.
+
+| Legacy ASCII | HTML Entity           | Unicode Character |
+------------------------------------------------------------
+| &lt;-        | &amp;larr;            | &larr;            |
+| &lt;-        | &amp;leftarrow;       | &leftarrow;       |
+| -&gt;        | &amp;rarr;            | &rarr;            |
+| -&gt;        | &amp;rightarrow;      | &rightarrow;      |
+| &lt;-&gt;    | &amp;leftrightarrow;  | &leftrightarrow;  |
+
+All the tokens in any row of the table above mean the same thing
+to Pikchr and can be freely interchanged.  So, in other words,
+each of the following Pikchr statements means the same thing:
+
+  *  `line ->`
+  *  `line &rarr;`
+  *  `line &rightarrow;`
+  *  `line →`

+ 79 - 0
pikchr.mod/pikchr/doc/behind.md

@@ -0,0 +1,79 @@
+# The "behind" attribute
+
+The "**behind** *object*" attribute causes the object currently under
+construction to be drawn before the referenced *object*.  
+
+Pikchr normally draws objects in the order that they appear in the
+input script.  However, the "`behind`" attribute can be used to alter
+the drawing order so that boxes used to implement background colors
+or borders can be drawn before the objects they enclose, even though
+the background-boxes are specified after the objects they enclose.
+
+Consider this example:
+
+~~~ pikchr toggle
+    lineht *= 0.4
+    $margin = lineht*2.5
+    scale = 0.75
+    fontscale = 1.1
+    charht *= 1.15
+    down
+IN: box "Interface" wid 150% ht 75% fill white
+    arrow
+CP: box same "SQL Command" "Processor"
+    arrow
+VM: box same "Virtual Machine"
+    arrow down 1.25*$margin
+BT: box same "B-Tree"
+    arrow
+    box same "Pager"
+    arrow
+OS: box same "OS Interface"
+    box same with .w at 1.25*$margin east of 1st box.e "Tokenizer"
+    arrow
+    box same "Parser"
+    arrow
+CG: box same ht 200% "Code" "Generator"
+UT: box same as 1st box at (Tokenizer,Pager) "Utilities"
+    move lineht
+TC: box same "Test Code"
+    arrow from CP to 1/4<Tokenizer.sw,Tokenizer.nw> chop
+    arrow from 1/3<CG.nw,CG.sw> to CP chop
+
+    box ht (IN.n.y-VM.s.y)+$margin wid IN.wid+$margin \
+       at CP fill 0xd8ecd0 behind IN
+#                          ^^^^^^^^^
+####################################
+    line invis from 0.25*$margin east of last.sw up last.ht \
+        "Core" italic aligned
+
+    box ht (BT.n.y-OS.s.y)+$margin wid IN.wid+$margin \
+       at Pager fill 0xd0ece8 behind IN
+#                             ^^^^^^^^^
+#######################################
+    line invis from 0.25*$margin east of last.sw up last.ht \
+       "Backend" italic aligned
+
+    box ht (Tokenizer.n.y-CG.s.y)+$margin wid IN.wid+$margin \
+       at 1/2<Tokenizer.n,CG.s> fill 0xe8d8d0 behind IN
+#                                             ^^^^^^^^^
+#######################################################
+    line invis from 0.25*$margin west of last.se up last.ht \
+       "SQL Compiler" italic aligned
+
+    box ht (UT.n.y-TC.s.y)+$margin wid IN.wid+$margin \
+       at 1/2<UT,TC> fill 0xe0ecc8 behind IN
+#                                  ^^^^^^^^^
+############################################
+    line invis from 0.25*$margin west of last.se up last.ht \
+      "Accessories" italic aligned
+~~~
+
+In the diagram above, the white
+component boxes are drawn first.  Then the larger boxes that
+implement the various background colors are drawn relative to
+the component boxes.  The "`behind`" attribute must be used to
+cause the background boxes to appear to be behind the component
+boxes.  Click on the diagram to see the source text.  Comments
+have been inserted into the source text to help identify the
+"`behind`" attributes amid all the others.

+ 67 - 0
pikchr.mod/pikchr/doc/boxobj.md

@@ -0,0 +1,67 @@
+# Box objects
+
+A box is a rectangle with a specified width and height.  The default
+width and height are the values of the "`boxwid`" and "`boxht`" variables.
+
+~~~~ pikchr indent
+A: box thick
+line thin color gray left 70% from 2mm left of A.nw
+line same from 2mm left of A.sw
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below A.sw
+X2: line thin color gray down 50% from 2mm below A.se
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+~~~~
+
+If a "`radius`" is specified, then the corners of the box are rounded using
+arcs of the given radius.  The default radius for each new box is the value
+of the "`boxrad`" variable which is initially 0.0.
+
+~~~~ pikchr indent
+A: box thick rad 0.3*boxht
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.s)
+X4: line thin color gray right 70% from A.rad above start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X3) down 30% <-
+line thin color gray from (previous text,X4) up 30% <-
+~~~~
+
+## Boundary points:
+
+~~~~ pikchr indent
+A: box thin
+dot ".c" above at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+
+A: box thin at 2.0*boxwid right of previous box rad 15px
+dot ".c" above at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+~~~~

+ 92 - 0
pikchr.mod/pikchr/doc/build.md

@@ -0,0 +1,92 @@
+# How To Compile Pikchr
+
+## Overview
+
+  *  Primary source file: "`pikchr.y`"
+  *  Process "`pikchr.y`" using Lemon (sources provided in tree)
+     to create "`pikchr.c`"
+  *  Compile "`pikchr.c`" into an object file, or add the
+     "`-DPIKCHR_SHELL`" command-line option to generate a stand-alone
+     executable.
+  *  A pre-processed "[`pikchr.c`][src]" source file is stored in the
+     source tree for the convenience of integrators who do not want to
+     figure out how to build and run Lemon.
+
+[src]: /file/pikchr.c
+
+~~~ pikchr
+            filewid *= 1.2
+  Src:      file "pikchr.y"; move
+  LemonSrc: file "lemon.c"; move
+  Lempar:   file "lempar.c"; move
+            arrow down from LemonSrc.s
+  CC1:      oval "C-Compiler" ht 50%
+            arrow " generates" ljust above
+  Lemon:    oval "lemon" ht 50%
+            arrow from Src chop down until even with CC1 \
+              then to Lemon.nw rad 10px
+            "Pikchr source " rjust "code input " rjust \
+              at 2nd vertex of previous
+            arrow from Lempar chop down until even with CC1 \
+              then to Lemon.ne rad 10px
+            " parser template" ljust " resource file" ljust \
+              at 2nd vertex of previous
+  PikSrc:   file "pikchr.c" with .n at lineht below Lemon.s
+            arrow from Lemon to PikSrc chop
+            arrow down from PikSrc.s
+  CC2:      oval "C-Compiler" ht 50%
+            arrow
+  Out:      file "pikchr.o" "or" "pikchr.exe" wid 110%
+            spline <- from 1mm west of Src.w go 60% heading 250 \
+               then go 40% heading 45 then go 60% heading 250 \
+               thin color gray
+            box invis "Canonical" ljust small "Source code" ljust small fit \
+               with .e at end of last spline width 90%
+            spline <- from 1mm west of PikSrc.w go 60% heading 250 \
+               then go 40% heading 45 then go 60% heading 250 \
+               thin color gray
+            box invis "Preprocessed" ljust small \
+              "C-code" ljust small "also in-tree" ljust small fit \
+               with .e at end of last spline width 90%
+~~~
+
+## Details:
+
+The source code for Pikchr is in the file named "`pikchr.y`".  As
+the ".y" suffix implies, this file is a grammar specification intended
+as input to the yacc-like LALR(1) parser generator program
+"[Lemon][lemon]".  Even though "pikchr.y" is technically a Lemon
+grammar file, it consists of mostly C-code and only 6% grammar.
+
+Running the command:
+
+~~~~
+   lemon pikchr.y
+~~~~
+
+Generates "`pikchr.c`" as an output file.  (Lemon generates a couple
+of other output files that can be ignored for this project.)  The
+"pikchr.c" file is pure C code ready to be compiled into the final
+application.  It can be compiled by itself with the
+"-DPIKCHR_SHELL" command-line option to generate a standalone program
+that reads Pikchr scripts and emits HTML with embedded SVG.  Or
+it can be integrated into a larger application which invokes the
+"`pikchr()`" C-API to do conversions from Pikchr to SVG.
+
+As the Lemon tool is not widely deployed, the source code for 
+Lemon is included in the Pikchr source tree for convenience.
+Compile the "`lemon.c`" source program into an executable using
+any ordinary C-compiler.
+
+When the lemon executable runs, it looks for the "`lempar.c`" template
+in the working directory.  The "`lempar.c`" template is also included
+in the Pikchr source repository for convenience.
+
+[lemon]: https://www.sqlite.org/lemon.html
+
+## Preprocessed Sources Available For Download.
+
+As a convenience to integrators, the pre-processed "`pikchr.c`" C-code
+file is normally checked into the Pikchr source tree.  Hence, to build
+Pikchr into another system, just grab a copy of that one file and add
+it to the project.

+ 32 - 0
pikchr.mod/pikchr/doc/chop.md

@@ -0,0 +1,32 @@
+# The "chop" Attribute
+
+Line objects may have a single "`chop`" attribute.  When the chop
+attribute is present, and if the line starts or ends at the center
+of a block object, then that start or end is automatically moved to
+the edge of the object.  For example:
+
+~~~~ pikchr toggle
+file "A"
+cylinder "B" at 5cm heading 125 from A
+arrow <-> from A to B "from A to B" aligned above color red
+arrow <-> from A to B chop "from A to B chop" aligned below color blue
+~~~~
+
+In the example, both of the arrows use "`from A to B`"  The difference
+is that the blue line adds the "`chop`" keyword whereas the red line
+does not.
+
+The chop feature only works if one or both ends of the line land on
+the center of a block object.  If neither end of a line is on the
+center of a block object, then the "`chop`" attribute is a no-op
+
+## Different From Legacy PIC
+
+The chop attribute in Pikchr differs from the chop attribute in legacy PIC.
+In PIC, the "`chop`" keyword can be followed by a distance and can appear
+twice.  The chop keyword causes the line to be shortened by the amount
+specified, or by `circlerad` if no distance is given.  The legacy "chop"
+works ok if you are drawing lines between circles, but it mostly pointless
+for lines between all other kinds of objects.  The enhanced "chop" in
+Pikchr is intended to make the feature helpful on a wider variety of
+diagrams.

+ 50 - 0
pikchr.mod/pikchr/doc/circleobj.md

@@ -0,0 +1,50 @@
+# Circle objects
+
+A is defined by one of:
+
+   *  `radius`
+   *  `diameter`
+   *  `width`
+   *  `height`
+
+Only one of these values can set for any particular circle.  The others 
+are determined automatically by the first.
+The default radius is value of the "`circlerad`" variable.
+
+
+~~~~ pikchr indent
+A: circle thick rad 120%
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.s)
+X4: line thin color gray right 70% from A.rad above start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X3) down 30% <-
+line thin color gray from (previous text,X4) up 30% <-
+line thin color gray <-> from A.sw to A.ne
+line thin color gray from A.ne go 0.5*A.rad ne then 0.25*A.rad east
+text " diameter" ljust at end of previous line
+~~~~
+
+## Boundary points:
+
+~~~~ pikchr indent
+A: circle thin
+dot ".c" above at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+~~~~

+ 28 - 0
pikchr.mod/pikchr/doc/colorexpr.md

@@ -0,0 +1,28 @@
+# color-expr
+
+Pikchr tracks colors as 24-bit RGB values.  Black is 0.  White is
+16777215.  Other color values are in between these two extremes.
+
+Pikchr understands C-style hexadecimal numeric literals.  So it is
+often convenient to express colors using 6-digit hex constants
+like 0x000000 or 0xffffff (for black and white respectively) rather
+than as base-10 literals.
+
+Pikchr knows the names of the 140 standard HTML color names.  If you
+use one of those color names in an expression, Pikchr will substitute
+the corresponding RGB value.  For example, if you write:
+
+~~~~~
+    circle "Hi" fill Bisque
+~~~~~
+
+That is the equivalent of writing:
+
+~~~~~
+    circle "Hi" fill 0xffe4c4
+~~~~~
+
+Because 0xffe4c4 is the 24-bit RGB value for "Bisque".
+
+To put it another way, Pikchr treats the keyword "Bisque" as an
+alternative spelling for the numeric literal 0xffe4c4.

+ 19 - 0
pikchr.mod/pikchr/doc/compassangle.md

@@ -0,0 +1,19 @@
+# compass-angle
+
+Because of the extensive historical use of compass heading names
+like "north" and "se" (short for "south-east") in PIC and Pikchr,
+it makes sense that angles should be specified according to compass
+degrees.   North is 0&deg; and the angle increases clockwise so that
+east is 90&deg;, south is 180&deg;, west is 270&deg; and 360&deg; is
+back to north again.
+
+~~~ pikchr
+C: dot
+arrow up from C; text " 0&deg;"
+arrow right from C; text "  90&deg;" rjust
+arrow down from C; text "180&deg;" below
+arrow left from C; text "270&deg;  " ljust
+~~~
+
+Even though heading angles are specified in degrees, the arguments
+to the built-in "sin()" and "cos()" functions are in radians.

+ 43 - 0
pikchr.mod/pikchr/doc/cylinderobj.md

@@ -0,0 +1,43 @@
+# Cylinder objects
+
+A cylinder is a stylized projection of a cylinder into the 2-D space of
+the diagram.  Cylinders are commonly used to represent bulk data storage in
+software architecture diagrams, as legacy disk packs were cylindrical in shape.
+
+The shape of a cylinder is defined by the width, height, and radius.
+The radius is the minor axis of the ellipse that forms the top of the
+cylinder, and the semiellipse the forms the bottom.
+
+~~~~ pikchr indent
+A: cylinder thick rad 150%
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.ne)
+X4: line thin color gray right 70% from A.rad below start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X4) down 30% <-
+line thin color gray from (previous text,X3) up 30% <-
+~~~~
+
+## Boundary points:
+
+~~~~ pikchr indent
+A: cylinder thin rad 80%
+dot ".c" below at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+~~~~

+ 486 - 0
pikchr.mod/pikchr/doc/differences.md

@@ -0,0 +1,486 @@
+# Differences Between Pikchr And Legacy-PIC
+
+Pikchr is mostly compatible with legacy PIC in the sense that it will
+run most of the example scripts contained in the
+[original technical report on PIC by BWK][bwk] with little to no change.
+Nevertheless, some features of legacy PIC have been omitted, and new
+features have been added.  This article attempts to highlight the
+important differences.
+
+[bwk]: /uv/pic.pdf
+
+Pikchr is implemented from scratch, without reference to the original
+PIC code, and without even access to a working version of legacy PIC
+with which to perform experiments.  The syntax implemented by Pikchr
+is based solely on the descriptions in the [BWK tech report][bwk] which was
+intended as a user manual, not a precise description of the language.
+Consequently, some details of Pikchr may differ from PIC without our
+even being aware of it.  This document tries to list the differences
+that we know of.  But there are likely omissions.
+
+## Designed for the Web
+
+Pikchr is designed to be embedded in Markdown, generating SVG
+output embedded into the resulting HTML.  It is
+intended for use in software development and software project
+management systems for the 2020s and beyond. Examples of this focus are
+Pikchr’s understanding of [CSS color names][color] and its allowance for
+Unicode arrows (→, ←, ↔) as [directions in `arrow` commands][adir] to
+avoid problems with angle bracket interpretation in SVG+HTML.
+
+[adir]:  ./arrowdir.md
+[color]: ./colorexpr.md
+
+PIC was designed to be embedded in [troff][troff] - an historically
+significant but now obsolete markup language developed at Bell Labs
+in the late 1970s and early 1980s.
+PIC could include troff markup in the middle of
+a drawing, a capability omitted from Pikchr (obviously).
+
+[troff]: https://en.wikipedia.org/wiki/Troff
+
+## New Object Types
+
+Pikchr supports several new object types that were unavailable
+in PIC.
+
+~~~ pikchr indent
+oval "oval"
+move
+cylinder "cylinder"
+move
+file "file"
+move
+dot "  dot" ljust
+~~~
+
+Additional object types may be added in subsequent versions of Pikchr.
+
+## Units Other Than Inches
+
+PIC operated purely in inches.  Pikchr allows you to attach a
+units designator on numeric literals so that distances can be easily
+expressed in other units.  For example, you can write "`2.3cm`" to
+mean 2.3 centimeters.  This is easier and more intuitive than writing
+something like
+"`2.3/2.54`".  Pikchr still does all of its calculations in inches,
+internally.  The "cm" suffix is actually part of the numeric literal
+so that "`2.3cm`" is really just an alternative spelling for "`0.905`".
+
+Units supported by Pikchr include:
+
+  *  `cm` &rarr; centimeters
+  *  `in` &rarr; inches (the default)
+  *  `mm` &rarr; millimeters
+  *  `pc` &rarr; picas
+  *  `pt` &rarr; points
+  *  `px` &rarr; pixels
+
+Because the units are part of the numeric literal,
+the unit designator cannot be separated from the number by whitespace.
+Units only apply to numeric literals, not to expressions.
+
+
+## New Uses For "`radius`":
+
+A positive "`radius`" attribute on "`box`" items causes the box
+to be displayed with rounded corners:
+
+~~~ pikchr indent
+box rad 15px "box" "radius 15px"
+~~~
+
+Similarly a "`radius`" value on a "`line`" or "`arrow`" with
+multiple segments rounds the corners:
+
+~~~ pikchr indent
+arrow rad 10px go heading 30 then go 200% heading 175 \
+  then go 150% west "arrow" below "radius 10px" below
+~~~
+
+## The "`color`" and "`fill`" attributes
+
+Any object can have a "`color`" attribute to set its foreground
+color and a "`fill`" attribute to set its background color.  The
+default "`color`" is black and the default "`fill`" is "None".
+
+~~~ pikchr indent
+boxrad = 12px
+box color blue "color blue"
+move
+box fill lightgray "fill lightgray"
+move
+box color white fill blue "color white" "fill blue"
+~~~
+
+## The "`thickness`" attribute
+
+The new "`thickness`" attribute specifies the stroke-width.  You can
+also use attributes "`thick`" and "`thin`" to increase or decrease the
+stroke-width in increments.
+
+~~~ pikchr indent
+boxrad = 12px
+box thin "thin"
+move
+box "(default)" italic
+move
+box thick "thick"
+move
+box thick thick "thick" "thick"
+~~~
+
+## The "`behind`" attribute
+
+The new ["`behind`" attribute](./behind.md) can be used to control
+object stacking order.
+
+## Enhanced ability to control text alignment and display
+
+There are new modifiers for text labels:
+
+~~~ pikchr indent
+box "bold" bold "italic" italic "big" big "small" small fit
+line from 1cm right of previous.se to 3cm right of previous.ne \
+   "aligned" above aligned
+~~~
+
+## Adjust the size of objects to fit their text annotations
+
+The ["`fit`" attribute](./fit.md) adjusts the width and height of
+box-like objects to snugly surround their text labels.
+
+Also, if the width or height of an object is zero after all attributes
+have been parsed, then the zero dimensions are increased to enclose
+the text annotations.
+
+## Change numeric property values by a percentage
+
+You can change the value of a numeric attribute by a percentage,
+rather than having to specify a particular value:
+
+~~~ pikchr indent
+box "default" italic "box" italic
+move
+box "width 150%" width 150%
+move
+box "wid 75%" wid 75%
+~~~
+
+## The "`chop`" attribute works differently
+
+The "`chop`" attribute is completely redesigned.  It takes no
+argument and can only appear once.  If "`chop`" is specified on
+a line (or arrow or spline) then end-points of the line that
+would have landed on the center of a box-like object (box,
+circle, cylinder, ellipse, file, or oval) are shortened to
+land exactly on the border of that object.  
+
+~~~ pikchr indent
+file "A"
+cylinder "B" at 5cm heading 125 from A
+arrow <-> from A to B chop "from A to B chop" aligned above
+~~~
+
+## The "`same as` *object*" construct
+
+An ordinary "`same`" attribute works as in PIC - it copies the
+configuration of the previous object of the same class.  Pikchr
+is extended with the "`same as` *object*" clause, that copies the
+configuration from any other prior object, including objects of
+different types.
+
+~~~ pikchr indent
+box thick thick fill lightgray "box" "thick" "fill lightgray"
+move
+file same as last box "file" "same as" "last box" rad filerad
+~~~
+
+## New ways to describe line paths
+
+  *  **go** *distance* **heading** *compass-angle*
+  *  **go** *distance* *compass-point*
+  *  **go** *direction* **until even with** *place*
+  *  **close**
+
+## New syntax to describe positions
+
+  *  *distance* **above**|**below** *position*
+  *  *distance* **left**|**right** **of** *position*
+  *  *distance* **heading** *compass-angle* **from** *position*
+  *  *nth* **vertex of** *line-object*
+
+
+## New ways to identify prior objects
+
+Pikchr allows the keywords "`last`" or "`previous`" to refer to
+the immediately previous object without having to specify the
+type of that object.
+
+Objects that contain text that looks like a label (starts with
+an upper-case letter and contains only letters, digits, and underscores)
+can be used as a label for that object.  Thus if you say:
+
+~~~
+  N1: circle "Node1"
+~~~
+
+Subsequent code can refer to that circle as either "`N1`" or as "`Node1`".
+
+## Support for C and C++ style comments
+
+Pikchr continues to support Bourne shell style “`#`” comments:
+a `#` character and all following
+characters until end-of-line.
+
+As an extension to PIC, Pikchr also recognizes
+C and C++ style comments:  “`//`” to end of line and block comments
+beginning with “`/*`”, extending through “`*/`”, irrespective of
+any intervening newlines.
+
+*Example:*
+
+        box "Hello,"            # say “hi”
+        box "world!"            // complete the thought
+        box "Hello," "world!!"  /* You may also break the
+                                   lines, like this. */
+
+## Variable names can start with "`$`" or "`@`" characters
+
+There are many built-in variable names and keywords in the PIC and
+Pikchr languages, all of which currently begin with lowercase letters.  To
+reduce the chance of a collision between an application-defined
+variable and a built-in variable name or keyword, Pikchr allows
+application-defined variable names to begin with "`$`" or "`@`".
+Pikchr does not now — nor will it ever — pre-define variables that
+begin with "`$`" or "`@`", other than the use of positional macro
+parameters `$1`, `$2`, etc.
+
+We recommend that you begin your own variable names with either
+"`$`" or "`@`" to ensure that they will never collide with variables
+that might be added to future version of Pikchr.
+
+## New assignment operators for variables
+
+Both Pikchr and PIC allow statements that assign values to
+built-in or user-defined variables, like this:
+
+>  *variable* **=** *expr*
+
+Pikchr adds several new assignment operators:
+
+  *  +=
+  *  -=
+  *  *=
+  *  /=
+
+The new operators are handy for scaling the value of an existing
+variable.  For example, to make the default radius of circles
+25% smaller:
+
+~~~~
+   circlerad *= 0.75
+~~~~
+
+## New keyword aliases
+
+Pikchr allows certain aliases for keywords that are not
+recognized by PIC:
+
+  *  "`invisible`" &lrarr; "`invis`"
+  *  "`first`" &lrarr; "`1st`"
+  *  "`previous`" &lrarr; "`last`"
+
+## The "`text`" Object
+
+With PIC, you create new text items by placing a string
+literal as the first token in a statement.  Pikchr works the
+same way, and further allows you to use the class name "`text`"
+as the first token of the statement.
+
+## New variables
+
+  *  bottommargin
+  *  charht
+  *  charwid
+  *  color
+  *  fill
+  *  fontscale
+  *  leftmargin
+  *  margin
+  *  rightmargin
+  *  thickness
+  *  topmargin
+
+If the "fontscale" variable exists and is not 1.0, then the point-size
+of fonts is increased or decreased by multiplying by the fontscale.
+This variable can be used to increase or decrease the fonts in a
+diagram relative to all the other elements.
+
+The "charht" and "charwid" variables should contain an estimate for
+the average height and width of a character.  This information is used
+when trying to estimate the size of text.  Because Pikchr has no access
+to the rendering engine, it cannot precisely determine the bounding box
+for text strings.  It tries to make a guess, and takes into account that
+some letters (like "w") are wider than others (like "i").  But Pikchr
+can only guess at the actual size of text strings.  Usually this guess
+is close enough.  Some scripts might need to compensate, however, by
+adding leading or trailing spaces to the text strings, or by adjusting
+the values for "charht" and "charwid".
+
+Setting the "`margin`" variable to a distance adds that amount of
+extra whitespace around all four sides of the diagram.  The other
+four margin variables ("rightmargin", "bottommargin", "leftmargin",
+and "topmargin") add extra whitespace to that one side.  The two
+methods are additive.  For example, to add one centimeter of extra
+space on all sides except the left, you could write:
+
+~~~
+     margin = 1cm;
+     leftmargin = -1cm;
+~~~
+
+The "thickness", "color", and "fill" variables determine the default
+value for the "thickness", "color", and "fill" attributes on all objects.
+Because the attribute name and the variable name are the same, the
+variable name can only be accessed from inside of parentheses, to avoid
+parsing ambiguities.  For example, to set the thickness of a box to
+be twice the default thickness:
+
+~~~~
+     box thickness 2*(thickness)
+     ###             ^^^^^^^^^^^---- must be inside (...)
+~~~~
+
+The extra parentheses around variables "thickness", "color", and "fill"
+are only required when the values are being read, not when the variable
+name appears on the left-hand size of an assignment.  You still do:
+
+~~~~
+     thickness *= 1.5
+~~~~
+
+## The "`arc`" object does not actually draw an arc.
+
+The behavior of the "`arc`" object is underspecified in the original
+[BWK paper on PIC][bwk].  Nobody is sure exactly what "arc" is supposed
+to do. Furthermore, arcs seem to be seldom used.
+Splines and lines with a radius at corners are better mechanisms
+for drawing curvy lines in a diagram.  For these reasons, and to
+keep the implementation simple, Pikchr does not actually draw an
+arc for the "`arc`" object.  Instead it draws a quadratic Bézier
+curve across *approximately* the same path that a true arc would have
+taken.
+
+The 30&deg; dimensional "arc" in the drawing below 
+(taken from [a tutorial analysis of a Pikchr script](./teardown01.md))
+is really a spline.  It is close enough to a true
+arc for the purposes of Pikchr.  Can you tell the difference?
+
+``` pikchr
+scale = 0.8
+linewid *= 0.5
+circle "C0" fit
+circlerad = previous.radius
+arrow
+circle "C1"
+arrow
+circle "C2"
+arrow
+circle "C4"
+arrow
+circle "C6"
+circle "C3" at dist(C2,C4) heading 30 from C2
+
+d1 = dist(C2,C3.ne)+2mm
+line thin color gray from d1 heading 30 from C2 \
+   to d1+1cm heading 30 from C2
+line thin color gray from d1 heading 0 from C2 \
+   to d1+1cm heading 0 from C2
+spline thin color gray <-> \
+   from d1+8mm heading 0 from C2 \
+   to d1+8mm heading 10 from C2 \
+   to d1+8mm heading 20 from C2 \
+   to d1+8mm heading 30 from C2 \
+   "30&deg;" aligned below small
+
+X1: line thin color gray from circlerad+1mm heading 300 from C3 \
+        to circlerad+6mm heading 300 from C3
+X2: line thin color gray from circlerad+1mm heading 300 from C2 \
+        to circlerad+6mm heading 300 from C2
+line thin color gray <-> from X2 to X1 "distance" aligned above small \
+    "C2 to C4" aligned below small
+```
+
+### Limit on the number of input tokens
+
+Pikchr is designed to operate safely in a hostile environment on the 
+open internet.  For that reason, it deliberately limits
+the number of tokens that will be processed in a single script.
+If more tokens than the limit are seen, the script aborts with an error.
+
+The input token limit was added to prevent a denial-of-service (DoS)
+attack based on deeply nested macros.  Each time a macro is invoked, it
+is rescanned and all of the tokens within the macro are added to the
+running total.  Without the token limit, an attacker could devise a
+script that contained nested macros that generates billions and billions
+of glyphs in the final image, consuming large amounts of memory and
+CPU time in the process.
+
+The token limit is determined by the PIKCHR_TOKEN_LIMIT proprocessor
+macro in the source code.  The default token limit is 100000, which
+should be more than enough for any reasonable script.  The limit
+can be increased (or decreased) at compile-time by redefining that
+macro.
+
+## Discontinued Features
+
+Pikchr deliberately omits some features of legacy PIC for security
+reasons.  Other features are omitted for lack of utility.
+
+### Pikchr omits the "`sh`" and "`copy`" statements.
+
+The "`sh`" command provided the script the ability to run arbitrary
+shell commands on the host computer.  Hence "`sh`" was just a built-in
+[RCE vulnerability][rce].  Having the ability to run arbitrary shell
+commands was a great innovation in a phototypesetting control
+system for Version-III Unix running on a PDP/11 in 1982, in a
+controlled-access facility.
+But such a feature is undesirable in modern web-facing applications
+accessible to random passers-by on the Internet.
+
+[rce]: https://en.wikipedia.org/wiki/Arbitrary_code_execution
+
+The "`copy`" command is similar.  It inserts the text of arbitrary
+files on the host computer into the middle of the PIC-script.
+
+### Pikchr omits "`for`" and "`if`" statements
+
+Pikchr omits all support for branching and looping.  Each Pikchr
+statement maps directly into (at most) one graphic object in the
+output.  This is a choice made to enhance the security and safety
+of Pikchr (without branching or looping, there is less opportunity
+for mischief) and to keep the language simple and accessible.
+
+To be clear, we *could* in theory implement loops and branches and
+subroutines in Pikchr in a safe way.  But doing so would be extra
+complication, both in the implementation and in the mental model that
+is maintained by the user.  Hence, in order to keep thing simple
+we choose to omit those features.
+If you need machine-generated code, employ a separate script
+language like Python or TCL to generate the Pikchr script for
+you. 
+
+### Pikchr omits the built-in "`sprintf()`" function
+
+The `sprintf()` function has well-known security concerns, and we
+do not want to make potential exploits accessible to attackers.
+Furthermore, the `sprintf()` is of little to no utility in a Pikchr
+script that lacks loops.  A secure version of `sprintf()` could be
+added to Pikchr, but doing that would basically require recoding
+a secure `sprintf()` from from scratch.  It is safer and easier
+to simply omit it.
+
+### Pikchr omits "`{...}`" subblocks
+
+The "`[...]`" style subblocks are supported and they work just as well.

+ 65 - 0
pikchr.mod/pikchr/doc/download.md

@@ -0,0 +1,65 @@
+# Download Options
+
+## The `pikchr.c` source file.
+
+The latest trunk version of the C source file for Pikchr is always
+available from the following link:
+
+  *  <https://pikchr.org/home/file/pikchr.c?ci=trunk>
+
+This is everything you need if you just want to build the Pikchr
+command-line tool, or use the Pikchr library in another application.
+Compile this file using commands like these:
+
+  *  `gcc -c pikchr.c`  &larr; to build the Pikchr library
+
+  *  `gcc -DPIKCHR_SHELL -o pikchr pikchr.c -lm` &larr; to build the
+     pikchr command-line tool
+
+See the [How To Compile Pikchr](./build.md) and the
+[How To Integrate Pikchr Into New Systems](./integrate.md) documents
+for more details on how to compile Pikchr.
+
+## Complete Source Tree Tarball
+
+A tarball or ZIP archive of the latest source code is available
+at the following links:
+
+  *  <https://pikchr.org/home/tarball/trunk/pikchr.tar.gz>
+  *  <https://pikchr.org/home/zip/trunk/pikchr.zip>
+
+With the complete source tree on your local machine, you can run
+"`make test`" to build and test Pikchr.
+
+## Clone The Fossil Repository
+
+Pikchr uses [Fossil](https://fossil-scm.org/home) for version control.
+You can clone the entire repository (which includes everything on
+this website, including all the documentation and test cases) as follows:
+
+  *  [Install Fossil](https://fossil-scm.org/home/uv/download.html)
+      if you haven't done so already
+
+  *  `fossil clone https://pikchr.org/ pikchr.fossil`
+
+  *  `fossil open pikchr.fossil`
+
+After you have the repository cloned, you can bring in any updates using:
+
+  *  `fossil up trunk`
+
+Once you have a clone of the Fossil repository, you can bring up a
+copy of this website on your local machine by typing:
+
+  *  `fossil ui`
+
+See the [Fossil documentation][fossil-doc] for more information on how
+manage a Fossil repository.
+
+[fossil-doc]: https://fossil-scm.org/home/doc/trunk/www/permutedindex.html
+
+## Clone The GitHub Mirror
+
+There is a (read-only) mirror of this repository on GitHub
+
+  *  <https://github.com/drhsqlite/pikchr>

+ 37 - 0
pikchr.mod/pikchr/doc/ellipseobj.md

@@ -0,0 +1,37 @@
+# Ellipse objects
+
+The shape of an ellipse is determined by its height and width.
+
+~~~~ pikchr indent
+A: ellipse thick
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+~~~~
+
+Curiously, the "radius" has no effect on the shape of an ellipse.
+The ellipse is determine solely by the width and height.  If the
+width and height are equal, the ellipse degenerates into a circle.
+
+
+## Boundary points:
+
+~~~~ pikchr indent
+A: ellipse thin
+dot ".c" below at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+~~~~

+ 660 - 0
pikchr.mod/pikchr/doc/examples.md

@@ -0,0 +1,660 @@
+# Pikchr Examples
+
+## Usage Note
+
+~~~ pikchr toggle
+box color red wid 2.6in \
+    "Click on any diagram on this page" big \
+    "to see the Pikchr source text" big
+
+~~~
+
+## How To Build Pikchr
+
+~~~ pikchr toggle
+            filewid *= 1.2
+  Src:      file "pikchr.y"; move
+  LemonSrc: file "lemon.c"; move
+  Lempar:   file "lempar.c"; move
+            arrow down from LemonSrc.s
+  CC1:      oval "C-Compiler" ht 50%
+            arrow " generates" ljust above
+  Lemon:    oval "lemon" ht 50%
+            arrow from Src chop down until even with CC1 \
+              then to Lemon.nw rad 20px
+            "Pikchr source " rjust "code input " rjust \
+              at 2nd vertex of previous
+            arrow from Lempar chop down until even with CC1 \
+              then to Lemon.ne rad 20px
+            " parser template" ljust " resource file" ljust \
+              at 2nd vertex of previous
+  PikSrc:   file "pikchr.c" with .n at lineht below Lemon.s
+            arrow from Lemon to PikSrc chop
+            arrow down from PikSrc.s
+  CC2:      oval "C-Compiler" ht 50%
+            arrow
+  Out:      file "pikchr.o" "or" "pikchr.exe" wid 110%
+~~~
+
+## SQLite Architecture Diagram
+
+Inspired by the JPG image seen at <https://www.sqlite.org/arch.html>
+
+~~~ pikchr toggle
+    lineht *= 0.4
+    $margin = lineht*2.5
+    scale = 0.75
+    fontscale = 1.1
+    charht *= 1.15
+    down
+In: box "Interface" wid 150% ht 75% fill white
+    arrow
+CP: box same "SQL Command" "Processor"
+    arrow
+VM: box same "Virtual Machine"
+    arrow down 1.25*$margin
+BT: box same "B-Tree"
+    arrow
+    box same "Pager"
+    arrow
+OS: box same "OS Interface"
+    box same with .w at 1.25*$margin east of 1st box.e "Tokenizer"
+    arrow
+    box same "Parser"
+    arrow
+CG: box same ht 200% "Code" "Generator"
+UT: box same as 1st box at (Tokenizer,Pager) "Utilities"
+    move lineht
+TC: box same "Test Code"
+    arrow from CP to 1/4<Tokenizer.sw,Tokenizer.nw> chop
+    arrow from 1/3<CG.nw,CG.sw> to CP chop
+
+    box ht (In.n.y-VM.s.y)+$margin wid In.wid+$margin \
+       at CP fill 0xd8ecd0 behind In
+    line invis from 0.25*$margin east of last.sw up last.ht \
+        "Core" italic aligned
+
+    box ht (BT.n.y-OS.s.y)+$margin wid In.wid+$margin \
+       at Pager fill 0xd0ece8 behind In
+    line invis from 0.25*$margin east of last.sw up last.ht \
+       "Backend" italic aligned
+
+    box ht (Tokenizer.n.y-CG.s.y)+$margin wid In.wid+$margin \
+       at 1/2<Tokenizer.n,CG.s> fill 0xe8d8d0 behind In
+    line invis from 0.25*$margin west of last.se up last.ht \
+       "SQL Compiler" italic aligned
+
+    box ht (UT.n.y-TC.s.y)+$margin wid In.wid+$margin \
+       at 1/2<UT,TC> fill 0xe0ecc8 behind In
+    line invis from 0.25*$margin west of last.se up last.ht \
+      "Accessories" italic aligned
+~~~
+
+## Syntax diagrams
+
+~~~ pikchr toggle
+$r = 0.2in
+linerad = 0.75*$r
+linewid = 0.25
+
+# Start and end blocks
+#
+box "element" bold fit
+line down 50% from last box.sw
+dot rad 250% color black
+X0: last.e + (0.3,0)
+arrow from last dot to X0
+move right 3.9in
+box wid 5% ht 25% fill black
+X9: last.w - (0.3,0)
+arrow from X9 to last box.w
+
+
+# The main rule that goes straight through from start to finish
+#
+box "object-definition" italic fit at 11/16 way between X0 and X9
+arrow to X9
+arrow from X0 to last box.w
+
+# The LABEL: rule
+#
+arrow right $r from X0 then down 1.25*$r then right $r
+oval " LABEL " fit
+arrow 50%
+oval "\":\"" fit
+arrow 200%
+box "position" italic fit
+arrow
+line right until even with X9 - ($r,0) \
+  then up until even with X9 then to X9
+arrow from last oval.e right $r*0.5 then up $r*0.8 right $r*0.8
+line up $r*0.45 right $r*0.45 then right
+
+# The VARIABLE = rule
+#
+arrow right $r from X0 then down 2.5*$r then right $r
+oval " VARIABLE " fit
+arrow 70%
+box "assignment-operator" italic fit
+arrow 70%
+box "expr" italic fit
+line right until even with X9 - ($r,0) \
+  then up until even with X9 then to X9
+
+# The PRINT rule
+#
+arrow right $r from X0 then down 3.75*$r then right $r
+oval "\"print\"" fit
+arrow
+box "print-args" italic fit
+line right until even with X9 - ($r,0) \
+  then up until even with X9 then to X9
+~~~
+
+## Swimlanes
+
+From the [Branching, Forking, Merging, and Tagging][bfmt] paper on
+the Fossil website.
+
+[bfmt]: https://fossil-scm.org/fossil/doc/trunk/www/branching.wiki
+
+~~~ pikchr toggle
+    $laneh = 0.75
+
+    # Draw the lanes
+    down
+    box width 3.5in height $laneh fill 0xacc9e3
+    box same fill 0xc5d8ef
+    box same as first box
+    box same as 2nd box
+    line from 1st box.sw+(0.2,0) up until even with 1st box.n \
+      "Alan" above aligned
+    line from 2nd box.sw+(0.2,0) up until even with 2nd box.n \
+      "Betty" above aligned
+    line from 3rd box.sw+(0.2,0) up until even with 3rd box.n \
+      "Charlie" above aligned
+    line from 4th box.sw+(0.2,0) up until even with 4th box.n \
+       "Darlene" above aligned
+
+    # fill in content for the Alice lane
+    right
+A1: circle rad 0.1in at end of first line + (0.2,-0.2) \
+       fill white thickness 1.5px "1" 
+    arrow right 50%
+    circle same "2"
+    arrow right until even with first box.e - (0.65,0.0)
+    ellipse "future" fit fill white height 0.2 width 0.5 thickness 1.5px
+A3: circle same at A1+(0.8,-0.3) "3" fill 0xc0c0c0
+    arrow from A1 to last circle chop "fork!" below aligned
+
+    # content for the Betty lane
+B1: circle same as A1 at A1-(0,$laneh) "1"
+    arrow right 50%
+    circle same "2"
+    arrow right until even with first ellipse.w
+    ellipse same "future"
+B3: circle same at A3-(0,$laneh) "3"
+    arrow right 50%
+    circle same as A3 "4"
+    arrow from B1 to 2nd last circle chop
+
+    # content for the Charlie lane
+C1: circle same as A1 at B1-(0,$laneh) "1"
+    arrow 50%
+    circle same "2"
+    arrow right 0.8in "goes" "offline"
+C5: circle same as A3 "5"
+    arrow right until even with first ellipse.w \
+      "back online" above "pushes 5" below "pulls 3 &amp; 4" below
+    ellipse same "future"
+
+    # content for the Darlene lane
+D1: circle same as A1 at C1-(0,$laneh) "1"
+    arrow 50%
+    circle same "2"
+    arrow right until even with C5.w
+    circle same "5"
+    arrow 50%
+    circle same as A3 "6"
+    arrow right until even with first ellipse.w
+    ellipse same "future"
+D3: circle same as B3 at B3-(0,2*$laneh) "3"
+    arrow 50%
+    circle same "4"
+    arrow from D1 to D3 chop
+~~~
+
+
+## Graphs
+
+Version control graph adapted from [Rebase Considered Harmful][rch].
+Commentary on the Pikchr source text to the first of these two 
+graphs is provided in a [separate tutorial](./teardown01.md).
+
+[rch]: https://fossil-scm.org/fossil/doc/trunk/www/rebaseharm.md
+
+~~~ pikchr toggle
+scale = 0.8
+fill = white
+linewid *= 0.5
+circle "C0" fit
+circlerad = previous.radius
+arrow
+circle "C1"
+arrow
+circle "C2"
+arrow
+circle "C4"
+arrow
+circle "C6"
+circle "C3" at dist(C2,C4) heading 30 from C2
+arrow
+circle "C5"
+arrow from C2 to C3 chop
+C3P: circle "C3'" at dist(C4,C6) heading 30 from C6
+arrow right from C3P.e
+C5P: circle "C5'"
+arrow from C6 to C3P chop
+
+box height C3.y-C2.y \
+    width (C5P.e.x-C0.w.x)+linewid \
+    with .w at 0.5*linewid west of C0.w \
+    behind C0 \
+    fill 0xc6e2ff thin color gray
+box same width previous.e.x - C2.w.x \
+    with .se at previous.ne \
+    fill 0x9accfc
+"trunk" below at 2nd last box.s
+"feature branch" above at last box.n
+
+circle "C0" at 3.7cm south of C0
+arrow
+circle "C1"
+arrow
+circle "C2"
+arrow
+circle "C4"
+arrow
+circle "C6"
+circle "C3" at dist(C2,C4) heading 30 from C2
+arrow
+circle "C5"
+arrow
+circle "C7"
+arrow from C2 to C3 chop
+arrow from C6 to C7 chop
+
+box height C3.y-C2.y \
+    width (C7.e.x-C0.w.x)+1.5*C1.radius \
+    with .w at 0.5*linewid west of C0.w \
+    behind C0 \
+    fill 0xc6e2ff thin color gray
+box same width previous.e.x - C2.w.x \
+    with .se at previous.ne \
+    fill 0x9accfc
+"trunk" below at 2nd last box.s
+"feature branch" above at last box.n
+~~~
+
+## Impossible Trident
+
+Contributed by Kees Nuyt
+
+~~~ pikchr toggle
+# Impossible trident pikchr script
+# https://en.wikipedia.org/wiki/Impossible_trident
+# pikchr script by Kees Nuyt, license Creative Commons BY-NC-SA 
+# https://creativecommons.org/licenses/by-nc-sa/4.0/
+
+scale = 1.0
+eh = 0.5cm
+ew = 0.2cm
+ed = 2 * eh
+er = 0.4cm
+lws = 4.0cm
+lwm = lws + er
+lwl = lwm + er
+
+ellipse height eh width ew
+L1: line width lwl from last ellipse.n
+line width lwm from last ellipse.s
+LV: line height eh down
+
+move right er down ed from last ellipse.n
+ellipse height eh width ew
+L3: line width lws right from last ellipse.n to LV.end then down eh right ew
+line width lwm right from last ellipse.s then to LV.start
+
+move right er down ed from last ellipse.n
+ellipse height eh width ew
+line width lwl right from last ellipse.n then to L1.end
+line width lwl right from last ellipse.s then up eh
+~~~
+
+
+## PIC Examples From The [Brian W. Kernighan paper][bwk]
+
+[bwk]: /uv/pic.pdf
+
+-----
+
+From page 18.
+
+~~~ pikchr toggle
+define ndblock {
+  box wid boxwid/2 ht boxht/2
+  down;  box same with .t at bottom of last box;   box same
+}
+boxht = .2; boxwid = .3; circlerad = .3; dx = 0.05
+down; box; box; box; box ht 3*boxht "." "." "."
+L: box; box; box invis wid 2*boxwid "hashtab:" with .e at 1st box .w
+right
+Start: box wid .5 with .sw at 1st box.ne + (.4,.2) "..."
+N1: box wid .2 "n1";  D1: box wid .3 "d1"
+N3: box wid .4 "n3";  D3: box wid .3 "d3"
+box wid .4 "..."
+N2: box wid .5 "n2";  D2: box wid .2 "d2"
+arrow right from 2nd box
+ndblock
+spline -> right .2 from 3rd last box then to N1.sw + (dx,0)
+spline -> right .3 from 2nd last box then to D1.sw + (dx,0)
+arrow right from last box
+ndblock
+spline -> right .2 from 3rd last box to N2.sw-(dx,.2) to N2.sw+(dx,0)
+spline -> right .3 from 2nd last box to D2.sw-(dx,.2) to D2.sw+(dx,0)
+arrow right 2*linewid from L
+ndblock
+spline -> right .2 from 3rd last box to N3.sw + (dx,0)
+spline -> right .3 from 2nd last box to D3.sw + (dx,0)
+circlerad = .3
+circle invis "ndblock"  at last box.e + (1.2,.2)
+arrow dashed from last circle.w to 5/8<last circle.w,2nd last box> chop
+box invis wid 2*boxwid "ndtable:" with .e at Start.w
+~~~
+
+
+-----
+
+From page 19:  The "intermediate code" line had to
+be lengthened so that the text would fit.
+
+~~~ pikchr toggle
+        arrow "source" "code"
+LA:     box "lexical" "analyzer"
+        arrow "tokens" above
+P:      box "parser"
+        arrow "intermediate" "code" wid 200%
+Sem:    box "semantic" "checker"
+        arrow
+        arrow <-> up from top of LA
+LC:     box "lexical" "corrector"
+        arrow <-> up from top of P
+Syn:    box "syntactic" "corrector"
+        arrow up
+DMP:    box "diagnostic" "message" "printer"
+        arrow <-> right  from east of DMP
+ST:     box "symbol" "table"
+        arrow from LC.ne to DMP.sw
+        arrow from Sem.nw to DMP.se
+        arrow <-> from Sem.top to ST.bot
+~~~
+
+------
+
+From page 20:  Various minor tweaks
+
+~~~ pikchr toggle
+        circle "DISK"
+        arrow "character" "defns" right 150%
+CPU:    box "CPU" "(16-bit mini)"
+        arrow <- from top of CPU up "input " rjust
+        move right from CPU.e
+CRT:    "   CRT" ljust
+        line from CRT - 0,0.075 up 0.15 \
+                then right 0.5 \
+                then right 0.5 up 0.25 \
+                then down 0.5+0.15 \
+                then left 0.5 up 0.25 \
+                then left 0.5
+        arrow from CPU.e right until even with previous.start
+Paper:  CRT + 1.05,0.75
+        arrow <- from Paper down 1.5
+        " ...  paper" ljust at end of last arrow + 0, 0.25
+        circle rad 0.05 at Paper + (-0.055, -0.25)
+        circle rad 0.05 at Paper + (0.055, -0.25)
+        "   rollers" ljust at Paper + (0.1, -0.25)
+~~~
+
+-----
+
+From [forum post 1b58e4bf9f717dbe](/forumpost/1b58e4bf9f717dbe) by
+Ryan Smith:
+
+~~~ pikchr toggle
+CP:   dot;
+
+      $h = 10;
+      line from CP go 0.7cm heading $h; DA1: dot;
+      line         go 1cm heading $h; DA2: dot;
+      line         go 1cm heading $h; DA3: dot;
+      line         go 1cm heading $h; DA4: dot;
+      line         go 1cm heading $h; DA5: dot;
+      line         go 1cm heading $h; DA6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 0.8cm heading $h; DB1: dot;
+      line         go 1cm heading $h; DB2: dot;
+      line         go 1cm heading $h; DB3: dot;
+      line         go 1cm heading $h; DB4: dot;
+      line         go 1cm heading $h; DB5: dot;
+      line         go 1cm heading $h; DB6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+22;
+      line from CP go 0.9cm heading $h; DC1: dot;
+      line         go 1cm heading $h; DC2: dot;
+      line         go 1cm heading $h; DC3: dot;
+      line         go 1cm heading $h; DC4: dot;
+      line         go 1cm heading $h; DC5: dot;
+      line         go 1cm heading $h; DC6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 0.9cm heading $h; DD1: dot;
+      line         go 1cm heading $h; DD2: dot;
+      line         go 1cm heading $h; DD3: dot;
+      line         go 1cm heading $h; DD4: dot;
+      line         go 1cm heading $h; DD5: dot;
+      line         go 1cm heading $h; DD6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+19;
+      line from CP go 1cm heading $h; DE1: dot;
+      line         go 1cm heading $h; DE2: dot;
+      line         go 1cm heading $h; DE3: dot;
+      line         go 1cm heading $h; DE4: dot;
+      line         go 1cm heading $h; DE5: dot;
+      line         go 1cm heading $h; DE6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+19;
+      line from CP go 1cm heading $h; DF1: dot;
+      line         go 1cm heading $h; DF2: dot;
+      line         go 1cm heading $h; DF3: dot;
+      line         go 1cm heading $h; DF4: dot;
+      line         go 1cm heading $h; DF5: dot;
+      line         go 1cm heading $h; DF6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 1cm heading $h; DG1: dot;
+      line         go 1cm heading $h; DG2: dot;
+      line         go 1cm heading $h; DG3: dot;
+      line         go 1cm heading $h; DG4: dot;
+      line         go 1cm heading $h; DG5: dot;
+      line         go 1cm heading $h; DG6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 1cm heading $h; DH1: dot;
+      line         go 1.1cm heading $h; DH2: dot;
+      line         go 1.1cm heading $h; DH3: dot;
+      line         go 1cm heading $h; DH4: dot;
+      line         go 1cm heading $h; DH5: dot;
+      line         go 1cm heading $h; DH6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+24;
+      line from CP go 1cm heading $h; DI1: dot;
+      line         go 1cm heading $h; DI2: dot;
+      line         go 1cm heading $h; DI3: dot;
+      line         go 1cm heading $h; DI4: dot;
+      line         go 1cm heading $h; DI5: dot;
+      line         go 1cm heading $h; DI6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 0.9cm heading $h; DJ1: dot;
+      line         go 1cm heading $h; DJ2: dot;
+      line         go 1cm heading $h; DJ3: dot;
+      line         go 1cm heading $h; DJ4: dot;
+      line         go 1cm heading $h; DJ5: dot;
+      line         go 1cm heading $h; DJ6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+18;
+      line from CP go 1cm heading $h; DK1: dot;
+      line         go 1cm heading $h; DK2: dot;
+      line         go 1cm heading $h; DK3: dot;
+      line         go 1cm heading $h; DK4: dot;
+      line         go 1cm heading $h; DK5: dot;
+      line         go 1cm heading $h; DK6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 1cm heading $h; DL1: dot;
+      line         go 1cm heading $h; DL2: dot;
+      line         go 1cm heading $h; DL3: dot;
+      line         go 1cm heading $h; DL4: dot;
+      line         go 1cm heading $h; DL5: dot;
+      line         go 1cm heading $h; DL6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+19;
+      line from CP go 1cm heading $h; DM1: dot;
+      line         go 1cm heading $h; DM2: dot;
+      line         go 1cm heading $h; DM3: dot;
+      line         go 1cm heading $h; DM4: dot;
+      line         go 1cm heading $h; DM5: dot;
+      line         go 1cm heading $h; DM6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 1cm heading $h; DN1: dot;
+      line         go 1cm heading $h; DN2: dot;
+      line         go 1cm heading $h; DN3: dot;
+      line         go 1cm heading $h; DN4: dot;
+      line         go 1cm heading $h; DN5: dot;
+      line         go 1cm heading $h; DN6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+16;
+      line from CP go 1.1cm heading $h; DO1: dot;
+      line         go 1cm heading $h; DO2: dot;
+      line         go 1cm heading $h; DO3: dot;
+      line         go 1cm heading $h; DO4: dot;
+      line         go 1cm heading $h; DO5: dot;
+      line         go 1cm heading $h; DO6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 1.2cm heading $h; DP1: dot;
+      line         go 1cm heading $h; DP2: dot;
+      line         go 1cm heading $h; DP3: dot;
+      line         go 1cm heading $h; DP4: dot;
+      line         go 1cm heading $h; DP5: dot;
+      line         go 1cm heading $h; DP6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+22;
+      line from CP go 1.3cm heading $h; DQ1: dot;
+      line         go 1cm heading $h; DQ2: dot;
+      line         go 1cm heading $h; DQ3: dot;
+      line         go 1cm heading $h; DQ4: dot;
+      line         go 1cm heading $h; DQ5: dot;
+      line         go 1cm heading $h; DQ6: dot;
+      line         go 1cm heading $h;
+
+      $h = $h+20;
+      line from CP go 1.5cm heading $h; DR1: dot;
+      line         go 1cm heading $h; DR2: dot;
+      line         go 1cm heading $h; DR3: dot;
+      line         go 1cm heading $h; DR4: dot;
+      line         go 1cm heading $h; DR5: dot;
+      line         go 1cm heading $h; DR6: dot;
+      line         go 0.4cm heading $h;
+
+      line from DA1 to DB1 then to DC1 then to DD1 \
+               then to DE1 then to DF1 then to DG1 \
+               then to DH1 then to DI1 then to DJ1 \
+               then to DK1 then to DL1 then to DM1 \
+               then to DN1 then to DO1 then to DP1 \
+               then to DQ1 then to DR1 then to DA2 thin;
+
+      line          to DB2 then to DC2 then to DD2 \
+               then to DE2 then to DF2 then to DG2 \
+               then to DH2 then to DI2 then to DJ2 \
+               then to DK2 then to DL2 then to DM2 \
+               then to DN2 then to DO2 then to DP2 \
+               then to DQ2 then to DR2 then to DA3 thin;
+
+      line          to DB3 then to DC3 then to DD3 \
+               then to DE3 then to DF3 then to DG3 \
+               then to DH3 then to DI3 then to DJ3 \
+               then to DK3 then to DL3 then to DM3 \
+               then to DN3 then to DO3 then to DP3 \
+               then to DQ3 then to DR3 then to DA4 thin;
+
+      line          to DB4 then to DC4 then to DD4 \
+               then to DE4 then to DF4 then to DG4 \
+               then to DH4 then to DI4 then to DJ4 \
+               then to DK4 then to DL4 then to DM4 \
+               then to DN4 then to DO4 then to DP4 \
+               then to DQ4 then to DR4 then to DA5 thin;
+
+      line          to DB5 then to DC5 then to DD5 \
+               then to DE5 then to DF5 then to DG5 \
+               then to DH5 then to DI5 then to DJ5 \
+               then to DK5 then to DL5 then to DM5 \
+               then to DN5 then to DO5 then to DP5 \
+               then to DQ5 then to DR5 then to DA6 thin;
+
+      line          to DB6 then to DC6 then to DD6 \
+               then to DE6 then to DF6 then to DG6 \
+               then to DH6 then to DI6 then to DJ6 \
+               then to DK6 then to DL6 then to DM6 \
+               then to DN6 then to DO6 then to DP6 \
+               then to DQ6 then to DR6 thin;
+
+BODY: ellipse width 0.3cm height 0.4cm fill black at 2cm heading 340 from CP;
+RDT:  ellipse width 0.2cm height 0.2cm fill red at last ellipse.c;
+      ellipse width 0.2cm height 0.1cm fill black at RDT.n;
+      ellipse width 0.2cm height 0.1cm fill black at RDT.s;
+HEAD: ellipse with .s at BODY.n width 0.14cm height 0.12cm;
+      line thin thin from HEAD.nw go 0.08cm heading 155;
+      line thin thin from HEAD.ne go 0.08cm heading 205;
+SPIN: ellipse thin at BODY.s width 0.06cm height 0.08cm;
+      line thin from SPIN.s go 1.2cm heading 142;
+
+  $h=340; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h- 70 then go 0.2cm heading $h-150;
+  $h=306; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h-108 then go 0.2cm heading $h-150;
+  $h=260; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h- 70 then go 0.2cm heading $h-110;
+  $h=210; line from RDT thin chop      go 0.8cm heading $h then go 0.5cm heading $h-115 then go 0.2cm heading $h-130;
+
+  $h= 22; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h+ 52 then go 0.2cm heading $h+130;
+  $h= 45; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h+ 74 then go 0.2cm heading $h+130;
+  $h= 68; line from RDT thin thin chop go 0.8cm heading $h then go 0.5cm heading $h+ 70 then go 0.2cm heading $h+134;
+  $h= 92; line from RDT thin chop      go 0.8cm heading $h then go 0.5cm heading $h+100 then go 0.2cm heading $h+130;
+
+~~~

+ 42 - 0
pikchr.mod/pikchr/doc/fileobj.md

@@ -0,0 +1,42 @@
+# File objects
+
+A "file" is a stylized image of a piece of paper with the upper right
+corner folded over.  Similar images are commonly used to represent "files".
+The shape of a file object is defined by its width, height, and radius.
+The radius is the height and width of the folded corner.  The default values
+for height, radius, and width are control by variables
+"`fileht`", "`filerad`", and "`filewid`".
+
+~~~~ pikchr indent
+A: file thick rad 100%
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.n)
+X4: line thin color gray right 70% from A.rad below start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X4) down 30% <-
+line thin color gray from (previous text,X3) up 30% <-
+~~~~
+
+## Boundary points:
+
+~~~~ pikchr indent
+A: file thin rad 80%
+dot ".c" below at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+~~~~

+ 48 - 0
pikchr.mod/pikchr/doc/fit.md

@@ -0,0 +1,48 @@
+# The "fit" attribute
+
+The "`fit`" attribute causes an object to automatically adjust its
+"`width`", "`height`", and/or "`radius`" so that it will enclose its
+text annotations with a reasonable margin.
+
+~~~ pikchr toggle
+box "with" "\"fit\"" fit
+move
+box "without" "\"fit\""
+~~~
+
+The "`fit`" attribute only works with text annotations that occur
+earlier in the object definition.  In other words, the "`fit`" keyword
+should come after all text annotations have been defined.
+
+## Pikchr guesses at the size of text
+
+Pikchr does not have access to the SVG rendering engine.  Therefore,
+it cannot know the precise dimensions of text annotations.  It has to
+guess.  Usually Pikchr does a reasonable job, but sometimes it can be
+a little off, especially with unusual characters.  If "`fit`" causes the
+object to be too narrow, you can try adding spaces at the beginning and
+end of the longest text annotation.  You can also adjust the width
+and height by a percentage after running "`fit`":
+
+   *  `width 110%`
+   *  `height 90%`
+   *  `radius 120%`
+
+And so forth.  Substitute percentage increases and decreases, as
+appropriate, to make the text fit like you want.
+
+## Auto-fit
+
+If at the end of an objection definition the requested width or height of the
+object is less then or equal to zero, then that dimension is adjusted
+upwards to enclose the text annotations.	  Thus, by setting variables
+like:
+
+~~~
+    boxwid = 0
+    boxht = 0
+~~~
+
+You can cause all boxes to scale to enclose their text annotations.
+(Caution:  boxes without any text annotations go to zero height and width
+and thus disappear when auto-fit is enabled.)

+ 379 - 0
pikchr.mod/pikchr/doc/grammar.md

@@ -0,0 +1,379 @@
+# Pikchr Grammar
+
+This file describes the grammar of the input files to Pikchr.  Keywords
+and operators are shown in **bold**.  Non-terminal symbols are shown
+in *italic*.  Special token classes are shown in ALL-CAPS.  A grammar
+symbol followed by "*" means zero-or-more.  A grammar symbol
+followed by "?" means zero-or-one.  Parentheses are used for grouping.
+Two grammar symbols within "(..|..)" means one or the other.
+ Marks of the form
+"[&#9654;info](./grammar.md)" are links to more information and are
+not part of the grammar.
+
+The following special token classes are recognized:
+
+  *  NEWLINE  &rarr;  A un-escaped newline character, U+000A.
+     A backslash followed by zero or more whitespace characters
+     and then a U+000A character is interpreted as ordinary whitespace,
+     not as a NEWLINE.
+
+  *  LABEL  &rarr;  An object or place label starting with an
+     upper-case ASCII letter and continuing with zero or more
+     ASCII letters, digits, and/or underscores.  A LABEL always starts
+     with an upper-case letter.
+
+  *  VARIABLE  &rarr;  A variable name consisting of a lower-case
+     ASCII letter or "$" or "@" and followed by zero or more
+     ASCII letters, digits, and/or underscores.  VARIABLEs may
+     contain upper-case letters, but they never begin with an upper-case.
+     In this way, VARIABLEs are distinct from LABELs.
+
+  *  NUMBER  &rarr;  A numeric literal.  The value can be a decimal
+     integer, a floating point value, or a hexadecimal literal
+     starting with "0x".  Decimal and floating point values can
+     optionally be followed by a two-character unit designator that is
+     one of:  "in", "cm", "px", "pt", "pc", or "mm".  There can be
+     no whitespace in between the numeric portion of the constant and
+     the unit.
+
+  *  ORDINAL  &rarr;  A non-zero integer literal followed by one of the
+     suffixes "st", "nd", "rd", or "th".  Examples: "1st", "2nd",
+    "3rd", "4th", "5th", and so forth.   As a special case, "first"
+     is accepted as an alternative spelling of "1st".
+
+  *  STRING  &rarr;  A string literal that begins and ends with
+     double-quotes (U+0022).  Within the string literal, a double-quote
+     character can be escaped using backslash (U+005c).  A backslash
+     can also be used to escape a backslash.  No other escape sequences
+     are recognized.
+
+  *  COLORNAME &rarr;  One of the 140 official HTML color names, in 
+     any mixture of upper and lower cases.  The value of a COLORNAME is
+     an integer which is the 24-bit RGB value of that color.  Two
+     additional color names of "None" and "Off" are also recognized and
+     have a value of -1.
+
+  *  CODEBLOCK &rarr;   All tokens contained within nested {...}.  This
+     is only used as the body of a "define" statement.
+
+There are many non-terminals in the grammar, but a few are more important.
+If you are new to the Pikchr language, begin by focusing on these
+six:
+
+  *  *statement* &rarr;  A Pikchr script is just a list of statements.
+
+  *  *attribute* &rarr;  Each graphic object is configured with zero or
+     more attributes.
+
+  *  *object* &rarr;  A reference to a prior graphic object.
+
+  *  *place* &rarr;  A specific point associated with an *object*.
+
+  *  *position* &rarr;  Any (2-D) point in space.  An (x,y) pair.
+
+  *  *expr* &rarr;  A scalar expression.
+
+
+A complete input file to Pikchr consists of a single *statement-list*.
+
+## *statement-list*: [&#9654;info](./stmtlist.md)
+
+  * *statement*?
+  * *statement-list* NEWLINE *statement*?
+  * *statement-list* **;** *statement*?
+
+## *statement*:  [&#9654;info](./stmt.md)
+  * *object-definition*
+  * LABEL **:** *object-definition*
+  * LABEL **:** *place*
+  * *direction*
+  * VARIABLE *assignment-op* *expr*
+  * **define** VARIABLE CODEBLOCK     [&#9654;info](./macro.md)
+  * **print** *print-argument* (**,** *print-argument*)\*
+  * **assert (** *expr* **==** *expr* **)**
+  * **assert (** *position* **==** *position* **)**
+
+
+## *direction*:
+  * **right**
+  * **down**
+  * **left**
+  * **up**
+
+## *assignment-op*:
+  * **=**
+  * **+=**
+  * **-=**
+  * **\*=**
+  * **/=**
+
+## *print-argument*:
+  * *expr*
+  * STRING
+
+## *object-definition*:
+  * *object-class* *attribute*\*
+  * STRING *text-attribute*\* *attribute*\*
+  * **[** *statement-list* **]** *attribute*\*
+
+## *object-class*:
+  * **arc**
+  * **arrow**
+  * **box**          [&#9654;info](./boxobj.md)
+  * **circle**       [&#9654;info](./circleobj.md)
+  * **cylinder**     [&#9654;info](./cylinderobj.md)
+  * **dot**
+  * **ellipse**      [&#9654;info](./ellipseobj.md)
+  * **file**         [&#9654;info](./fileobj.md)
+  * **line**
+  * **move**
+  * **oval**         [&#9654;info](./ovalobj.md)
+  * **spline**
+  * **text**
+
+## *attribute*:
+  * *path-attribute*              [&#9654;info](./pathattr.md)
+  * *location-attribute*          [&#9654;info](./locattr.md)
+  * STRING *text-attribute*\*     [&#9654;info](./annotate.md)
+  * **same**
+  * **same as** *object*
+  * *numeric-property* *new-property-value*
+  * **dashed** *expr*?
+  * **dotted** *expr*?
+  * **color** *color-expr*
+  * **fill** *color-expr*
+  * **behind** *object*      [&#9654;info](./behind.md)
+  * **cw**
+  * **ccw**
+  * **&lt;-**                [&#9654;info](./arrowdir.md)
+  * **-&gt;**                [&#9654;info](./arrowdir.md)
+  * **&lt;-&gt;**            [&#9654;info](./arrowdir.md)
+  * **invis**|**invisible**  [&#9654;info](./invis.md)
+  * **thick**                [&#9654;info](./thickthin.md)
+  * **thin**                 [&#9654;info](./thickthin.md)
+  * **solid**                [&#9654;info](./thickthin.md)
+  * **chop**                 [&#9654;info](./chop.md)
+  * **fit**                  [&#9654;info](./fit.md)
+
+## *color-expr*: [&#9654;info](./colorexpr.md)
+  * *expr*
+
+## *new-property-value*:  [&#9654;info](./newpropval.md)
+  * *expr*
+  * *expr* **%**
+
+## *numeric-property*:  [&#9654;info](./numprop.md)
+  * **diameter**
+  * **ht**
+  * **height**
+  * **rad**
+  * **radius**
+  * **thickness**
+  * **width**
+  * **wid**
+
+## *text-attribute*:  [&#9654;info](./textattr.md)
+  * **above**
+  * **aligned**
+  * **below**
+  * **big**
+  * **bold**
+  * **center**
+  * **italic**
+  * **ljust**
+  * **rjust**
+  * **small**
+
+## *path-attribute*:   [&#9654;info](./pathattr.md)
+  * **from** *position*
+  * **then**? **to** *position*
+  * **then**? **go**? *direction* *line-length*?
+  * **then**? **go**? *direction* **until**? **even with** *position*
+  * (**then**|**go**) *line-length*? **heading** *compass-angle*
+  * (**then**|**go**) *line-length*? *compass-direction*
+  * **close**
+
+## *line-length*:  [&#9654;info](./linelen.md)
+
+  * *expr*
+  * *expr* **%**
+
+## *compass-angle*:   [&#9654;info](./compassangle.md)
+
+  * *expr*
+
+## *compass-direction*:
+  * **n**
+  * **north**
+  * **ne**
+  * **e**
+  * **east**
+  * **se**
+  * **s**
+  * **south**
+  * **sw**
+  * **w**
+  * **west**
+  * **nw**
+
+## *location-attribute*: [&#9654;info](./locattr.md)
+  * **at** *position*
+  * **with** *edgename* **at** *position*
+  * **with** *dot-edgename* **at** *position*
+
+## *position*:  [&#9654;info](./position.md)
+
+  *  *expr* **,** *expr*
+  *  *place*
+  *  *place* **+** *expr* **,** *expr*
+  *  *place* **-** *expr* **,** *expr*
+  *  *place* **+ (** *expr* **,** *expr* **)**
+  *  *place* **- (** *expr* **,** *expr* **)**
+  *  **(** *position* **,** *position* **)**
+  *  **(** *position* **)**
+  *  *fraction* **of the way between** *position* **and** *position*
+  *  *fraction* **way between** *position* **and** *position*
+  *  *fraction* **between** *position* **and** *position*
+  *  *fraction* **<** *position* **,** *position* **>**
+  *  *distance* *which-way-from* *position*
+
+## *fraction*:
+  *  *expr*
+
+## *distance*
+  *  *expr*
+
+## *which-way-from*:
+
+  *  **above**
+  *  **below**
+  *  **right of**
+  *  **left of**
+  *  **n of**
+  *  **north of**
+  *  **ne of**
+  *  **e of**
+  *  **east of**
+  *  **se of**
+  *  **s of**
+  *  **south of**
+  *  **sw of**
+  *  **w of**
+  *  **west of**
+  *  **nw of**
+  *  **heading** *compass-angle* **from**
+
+## *place*:      [&#9654;info](./place.md)
+
+  *  *object*
+  *  *object* *dot-edgename*
+  *  *edgename* **of** *object*
+  *  ORDINAL **vertex of** *object*
+
+## *object*:
+  *  LABEL
+  *  *object* **.** LABEL
+  *  *nth-object* **of**|**in** *object*
+
+## *nth-object*:
+
+  *  ORDINAL *object-class*
+  *  ORDINAL **last** *object-class*
+  *  ORDINAL **previous** *object-class*
+  *  **last** *object-class*
+  *  **previous** *object-class*
+  *  **last**
+  *  **previous**
+  *  ORDINAL **[]**
+  *  ORDINAL **last []**
+  *  ORDINAL **previous []**
+  *  **last []**
+  *  **previous []**
+
+## *dot-edgename*:
+  * **.n**
+  * **.north**
+  * **.t**
+  * **.top**
+  * **.ne**
+  * **.e**
+  * **.east**
+  * **.right**
+  * **.se**
+  * **.s**
+  * **.south**
+  * **.bot**
+  * **.bottom**
+  * **.sw**
+  * **.w**
+  * **.west**
+  * **.left**
+  * **.nw**
+  * **.c**
+  * **.center**
+  * **.start**
+  * **.end**
+
+## *edgename*:
+  * **n**
+  * **north**
+  * **ne**
+  * **e**
+  * **east**
+  * **se**
+  * **s**
+  * **south**
+  * **sw**
+  * **w**
+  * **west**
+  * **nw**
+  * **t**
+  * **top**
+  * **bot**
+  * **bottom**
+  * **left**
+  * **right**
+  * **c**
+  * **center**
+  * **start**
+  * **end**
+
+
+## *expr*:
+
+  *  NUMBER
+  *  VARIABLE
+  *  COLORNAME
+  *  *place* **.x**
+  *  *place* **.y**
+  *  *object* *dot-property*
+  *  **(** *expr* **)**
+  *  *expr* **+** *expr*
+  *  *expr* **-** *expr*
+  *  *expr* **\*** *expr*
+  *  *expr* **/** *expr*
+  *  **-** *expr*
+  *  **+** *expr*
+  *  **abs (** *expr* **)**
+  *  **cos (** *expr* **)**
+  *  **dist (** *position* **,** *position* **)**
+  *  **int (** *expr* **)**
+  *  **max (** *expr* **,** *expr* **)**
+  *  **min (** *expr* **,** *expr* **)**
+  *  **sin (** *expr* **)**
+  *  **sqrt (** *expr* **)**
+
+## *dot-property*:
+
+  * **.color**
+  * **.dashed**
+  * **.diameter**
+  * **.dotted**
+  * **.fill**
+  * **.ht**
+  * **.height**
+  * **.rad**
+  * **.radius**
+  * **.thickness**
+  * **.wid**
+  * **.width**

+ 114 - 0
pikchr.mod/pikchr/doc/integrate.md

@@ -0,0 +1,114 @@
+# How To Integrate Pikchr Into New Systems
+
+Pikchr is (currently) implemented in C.
+
+  *  It uses no external libraries other than the standard C library and
+     the standard math library (for sin(), cos(), and some others).
+  *  It is completely contained in a single source code file:
+     [`pikchr.c`](/file/pikchr.c).  There is also a header file
+     [`pikchr.h`](/file/pikchr.h) available if you want it, but it is not
+     required.
+  *  It uses a single C-language interface routine: `pikchr()`
+
+Any existing Markdown or other wiki rendering engine that can invoke
+a C-language library should be able to integrate Pikchr quickly and
+easily.  The code has been audited and fuzzed and is
+believed to be impervious to hostile inputs.
+
+## C-language interface.
+
+There is a single interface function:
+
+~~~~
+  char *pikchr(
+    const char *zText,     /* Input PIKCHR source text.  zero-terminated */
+    const char *zClass,    /* Add class="%s" to <svg> markup */
+    unsigned int mFlags,   /* Flags used to influence rendering behavior */
+    int *pnWidth,          /* Write width of <svg> here, if not NULL */
+    int *pnHeight          /* Write height here, if not NULL */
+  );
+~~~~
+
+To convert Pikchr into SVG text ready to be inserted into the HTML output
+stream, simply invoke the pikchr() function, passing the source text
+as the first argument.  The SVG output text is returned, and the desired
+width and height of that text is written into the *pnWidth and *pnHeight
+variables.
+
+If the input Pikchr text contains errors, a negative number is
+written into *pnWidth and the returned text is an error message ready
+to be dropped into "`<pre>...</pre>`".  Any "`<`" or "`>`" or
+"`&`" characters in the error message text have already been escaped,
+so the error message can be inserted directly into an HTML output stream
+without further processing.
+
+The returned string is held in memory obtained from `malloc()`.  The caller
+is responsible for freeing this memory to prevent a memory leak.  It is
+possible (though unlikely) for pikchr() to return a NULL pointer, for
+example if it hits a `malloc()` failure.
+
+If the zClass parameter is not NULL, then it is an extra class name
+(or names) that is inserted into the "`<svg>`" element of the returned
+string.
+
+## Flags passed to pikchr()
+
+The [`pikchr.h`](/file/pikchr.h) header file currently defines two flags
+that can be passed into the pikchr() function as the 3rd argument, "mFlags".
+(Additional flags might get added in future releases.)
+
+   *  `PIKCHR_PLAINTEXT_ERRORS` &rarr;
+      Normally, the text returned by pikchr() in the event of an error
+      is formatted as HTML.  Setting this flag causes the error message
+      to be plain text.
+
+   *  `PIKCHR_DARK_MODE` &rarr;
+      When this flag is used,  Pikchr inverts the colors in the diagram
+      to make them suitable for "dark mode" pages.  The main Pikchr
+      website can be switched between
+      [dark-mode](./integrate.md?skin=darkmode) and
+      [light-mode](./integrate.md?skin=) so
+      that you can see the effects of this flag on Pikchr diagrams.
+
+## Example use of pikchr()
+
+The "`pikchr.c`" source file itself contains an example use of the
+pikchr() function.  If "`pikchr.c`" is compiled with the `-DPIKCHR_SHELL`
+compile-time option, it will include a `main()` that reads all the
+files named as arguments, runs each through pikchr() and outputs
+the result embedded in HTML.  So if you want an example, look at the
+"main()" function at the bottom of the "`pikchr.c`" source file.
+
+## Performance considerations
+
+Pikchr seems to use about 650 CPU cycles per byte of input.  So even
+a slow core can handle on the order of 3 or 4 megabytes of Pikchr input
+per second.  As most Pikchr scripts are less than 1000 bytes, the processing
+overhead of running Pikchr is likely to be too small to measure.  Pikchr
+could perhaps be optimized to increase its performance, but it is so fast
+already (especially compared to the rest of the Markdown formatting
+stream) that we don't see any point in that.  Contact the developers if
+you uncover evidence that contradicts anything in this paragraph.
+
+## Fuzz Testing
+
+You can build a [libFuzzer][1]-based fuzz tester for Pikchr by
+compiling like this (or similarly):
+
+~~~~
+   clang -g -O3 -fsanitize=fuzzer,undefined,address -o fuzz -DPIKCHR_FUZZ pikchr.c
+~~~~
+
+Gather a bunch of Pikchr scripts to be used as seeds (perhaps from the
+tests/ or examples/ subdirectories of the source tree) and put them in
+a subdirectory, which we will call "fz".  Then run:
+
+~~~~
+   fuzz fz
+~~~~
+
+We have run this for hundreds of millions of tests already.  You
+are welcomed to run more.
+
+
+[1]: https://www.llvm.org/docs/LibFuzzer.html

+ 8 - 0
pikchr.mod/pikchr/doc/invis.md

@@ -0,0 +1,8 @@
+# The invis or invisible attribute
+
+The "`invis`" or "`invisible`" attribute has the effect of setting
+the "`thickness`" to zero.  This makes the object disappear.  However,
+all text annotations associated with the object are still visible.
+
+Draw rotated text by making the text an annotation on an "`invis`"
+line and using the "`aligned`" *[text-attribute](./textattr.md)*.

+ 24 - 0
pikchr.mod/pikchr/doc/linelen.md

@@ -0,0 +1,24 @@
+# line-length
+
+A *line-length* is an expression that specifies how long to draw a
+line segment.  The value can be either absolute (ex: "`1.2cm`", 
+"`.5in`", "`0.5*circlerad`", and so forth) or it can be a percentage value
+(ex: "`85%`").
+
+  * *expr*
+  * *expr* **%**
+
+If the percentage value is used, the basis is usually the
+value stored in the "`linewid`" variable.  However, for a case of
+either
+
+  * **up** *expr* **%**
+  * **down** *expr* **%**
+
+Then the percentage refers to the current "`lineht`" value instead.  The
+"`linewid`" value is always used for headings even if the heading
+is "`0`" or "`180`" or "`north`" or "`south`".
+
+In most cases it does not matter whether "`linewid`" or "`lineht`"
+gets used for the percentage basis since both variables have the
+same initial default of 0.5in.

+ 31 - 0
pikchr.mod/pikchr/doc/locattr.md

@@ -0,0 +1,31 @@
+# location-attribute
+
+A *location-attribute* is an attribute used to assign a location to
+a block object (box, circle, cylinder, dot, ellipse, file, oval, or text).
+If a *location-attribute* appears on a line object (arc, arrow, line, move,
+or spline) an error is issued and processing stops.
+
+There are three forms:
+
+  *  **at** *position*
+  *  **with** *edgename* **at** *position*
+  *  **with** *dot-edgename* **at** *position*
+
+The second and third forms are equivalent and only differ in the
+the "." that comes before the edge name.  PIC does not recognize
+the second form, only the first and third.
+
+If the "`with`" clause is omitted, then "`with center`" or
+(equivalently) "`with .c`" is assumed.
+
+This attribute causes the block object to be positioned so that
+its *edgename* corner is at *position*.
+
+If a block object omits this attribute, then a default location-attribute
+is used as follows:
+
+  *  **with .begin at previous.end**
+  *  **with .c at (0,0)**
+
+The first default form is what is normally used.  The second default
+form is only used if there is no "previous" object.

+ 69 - 0
pikchr.mod/pikchr/doc/macro.md

@@ -0,0 +1,69 @@
+# Macros
+
+A macro is created using a "`define`" statement:
+
+~~~ pikchr toggle
+$r = 0.2in
+linerad = 0.75*$r
+linewid = 0.25
+
+# Start and end blocks
+#
+box "define-statement" bold fit
+line down 50% from last box.sw
+START: dot rad 250% color black
+X0: last.e
+move right 3.2in
+END: box wid 5% ht 25% fill black
+X9: last.w
+
+# The main rule
+#
+arrow from X0 right 2*linerad+arrowht
+oval "\"define\"" fit
+arrow
+oval "MACRONAME" fit
+arrow
+oval "{...}" fit
+line right to X9
+~~~
+
+A define statement consists of the keyword "`define`" followed by
+an identifier that is the name of the macro and then the body of
+the macro contained within (possibly nested) curly braces.
+
+After a macro is defined, the body of the macro is substituted in
+place of any subsquent occurrence of the identifier that is the
+macro name.  The macro name can occur anywhere.  The substitution
+is performed by the lexical analyzer, before tokens are identified
+and sent into the parser.  Note this distinction:  The "`define`"
+statement used to create a new macro is recognized by the parser,
+but the expansion of the macro is subsequent text happens in the
+lexical analyzer.
+
+## Parameters
+
+The invocation of a macro can be followed immediately by a
+parenthesized list of parameters.  The open-parenthesis must immediately
+follow the macro name with no intervening whitespace.  Parameters are
+comma-separated.  There can be at most 9 parameters.
+
+When parameters are present, they are substituted in the macro body
+in place of "`$1`", "`$2`", ..., "`$9`" in the macro body.  If
+"$N" (for N between 1 and 9) occurs in the macro body but there are
+fewer than N parameters, then the "$N" is omitted.
+
+## Nested Macros
+
+Macros can be nested up to a maximum depth that is determined at
+compile-time.  (The current limit is 10.)
+
+Arguments to nested macros can be arbitrary text, or a single "$N"
+parameter, but not both.
+
+## Macros cannot be undefined or redefined
+
+Once created, a macro cannot be redefined.  If you attempt to redefine
+a macro by providing a second "`define`" statement with the same macro
+name, the macro name will be replaced by the previous macro body definition
+during lexical analysis, likely resulting in a syntax error.

+ 19 - 0
pikchr.mod/pikchr/doc/newpropval.md

@@ -0,0 +1,19 @@
+# new-property-value
+
+When setting the value of certain numeric properties (like
+"`width`" and "`radius`") you can specify either an absolute
+amount, or a percentage relative to the current setting.
+
+So, for example, you can say:
+
+~~~~~
+    box width 2.3cm
+~~~~~
+
+To create a box with a width of 2.3 centimeters - an absolute amount.
+Or, if the current "`boxwid`" variable value is 2.0cm, then you could
+do the same by saying:
+
+~~~~~
+    box width 115%
+~~~~~

+ 89 - 0
pikchr.mod/pikchr/doc/numprop.md

@@ -0,0 +1,89 @@
+# numeric-property
+
+There are really only four numeric properties:
+
+  * width
+  * height
+  * radius
+  * thickness
+
+The width and height are the size of most objects.  The radius is used
+to set the size of circles.  The thickness value is the width of lines used to
+draw each object.  The other property names are just aliases for these
+four:
+
+  * wid &rarr; an abbreviation for "width"
+  * ht &rarr; an abbreviation for "height"
+  * rad &rarr; an abbreviation for "radius"
+  * diameter &rarr;  twice the radius
+
+## Radius Of A "box" Object
+
+By default, boxes have a radius of 0.  But if you assign a positive
+radius to a box, it causes the box to have rounded corners:
+
+~~~~~ pikchr center
+box "radius 0"
+move
+box "radius 5px" rad 5px
+move
+box "radius 20px" rad 20px
+~~~~~
+
+## Dimensions Of A "circle" Object
+
+If you change any of the "width", "height", "radius", or "diameter" of
+a circle, the other three values are set automatically.
+
+## Radius Of A "cylinder" Object
+
+The "radius" of a "cylinder" object is the semiminor axis of the ellipse
+that forms the top of the "cylinder".
+
+~~~~~ pikchr center
+C: cylinder
+line thin left from C.nw - (2mm,0)
+line thin left from C.nw - (2mm,C.radius)
+arrow <- from 3/4<first line.start,first line.end> up 30%
+arrow <- from 3/4<2nd line.start,2nd line.end> down 30%
+text "radius" above at end of 1st arrow
+~~~~~
+
+Some examples:
+
+~~~~~ pikchr center
+cylinder "radius 50%" rad 50%
+move
+cylinder "radius 100%" rad 100%
+move
+cylinder "radius 200%" "height 200%" rad 200% ht 200%
+~~~~~
+
+
+## Radius Of A "file"
+
+For a "file" object, the radius is the amount by which the upper right
+corner is folded over.
+
+~~~~~ pikchr center
+F: file
+line thin from 2mm right of (F.e,F.n) right 75%
+line thin from F.rad below start of previous right 75%
+arrow <- from 3/4<first line.start,first line.end> up 30%
+arrow <- from 3/4<2nd line.start,2nd line.end> down 30%
+text "radius" above at end of 1st arrow
+~~~~~
+
+## Radius Of A "line"
+
+Setting a radius on a line causes the corners to be rounded by that
+amount.
+
+~~~~~ pikchr center
+line go 2cm heading 40 then 4cm heading 165 then 1cm heading 280\
+   "radius" "0"
+move to 3cm right of previous.start
+line same "radius" "15px" rad 15px
+move to 3cm right of previous.start
+line same  "radius" "30px" rad 30px
+~~~~~

+ 65 - 0
pikchr.mod/pikchr/doc/ovalobj.md

@@ -0,0 +1,65 @@
+# Oval objects
+
+An oval is a box in which the narrow ends are formed by semicircles.
+If the height is less than the width (the default) then the semicircles
+are on the left and right.  If the width is less than the height, then the
+semicircles are on the top and bottom:
+
+~~~~ pikchr indent
+A: oval thick
+X0: line thin color gray left 70% from 2mm left of (A.w,A.n)
+X1: line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<X0,X1>)
+line thin color gray from previous text.n up until even with X0 ->
+line thin color gray from previous text.s down until even with X1 ->
+X2: line thin color gray down 50% from 2mm below (A.w,A.s)
+X3: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X2,X3>,6/8<X2.start,X2.end>)
+line thin color gray from previous text.w left until even with X2 ->
+line thin color gray from previous text.e right until even with X3 ->
+
+A: oval thick wid A.ht ht A.wid at 2.0*A.wid right of A
+X0: line thin color gray left 70% from 2mm left of (A.w,A.n)
+X1: line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<X0,X1>)
+line thin color gray from previous text.n up until even with X0 ->
+line thin color gray from previous text.s down until even with X1 ->
+X2: line thin color gray down 50% from 2mm below (A.w,A.s)
+X3: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X2,X3>,6/8<X2.start,X2.end>)
+line thin color gray from previous text.w left until even with X2 ->
+line thin color gray from previous text.e right until even with X3 ->
+~~~~
+
+An oval works like a [box](./boxobj.md) in which the radius is
+set to half the minimum of the height and width.  An oval where the
+width and height are the same is a [circle](./circleobj.md)
+
+
+## Boundary points:
+
+~~~~ pikchr indent
+A: oval thin
+dot ".c" below at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+
+A: oval thin  wid A.ht ht A.wid at 2.0*A.wid right of A
+dot ".c" below at A
+dot ".n" above at A.n
+dot " .ne" ljust above at A.ne
+dot " .e" ljust at A.e
+dot " .se" ljust below at A.se
+dot ".s" below at A.s
+dot ".sw " rjust below at A.sw
+dot ".w " rjust at A.w
+dot ".nw " rjust above at A.nw
+
+
+~~~~

+ 183 - 0
pikchr.mod/pikchr/doc/pathattr.md

@@ -0,0 +1,183 @@
+# path-attribute
+
+A *path-attribute* is used provide the origin and direction of a line
+object (arc, arrow, line, move, or spline).  It is an error to use a
+*path-attribute* on
+a block object (box, circle, cylinder, dot, ellipse, file, oval, or text).
+
+There are seven forms:
+
+  *  **from** *position*
+  *  **then**? **to** *position*
+  *  **then**? **go**? *direction* *distance*?
+  *  **then**? **go**? *direction* **until**? **even with** *place*
+  *  (**then**|**go**) *distance*? **heading** *compass-angle*
+  *  (**then**|**go**) *distance*? *compass-direction*
+  *  **close**
+
+The "`from`" attribute is used to assign the starting location
+of the line object (its ".start" value).  The other six forms
+(collectively called "to" forms) assign
+intermediate vertexes or the end point (.end).   If the "`from`"
+is omitted, then "`from previous.end`" is assumed, or if there
+is no previous object, "`from (0,0)`".   If no "to" forms are
+provided then a single movement in the current layout direction
+by either the "linewid" or "lineht" (depending on layout direction)
+is used.
+
+The "from" can occur
+either before or after the various "to" subclauses.  That does not
+matter.  But the order is important for the various "to" subclauses.
+
+If there are two consecutive *direction* clauses (*direction* is
+always one of "`up`", "`down`", "`left`", or "`right`") then
+the two will be combined to specify a single line segment.
+Hence, the following are equivalent:
+
+
+  *  ... **right 4cm up 3cm** ...
+  *  ... **go 5cm heading 53.13010235** ...
+
+~~~ pikchr
+leftmargin = 1cm
+A1: arrow thick right 4cm up 3cm
+dot at A1.start
+X1: line thin color gray from (0,-3mm) down 0.4cm
+X2: line same from (4cm,-3mm) down 0.4cm
+arrow thin color gray from X1 to X2 "4cm" above
+X3: line same from (4cm+3mm,0) right 0.4cm
+X4: line same from (4cm+3mm,3cm) right .4cm
+arrow thin color gray from X3 to X4 "3cm" aligned above
+X5: line same from A1.start go 4mm heading 90+53.13010235
+X6: line same from A1.end go 4mm heading 90+53.13010235
+arrow thin color gray from X5 to X6 "5cm" below aligned
+line same from (0,1cm) up 1cm
+spline -> from 1.5cm heading 0 from A1.start \
+   to 1.5cm heading 10 from A1.start \
+   to 1.5cm heading 20 from A1.start \
+   to 1.5cm heading 30 from A1.start \
+   to 1.5cm heading 40 from A1.start \
+   to 1.5cm heading 53.13 from A1.start \
+   thin color gray "53.13&deg;" aligned center small
+~~~
+
+If two separate movements are desired, one 4cm right and another 3cm up,
+then the "right" and "up" subphrases must be separated by the "`then`" keyword:
+
+  *  ... **right 4cm then up 3cm** ...
+
+~~~ pikchr
+leftmargin = 1cm
+A1: arrow thick right 4cm then up 3cm
+dot at A1.start
+X1: line thin color gray from (0,-3mm) down 0.4cm
+X2: line same from (4cm,-3mm) down 0.4cm
+arrow thin color gray from X1 to X2 "4cm" above
+X3: line same from (4cm+3mm,0) right 0.4cm
+X4: line same from (4cm+3mm,3cm) right .4cm
+arrow thin color gray from X3 to X4 "3cm" aligned above
+~~~
+
+## The "`until even with`" subclause
+
+The "until even with" clause is a Pikchr extension (it does not exist
+in PIC) that makes it easier to specify paths that follow a
+"Manhattan geometry" (lines are axis-aligned) or that negotiate around
+obstacles.  The phrase:
+
+>  go *direction* until even with *position*
+
+Means to continue the line in the specified *direction* until the
+coordinate being changed matches the corresponding coordinate in
+*position* If the line is going up or down, then it continues until
+the Y coordinate matches the Y coordinate of *position*.  If the line
+is going left or right, then it continues until
+the X coordinate matches the X coordinate of *position*.
+
+For example, suppose in the diagram below that we want to draw an arrow 
+that begins on Origin.s and ends on Destination.s but goes around
+the Obstacle oval, clearing it by at least one centimeter.
+
+~~~ pikchr toggle
+box "Origin"
+Obstacle: oval ht 300% wid 30% with .n at linewid right of Origin.ne;
+box "Destination" with .nw at linewid right of Obstacle.n
+line invis from 1st oval.s to 1st oval.n "Obstacle" aligned
+~~~
+
+The arrow might look like this:
+
+~~~
+   arrow from Origin.s \
+      down until even with 1cm below Obstacle.s \
+      then right until even with Destination.s \
+      then to Destination.s
+~~~
+
+And we have (annotations added):
+
+~~~ pikchr toggle
+box "Origin"
+Obstacle: oval ht 300% wid 30% with .n at linewid right of Origin.ne;
+box "Destination" with .nw at linewid right of Obstacle.n
+line invis from 1st oval.s to 1st oval.n "Obstacle" aligned
+X: \
+   arrow from Origin.s \
+      down until even with 1cm below Obstacle.s \
+      then right until even with Destination.s \
+      then to Destination.s
+
+line invis color gray from X.start to 2nd vertex of X \
+    "down until even with" aligned small \
+    "1cm below Obstacle.s" aligned small
+line invis color gray from 2nd vertex of X to 3rd vertex of X \
+    "right until even with Destination.s" aligned small above
+line invis color gray from 3nd vertex of X to 4rd vertex of X \
+    "to Destination.s" aligned small above
+
+# Evidence that the alternative arrow is equivalent:
+assert( 2nd vertex of X == (Origin.s, 1cm below Obstacle.s) )
+assert( 3nd vertex of X == (Destination.s, 1cm below Obstacle.s) )
+~~~
+
+The "**(** *position* **,** *position* **)**" syntax can be used
+in a similar way.  The "**(** *position* **,** *position* **)**"
+syntax means a point whose X coordinate is taken from the first
+position and whose Y coordinate is taken from the second position.
+So the line around the obstacle could have been written like this:
+
+~~~ 
+   arrow from Origin.s \
+     to (Origin.s, 1cm below Obstacle.s) \
+     then to (Destination.s, 1cm below Obstacle.s) \
+     then to Destination.s
+~~~
+
+However, we believe the "`until even with`" notation is easier.
+
+## The "`close`" subclause
+
+The "`close`" attribute closes a multi-segment path so that it
+forms a polygon.  When "`close`" is used, the "`.end`" point of the
+object is no longer the last vertex in the path but is instead
+one of "`.e`", "`.s`", "`.w`", or "`.n`" according to the current
+layout direction, as it would be for a block object.
+
+The following diagram illustrates this behavior.  The "`.end`" of
+each line is tagged with a red dot.  The line that uses "`close`"
+has its end at the "`.e`" point of the bounding box since the
+layout direction is "right".  The line without "`close`" has its
+"`.end`" at the last vertex of the line.
+
+~~~ pikchr toggle
+line right 2cm then down .5cm then up 1cm right 1cm \
+   then up 1cm left 1cm then down .5cm then left 2cm \
+   close "with 'close'"
+dot color red at last line.end
+
+move to 2.5cm south of last line.start
+line right 2cm then down .5cm then up 1cm right 1cm \
+   then up 1cm left 1cm then down .5cm then left 2cm \
+   then down 1cm "without 'close'"
+dot color red at last line.end
+~~~

+ 217 - 0
pikchr.mod/pikchr/doc/place.md

@@ -0,0 +1,217 @@
+# place
+
+A *place* is a specific point on an object.
+A *[position](./position.md)* is a more general concept that means
+any X,Y coordinate on the drawing.  This page is about *place*.
+
+  *  *object*
+  *  *object* *dot-edgename*
+  *  *edgename* **of** *object*
+  *  ORDINAL **vertex of** *object*
+
+Every object has at least 9 places.  Line objects have additional
+places for each internal vertex.   Most places are on the boundary
+of the object, though ".center" or ".c" is in the middle.  The
+".start" and ".end" places might be in the interior of the object
+for the case of lines.
+Some places may overlap.
+Places usually have multiple names.
+There are 22 different place names to refer to the 9 potentially
+distinct places.
+
+For a block object, when the layout direction is "right", we have:
+
+~~~ pikchr
+B: box thick thick color blue
+
+circle ".n" fit at 1.5cm heading 0 from B.n
+    arrow thin from previous to B.n chop
+circle ".north" fit at 3cm heading 15 from B.north
+    arrow thin from previous to B.north chop
+circle ".t" fit at 1.5cm heading 30 from B.t
+    arrow thin from previous to B.t chop
+circle ".top" fit at 3cm heading -15 from B.top
+    arrow thin from previous to B.top chop
+circle ".ne" fit at 1cm ne of B.ne; arrow thin from previous to B.ne chop
+circle ".e" fit at 2cm heading 50 from B.e; arrow thin from previous to B.e chop
+circle ".right" fit at 3cm heading 75 from B.right
+    arrow thin from previous to B.right chop
+circle ".end&sup1;" fit at 3cm heading 100 from B.end
+    arrow thin from previous to B.end chop
+circle ".se" fit at 1cm heading 110 from B.se
+    arrow thin from previous to B.se chop
+circle ".s" fit at 1.5cm heading 180 from B.s
+    arrow thin from previous to B.s chop
+circle ".south" fit at 3cm heading 195 from B.south
+    arrow thin from previous to B.south chop
+circle ".bot" fit at 1.8cm heading 215 from B.bot
+    arrow thin from previous to B.bot chop
+circle ".bottom" fit at 2.7cm heading 160 from B.bottom
+    arrow thin from previous to B.bottom chop
+circle ".sw" fit at 1cm sw of B.sw; arrow thin from previous to B.sw chop
+circle ".w" fit at 2cm heading 270 from B.w
+    arrow thin from previous to B.w chop
+circle ".left" fit at 3cm heading 180+75 from B.left
+    arrow thin from previous to B.left chop
+circle ".start&sup1;" fit at 2.5cm heading 295 from B.start
+    arrow thin from previous to B.start chop
+circle ".nw" fit at 1cm nw of B.nw; arrow thin from previous to B.nw chop
+circle ".c" fit at 2.5cm heading -25 from B.c
+    line thin from previous to 0.5<previous,B.c> chop
+    arrow thin from previous.end to B.c
+circle ".center" fit at 3.6cm heading 180-44 from B.center
+    line thin from previous to 0.5<previous,B.center> chop
+    arrow thin from previous.end to B.center
+circle "&lambda;" fit at 2.5cm heading 250 from B
+    line from previous to 0.5<previous,B> chop
+    arrow thin from previous.end to B
+~~~
+
+The diagram above is for a box with square corners.  The non-center
+places for other block objects are always on the boundary of the
+object.  Thus for an ellipse:
+
+~~~ pikchr
+B: ellipse thick thick color blue
+
+circle ".n" fit at 1.5cm heading 0 from B.n
+    arrow thin from previous to B.n chop
+circle ".north" fit at 3cm heading 15 from B.north
+    arrow thin from previous to B.north chop
+circle ".t" fit at 1.5cm heading 30 from B.t
+    arrow thin from previous to B.t chop
+circle ".top" fit at 3cm heading -15 from B.top
+    arrow thin from previous to B.top chop
+circle ".ne" fit at 1cm ne of B.ne; arrow thin from previous to B.ne chop
+circle ".e" fit at 2cm heading 50 from B.e; arrow thin from previous to B.e chop
+circle ".right" fit at 3cm heading 75 from B.right
+    arrow thin from previous to B.right chop
+circle ".end&sup1;" fit at 3cm heading 100 from B.end
+    arrow thin from previous to B.end chop
+circle ".se" fit at 1cm heading 110 from B.se
+    arrow thin from previous to B.se chop
+circle ".s" fit at 1.5cm heading 180 from B.s
+    arrow thin from previous to B.s chop
+circle ".south" fit at 3cm heading 195 from B.south
+    arrow thin from previous to B.south chop
+circle ".bot" fit at 1.8cm heading 215 from B.bot
+    arrow thin from previous to B.bot chop
+circle ".bottom" fit at 2.7cm heading 160 from B.bottom
+    arrow thin from previous to B.bottom chop
+circle ".sw" fit at 1cm sw of B.sw; arrow thin from previous to B.sw chop
+circle ".w" fit at 2cm heading 270 from B.w
+    arrow thin from previous to B.w chop
+circle ".left" fit at 3cm heading 180+75 from B.left
+    arrow thin from previous to B.left chop
+circle ".start&sup1;" fit at 2.5cm heading 295 from B.start
+    arrow thin from previous to B.start chop
+circle ".nw" fit at 1cm nw of B.nw; arrow thin from previous to B.nw chop
+circle ".c" fit at 2.5cm heading -25 from B.c
+    line thin from previous to 0.5<previous,B.c> chop
+    arrow thin from previous.end to B.c
+circle ".center" fit at 3.6cm heading 180-44 from B.center
+    line thin from previous to 0.5<previous,B.center> chop
+    arrow thin from previous.end to B.center
+circle "&lambda;" fit at 2.5cm heading 250 from B
+    line from previous to 0.5<previous,B> chop
+    arrow thin from previous.end to B
+~~~
+
+The "&lambda;" case refers to when a bare object name is used.
+A bare object name is the same as referring to the center of
+the object.
+
+In the previous two diagrams, the ".start" and ".end" objects
+are marked with "&sup1;" because
+the location of ".start" and ".end" varies 
+according to the layout direction.  The previous diagrams assumed
+a layout direction of "right".  For other layout directions, we have:
+
+<blockquote>
+<table border="1" cellpadding="10px" cellspacing="0">
+<tr><th>Layout Direction<th>.start<th>.end
+<tr><td>right<td>.w<td>.e
+<tr><td>down<td>.n<td>.s
+<tr><td>left<td>.e<td>.w
+<tr><td>up<td>.s<td>.n
+</table></blockquote>
+
+For a line, the place names refer to a bounding box that
+encloses the line:
+
+~~~ pikchr
+B: line thick thick color blue go 0.8 heading 350 then go 0.4 heading 120 \
+    then go 0.5 heading 35 \
+    then go 1.2 heading 190  then go 0.4 heading 340 "+"
+
+   line thin dashed color gray from B.nw to B.ne to B.se to B.sw close
+
+circle ".n" fit at 1.5cm heading 0 from B.n
+    arrow thin from previous to B.n chop
+circle ".north" fit at 3cm heading 15 from B.north
+    arrow thin from previous to B.north chop
+circle ".t" fit at 1.5cm heading 30 from B.t
+    arrow thin from previous to B.t chop
+circle ".top" fit at 3cm heading -15 from B.top
+    arrow thin from previous to B.top chop
+circle ".ne" fit at 1cm ne of B.ne; arrow thin from previous to B.ne chop
+circle ".e" fit at 2cm heading 50 from B.e; arrow thin from previous to B.e chop
+circle ".right" fit at 3cm heading 75 from B.right
+    arrow thin from previous to B.right chop
+circle ".end" fit at 2cm heading 120 from B.end
+    arrow thin from previous to B.end chop
+circle ".se" fit at 1cm heading 170 from B.se
+    arrow thin from previous to B.se chop
+circle ".s" fit at 1.5cm heading 180 from B.s
+    arrow thin from previous to B.s chop
+circle ".south" fit at 3cm heading 195 from B.south
+    arrow thin from previous to B.south chop
+circle ".bot" fit at 1.8cm heading 215 from B.bot
+    arrow thin from previous to B.bot chop
+circle ".bottom" fit at 2.7cm heading 160 from B.bottom
+    arrow thin from previous to B.bottom chop
+circle ".sw" fit at 1cm sw of B.sw; arrow thin from previous to B.sw chop
+circle ".w" fit at 2cm heading 300 from B.w
+    arrow thin from previous to B.w chop
+circle ".left" fit at 3cm heading 280 from B.left
+    arrow thin from previous to B.left chop
+circle ".start" fit at 2.5cm heading 265 from B.start
+    arrow thin from previous to B.start chop
+circle ".nw" fit at 1cm nw of B.nw; arrow thin from previous to B.nw chop
+circle ".c" fit at 2.5cm heading -15 from B.c
+    line thin from previous to 0.5<previous,B.c> chop
+    arrow thin from previous.end to B.c
+circle ".center" fit at 3.3cm heading 110 from B.center
+    line thin from previous to 0.5<previous,B.center> chop
+    arrow thin from previous.end to B.center
+circle "&lambda;" fit at 1.7cm heading 250 from B
+    line from previous to 0.5<previous,B> chop
+    arrow thin from previous.end to B
+~~~
+
+The ".start" of a line always refers to its first vertex.
+The ".end" is usually the last vertex, except when the "`close`"
+keyword is used, in which case the ".end" is the same as
+".e", ".s", ".w", or ".n" depending on layout direction,
+just like a block object.
+
+The vertexes of a line object are also places:
+
+~~~ pikchr
+B: line -> thick color blue go 0.8 heading 350 then go 0.4 heading 120 \
+    then go 0.5 heading 35 \
+    then go 1.2 heading 190  then go 0.4 heading 340
+
+oval "1st vertex" fit at 2cm heading 250 from 1st vertex of B
+    arrow thin from previous to 1st vertex of B chop
+oval "2nd vertex" fit at 2cm west of 2nd vertex of B
+    arrow thin from previous to 2nd vertex of B chop
+oval "3rd vertex" fit at 2cm north of 3rd vertex of B
+    arrow thin from previous to 3rd vertex of B chop
+oval "4th vertex" fit at 2cm east of 4th vertex of B
+    arrow thin from previous to 4th vertex of B chop
+oval "5th vertex" fit at 2cm east of 5th vertex of B
+    arrow thin from previous to 5th vertex of B chop
+oval "6th vertex" fit at 2cm heading 200 from 6th vertex of B
+    arrow thin from previous to 6th vertex of B chop
+~~~

+ 77 - 0
pikchr.mod/pikchr/doc/position.md

@@ -0,0 +1,77 @@
+# position
+
+A *position* is a point on the SVG canvas.  A *[place](./place.md)* is
+a specific position associated with an object.  Every *place* is a *position*,
+but not every *position* is a *place*.  This page is about *position*.
+
+  *  *expr* **,** *expr*
+  *  *place*
+  *  *place* **+** *expr* **,** *expr*
+  *  *place* **-** *expr* **,** *expr*
+  *  *place* **+ (** *expr* **,** *expr* **)**
+  *  *place* **- (** *expr* **,** *expr* **)**
+  *  **(** *position* **,** *position* **)**
+  *  **(** *position* **)**
+  *  *fraction* **of the way between** *position* **and** *position*
+  *  *fraction* **way between** *position* **and** *position*
+  *  *fraction* **between** *position* **and** *position*
+  *  *fraction* **<** *position* **,** *position* **>**
+  *  *distance* *which-way-from* *position*
+
+## Absolute versus Place-relative Positions
+
+One form of a position is an (X,Y) coordinate pair.  This works, but
+its use is discouraged.  It is better to use positions that are 
+either a *[place](./place.md)* or are derived from one or more places.
+
+## The "**(** *position* **,** *position* **)**" Form
+
+A place of the form "(pos1,pos2)" where pos1 and pos2 are other positions
+means use the X coordinate from pos1 and the Y coordinate from pos2.
+
+~~~ pikchr
+leftmargin = 1cm;
+P1: dot; text "P1" with .s at 2mm above P1
+P2: dot at P1+(2cm,-2cm); text "P2" with .s at 2mm above P2
+dot at (P1,P2); text "(P1,P2)" with .s at 2mm above last dot
+dot at (P2,P1); text "(P2,P1)" with .s at 2mm above last dot
+~~~
+
+## "*fraction* **of the way between**" Forms
+
+All of these syntactic forms of position are the same:
+
+  *  *fraction* **of the way between** *position* **and** *position*
+  *  *fraction* **way between** *position* **and** *position*
+  *  *fraction* **between** *position* **and** *position*
+  *  *fraction* **<** *position* **,** *position* **>**
+
+The last form is the most cryptic, but it is also the most compact
+and hence ends up being the most often used.
+
+In all cases *fraction* is an expression that usually evaluates to between 0.0
+and 1.0.  The resulting position is that fraction along a line that
+connects the first *position* to the second *position*.
+
+The *fraction* can be less than 0.0 or greater than 1.0, in which case
+the point is on the extended line that connects the two positions.
+
+~~~ pikchr
+P1: dot; text "P1" with .s at 2mm above P1
+P2: dot at P1+(4cm,1.5cm); text "P2" with .s at 2mm above P2
+line thin color gray dotted from -.5<P1,P2> to 1.5<P1,P2>
+dot at 3/4<P1,P2>; text "3/4<P1,P2>" at (last dot,P1)
+   arrow thin color gray from last text.n to 1mm south of last dot
+dot at -0.25 of the way between P1 and P2
+   text "-0.25 of the way between P1 and P2" at (last dot,P2)
+   arrow thin color gray from last text.s to 1mm north of last dot
+~~~
+
+## "*position* *which-way-from* *position*" Forms
+
+It is very common to specify a position as an offset from some other
+position using this format.  Some examples:
+
+  *  1cm below Obstacle.s
+  *  0.5*linewid left of C0.w
+  *  dist(C2,C3) heading 30 from C2

+ 43 - 0
pikchr.mod/pikchr/doc/purpose.md

@@ -0,0 +1,43 @@
+# The Intended Scope And Purpose Of Pikchr
+
+Pikchr is a specialized tool designed for one specific purpose:
+
+  *  Pikchr generates diagrams for technical documentation written in
+     Markdown or similar markup languages using an enduring language that
+     is easy for humans to read and maintain using a generic text
+     editor.
+
+To this end, Pikchr diagrams are designed to be:
+
+  *  Cross platform &rarr; Pikchr is not tied to any particular
+     computer architecture or operating system.  The current implementation
+     is a single file of generic C code using no external resources apart
+     from the standard C library.
+
+  *  Simple, well-defined, and easy-to-learn syntax.
+
+  *  Enduring &rarr; The diagram source text should be easily readable,
+     editable, and understandable by people not yet born.  Pikchr is based
+     on the [PIC][1] language, which was developed in the early 1980s.
+
+[1]: https://en.wikipedia.org/wiki/Pic_language
+
+What Pikchr is <u>not</u>:
+
+  *  Pikchr is not intended for marketing graphics.  Pikchr
+     strives to present information in a dry and mathematical style.
+     The objective of Pikchr is to convey truth, not feeling.
+
+  *  Pikchr is not intended for generating charts and graphs.  It could
+     perhaps be used for this.  One might propose extensions to make it more
+     suitable for this.  But that is not its current purpose.
+
+  *  Pikchr is not intended to generate CAD/CAM images.  The rendered
+     diagrams are close enough to display concepts, but do not have the
+     pixel-perfect precision required for CAD/CAM.
+
+  *  Pikchr is not intended as a replacement for point-and-click diagrams
+     creation software.  Pikchr is to point-and-click systems as
+     Markdown is to MS-Word or Google-Docs.  Point-and-click interfaces
+     have their place.  But so do text-based systems such as Markdown and
+     Pikchr.

+ 86 - 0
pikchr.mod/pikchr/doc/sqlitesyntax.md

@@ -0,0 +1,86 @@
+# How Pikchr Generates the SQLite Syntax Diagrams
+
+Beginning with SQLite version 3.34.0, the graphical
+[syntax diagrams][1] in the SQLite documentation are SVGs generated
+using Pikchr.  In prior versions of SQLite, the diagrams were GIF images generated
+using a fiddly processing chain based on Tcl/Tk, Ghostscript, and ImageMagick.
+
+[1]: https://sqlite.org/syntaxdiagrams.html
+
+## Advantages to the New Approach
+
+  *  The SVG syntax diagrams are embedded in the text of the
+     HTML documentation pages rather than separately-loaded
+     GIF images.  That speeds page loads due to fewer HTTP round-trips to the server to fetch
+     resources, and it makes saved copies of the resulting HTML documentation pages
+     self-contained, simplifying later off-line viewing.
+
+  *  SVG is resolution independent, so that diagrams look better on
+     high-DPI devices such as smartphones and tablets.
+
+  *  The Pikchr source text promises to be easier to maintain than
+     the prior Tcl/Tk+Ghostscript+ImageMagick toolchain.
+
+  *  The scripts used to convert the documentation source text into
+     display-ready HTML can now be cross-platform.  The prior approach
+     only seemed to work on Linux, and then only if the right utilities
+     were installed.
+
+  *  Pikchr provides additional flexibility in the formatting of
+     syntax digrams, so that the diagrams can be made easier to read
+     and understand.
+
+## How It Works
+
+The SQLite documentation is generated using [a Tcl script][2] from
+source documents. The script evaluates Tcl code embedded into the
+source files between `<tcl>`...`</tcl>` tags, extends HTML with
+enhanced hyperlinks and formatting features, and outputs the result
+as standard HTML.
+
+[2]: https://sqlite.org/docsrc/file/wrap.tcl
+
+Each syntax diagram is a file in the
+[art/syntax/][3] subdirectory of the [documentation source repository][4].
+You can click on [any of the Pikchr source files][3] to see the corresponding
+diagram.  Click on the "Text" submenu option to see the original Pikchr
+source text rather than the rendered SVG.
+
+[3]: https://www.sqlite.org/docsrc/dir/art/syntax?ci=trunk
+[4]: https://www.sqlite.org/docsrc/doc/trunk/README.md
+
+### Pikchr as a Tcl Extension
+
+The [`pikchr.c` source file][src] can be compiled into a Tcl extension by
+adding the `-DPIKCHR_TCL` compile-time option.  As a Tcl extension,
+Pikchr provides a single new Tcl command named "`pikchr`" which takes
+a single argument: the Pikchr source text.  The `pikchr` command returns
+a Tcl list of three elements which are:
+
+   1.  The SVG output text
+   2.  The width of the output in pixels
+   3.  The height of the output in pixels
+
+As with ordinary Pikchr, a negative width is returned if the input text
+contains an error, and the output text is the error message.
+
+[src]: https://pikchr.org/home/file/pikchr.c
+
+### Automatic Insertion of Diagrams into Documentation
+
+Within the SQLite documentation source text, markup of the following
+form causes the named Pikchr syntax diagram to be loaded, converted
+into SVG, and the output SVG added to the documentation under construction:
+
+~~~~
+   <tcl>
+   RecursiveBubbleDiagram expr
+   </tcl>
+~~~~
+
+The RecursiveBubbleDiagram Tcl procedure uses the "`pikchr`" Tcl command
+to convert the Pikchr source text into SVG and inserts that SVG.  The
+command also looks for other diagrams that are referenced by that diagram
+and loads them as well, together with appropriate JavaScript to cause the
+sub-diagrams to be initially hidden, but to expand when the reader clicks on
+the appropriate links.

+ 180 - 0
pikchr.mod/pikchr/doc/stmt.md

@@ -0,0 +1,180 @@
+# statement
+
+## Rules
+
+  * *object-definition*
+  * LABEL **:** *object-definition*
+  * LABEL **:** *place*
+  * *direction*
+  * VARIABLE *assignment-op* *expr*
+  * **define** VARIABLE CODEBLOCK
+  * **print** *print-argument* (**,** *print-argument*)\*
+  * **assert (** *expr* **==** *expr* **)**
+  * **assert (** *position* **==** *position* **)**
+
+
+## Labels
+
+A label can be attached to either an *object* or a *place* so that
+the object or place can be more easily referenced by subsequent statements.
+Labels always begin with an upper-case ASCII character.
+
+Labels do not have to be unique.  When there are two or more
+labels with the same name, the later one takes precedence.
+This allows a label to be effectively redefined.  New labels do not
+come into existence until after the object or place to which they are
+attached has been completely parsed and analyzed.  This allows labels
+to be redefined in terms of themselves.  Consider an example:
+
+~~~~~
+/* 01 */        down
+/* 02 */  Root: dot "First \"Root\"" above color red
+/* 03 */        circle wid 50% at Root + (1.5cm, -1.5cm)
+/* 04 */        arrow dashed from previous to Root chop
+/* 05 */  Root: 3cm right of Root   // Move the location of Root 3cm right
+/* 06 */        arrow from last circle to Root chop
+/* 07 */        dot "Second \"Root\"" above color blue at Root
+~~~~~
+
+Line 05 redefines Root in terms of itself.
+In the rendering below, you can see that the dashed arrow drawn before
+Root was redefined goes to the original Root, but the solid arrow drawn
+afterwards goes to the revised location for Root.
+
+~~~~~ pikchr center toggle
+/* 01 */        down
+/* 02 */  Root: dot "First \"Root\"" above color red
+/* 03 */        circle wid 50% at Root + (1.5cm, -1.5cm)
+/* 04 */        arrow dashed from previous to Root chop
+/* 05 */  Root: 3cm right of Root   // Move the location of Root 3cm right
+/* 06 */        arrow from last circle to Root chop
+/* 07 */        dot "Second \"Root\"" above color blue at Root
+~~~~~
+
+## Variables
+
+Variable names begin with a lower-case ASCII letter or with "`$`"
+or with "`@`".  The $- and @- variable names are a Pikchr extension
+designed to help prevent collisions between variable names and the
+(numerous) keywords in the Pikchr language.
+
+Pikchr has built-in variables as follows:
+
+>
+| Variable Name | &nbsp;&nbsp; Initial Value &nbsp;&nbsp; |: Purpose         |
+------------------------------------------------------------------------------
+| arcrad        |: 0.250 :| Default arc radius                               |
+| arrowhead     |: 2.000 :| *Not used by Pikchr*                             |
+| arrowht       |: 0.080 :| Length of arrowheads                             |
+| arrowwid      |: 0.060 :| Width of arrowheads                              |
+| boxht         |: 0.500 :| Default height of "box" objects                  |
+| boxrad        |: 0.000 :| Default corner radius for "box" objects          |
+| boxwid        |: 0.750 :| Default width for "box" objects                  |
+| charht        |: 0.140 :| Average height of a character                    |
+| charwid       |: 0.080 :| Average width of a character                     |
+| circlerad     |: 0.250 :| Default radius for "circle" objects              |
+| color         |: 0.000 :| Default foreground color                         |
+| cylht         |: 0.500 :| Default height for "cylinder" objects            |
+| cylrad        |: 0.075 :| Default minor axis for ellipses in a cylinder    |
+| cylwid        |: 0.750 :| Default width of a "cylinder" object             |
+| dashwid       |: 0.050 :| Default width of dashes in dashed lines          |
+| dotrad        |: 0.015 :| Default radius for a "dot" object                |
+| ellipseht     |: 0.500 :| Default height for "ellipse" objects             |
+| ellipsewid    |: 0.750 :| Default width for "ellipse" objects              |
+| fileht        |: 0.750 :| Default height for "file" objects                |
+| filerad       |: 0.150 :| Default corner fold length for "file" objects    |
+| filewid       |: 0.500 :| Default width for "file" objects                 |
+| fill          |: -1.00 :| Default fill color.  Negative means "none"       |
+| lineht        |: 0.500 :| Default length for lines drawn up or down        |
+| linewid       |: 0.500 :| Default length for lines drawn left or right     |
+| movewid       |: 0.500 :| Default distance traversed by a "move"           |
+| ovalht        |: 0.500 :| Default height of an "oval" object               |
+| ovalwid       |: 1.000 :| Default width of an "oval" object                |
+| scale         |: 1.000 :| Scale factor for drawing.  Larger is bigger.     |
+| textht        |: 0.500 :| *Not used by Pikchr*                             |
+| textwid       |: 0.750 :| *Not used by Pikchr*                             |
+| thickness     |: 0.015 :| Default line thickness for all objects           |
+
+In addition to the above, Pikchr recognizes the following variables
+which are not initially defined, but if they are defined by the script
+have special properties:
+
+>
+| Variable Name&nbsp;&nbsp;&nbsp;&nbsp; |: Purpose                           |
+------------------------------------------------------------------------------
+| bottommargin  | Extra border space added to the bottom of the diagram      |
+| fgcolor       | Use this foreground color in place of black                |
+| fontscale     | Scale factor applied to the font size of text              |
+| layer         | The default layer for all subsequent objects               |
+| leftmargin    | Extra border space added to the left of the diagram        |
+| margin        | Extra border space added to all four sides of the diagram  |
+| rightmargin   | Extra border space added to the right side of the diagram  |
+| topmargin     | Extra border space added to top top side of the diagram    |
+
+
+The "VARIABLE *assignment-op* *expr*" syntax is able to modify the value
+of built-in variables, or create new variables.  In legacy-PIC, the only
+*assignment-op* was "`=`".  Pikchr adds "`+=`", "`-=`", "`*=`", and
+"`/=`" to make it easier to scale existing variables up or down.
+
+### Conflicts between variable names and keywords
+
+Some of the built-in variables have names that conflict with keywords:
+
+  *  color
+  *  fill
+  *  thickness
+
+To access such variables as part of an expression, simply put them inside
+of parentheses.  For example, to set the thickness of a box to be twice
+the default thinkness:
+
+~~~ pikchr center toggle source
+   box "Normal"
+   move
+   box "Double" "Thick" thickness 2*(thickness)
+~~~
+
+## Define
+
+The "`define`" statement creates a [macro](./macro.md)
+that can then be called in subsequent text.
+
+## Print
+
+The "`print`" statement prints the strings and the values of the expressions
+in its argument into the generated output in front of the 
+"`<svg>`" element for the diagram.  This facility is intended for testing
+and debugging purposes.  There is no known practical use for "`print`" in
+a production Pikchr script.
+
+The following Pikchr script demonstrates the effect of "print".
+Click to toggle between the script and its rendering.
+
+~~~ pikchr toggle source indent
+   oval "Hello, World!" fit
+   print "Oval at: ",previous.x, ",", previous.y
+   line
+   oval "2nd oval" fit
+   print "2nd oval at: ",previous.x, ",", previous.y
+~~~
+
+## Assert
+
+The "`assert`" statement is intended for testing and debugging of Pikchr
+scripts.  An assert() is a no-op if the equality comparison in its
+argument is true.  But it raises an error if the condition is false.
+
+Consider this script:
+
+~~~
+   oval "Hello, World!" fit
+   assert( last oval.w == last oval.e ); # <-- should fail
+~~~
+
+And its rendering:
+
+~~~ pikchr
+   oval "Hello, World!" fit
+   assert( last oval.w == last oval.e ); # <-- should fail
+~~~

+ 94 - 0
pikchr.mod/pikchr/doc/stmtlist.md

@@ -0,0 +1,94 @@
+# statement-list
+
+A complete Pikchr source document consists of a list of zero or more
+statements. Individual statements within the list are separated from
+each other by semicolons ("`;`") and/or newlines.  Surplus semicolons
+and newlines are ignored.  A zero-length string, or a string consisting
+of only semicolons and newlines, is a valid Pikchr document.
+
+The *statement-list* is also a subpart of the syntax for 
+the `[]`-collection object.
+
+## Rules
+
+  * *statement* 
+  * *statement-list* NEWLINE *statement*
+  * *statement-list* **;** *statement*
+
+## Bubble Chart
+
+~~~~~ pikchr indent
+$r = 0.2in
+linerad = 0.75*$r
+linewid = 0.25
+
+# Start and end blocks
+#
+box "statement-list" bold fit
+line down 75% from last box.sw
+dot rad 250% color black
+X0: last.e + (0.3,0)
+arrow from last dot to X0
+move right 2in
+box wid 5% ht 25% fill black
+X9: last.w - (0.3,0)
+arrow from X9 to last box.w
+
+
+# The main rule that goes straight through from start to finish
+#
+box "statement" italic fit at 0.5<X0,X9>
+arrow to X9
+arrow from X0 to last box.w
+
+# The by-pass line
+#
+arrow right $r from X0 then up $r \
+  then right until even with 1/2 way between X0 and X9
+line right until even with X9 - ($r,0) \
+  then down until even with X9 then right $r
+
+# The Loop-back rule
+#
+oval "\"&#92;n\"" fit at $r*1.2 below 1/2 way between X0 and X9
+line right $r from X9-($r/2,0) then down until even with last oval \
+   then to last oval.e ->
+line from last oval.w left until even with X0-($r,0) \
+   then up until even with X0 then right $r
+oval "\";\"" fit at $r*1.2 below last oval
+line from 2*$r right of 2nd last oval.e left $r \
+   then down until even with last oval \
+   then to last oval.e ->
+line from last oval.w left $r then up until even with 2nd last oval \
+   then left 2*$r ->
+~~~~~
+
+## Whitespace
+
+Whitespace other a newline is ignored.  If a backslash is followed
+by one or more whitespace characters ending in a newline, then the
+backslash and all of the spaces that follow, including the newline,
+are considered whitespace.  Thus, a backslash at the end of a line
+causes a statement to continue onto the next line.
+
+## Comments
+
+Three comment formats are supported:
+
+   *  The "`#`" character and all characters that follow up to but not
+      including the next newline character.  (Bourne-shell style comments.)
+
+   *  Two forward slashes ("`//`") and all characters that follow up to
+      but not including the next newline character.  (C++ style comments.)
+
+   *  The sequence "`/*`" and all characters that follow up to and including
+      the next "`*/`".  (C style comments.)
+
+The first form (#-comments) is the only form supported by legacy-PIC.
+The C++ and C style commenting is new to Pikchr.
+
+For #-comments and //-comments, the newline that follows is not part of the
+comment.  Hence that newline will terminate the current statement.  There
+is no way to escape the newline at the end of a #- or //-comment.  If you
+need a comment at the end of a line but want to continue the statement on
+the next line, you must use `/*..*/` style comments.

+ 296 - 0
pikchr.mod/pikchr/doc/teardown01.md

@@ -0,0 +1,296 @@
+# Tutorial Analysis Of A Pikchr Diagram
+
+Let's look closely at an example Pikchr diagram to better understand
+how they work.  For this analysis, we will use a diagram that depicts
+a rebase operation in a version control system.  The original diagram
+is found in the
+[Rebase Considered Harmful][rch] document of the Fossil documentation.
+The version shown here is modified slightly from the original, for example by
+adding line number comments.
+Click on the diagram to see the Pikchr code.
+
+[rch]: https://fossil-scm.org/fossil/doc/trunk/www/rebaseharm.md
+
+~~~ pikchr toggle
+/* 01 */ scale = 0.8
+/* 02 */ fill = white
+/* 03 */ linewid *= 0.5
+/* 04 */ circle "C0" fit
+/* 05 */ circlerad = previous.radius
+/* 06 */ arrow
+/* 07 */ circle "C1"
+/* 08 */ arrow
+/* 09 */ circle "C2"
+/* 10 */ arrow
+/* 11 */ circle "C4"
+/* 12 */ arrow
+/* 13 */ circle "C6"
+/* 14 */ circle "C3" at dist(C2,C4) heading 30 from C2
+/* 15 */ arrow
+/* 16 */ circle "C5"
+/* 17 */ arrow from C2 to C3 chop
+/* 18 */ C3P: circle "C3'" at dist(C4,C6) heading 30 from C6
+/* 19 */ arrow right from C3P.e
+/* 20 */ C5P: circle "C5'"
+/* 21 */ arrow from C6 to C3P chop
+/* 22 */ box height C3.y-C2.y \
+/* 23 */     width (C5P.e.x-C0.w.x)+linewid \
+/* 24 */     with .w at 0.5*linewid west of C0.w \
+/* 25 */     behind C0 \
+/* 26 */     fill 0xc6e2ff thin color gray
+/* 27 */ box same width previous.e.x - C2.w.x \
+/* 28 */     with .se at previous.ne \
+/* 29 */     fill 0x9accfc
+/* 30 */ "trunk" below at 2nd last box.s
+/* 31 */ "feature branch" above at last box.n
+~~~
+
+Hint:  Copy the Pikchr source text and paste it into the
+[](/pikchrshow) page in a separate browser window or tab so that
+you can make minor changes and see the effect of those changes
+as we work through the text.
+
+## Lines 01 through 03 - modifying object size defaults
+
+The script begins by setting some global property variables.  The
+"`scale = 0.8`" line simply makes the whole diagram a little smaller
+so that it fits better within its host document.  Try commenting out
+that line by adding a "`#`" or "`//`" at the start to see the difference.
+
+The "`fill = white`" line causes all objects on the graph to have a
+default background fill color of white.  Without this line, the objects are
+not filled at all, and so the background colors (to be inserted on lines 22
+through 29) show through.  The result is still legible, but less pleasing.
+Try commenting out line 02 to see what happens.  We could have
+added a "`fill white`" attribute on every circle in the diagram instead,
+but it's easier to set the default fill color once.
+
+The "`linewid *= 0.5`" on line 03 shortens the default length of lines
+and arrows by 50%.  Try commenting out that line.  You will see that the
+arrows become twice as long, which makes the graph more spread out and
+harder to read.  Shortening the arrows is an aesthetic improvement.
+
+Even though they appear first in the script, directives like these
+are typically inserted after creating the initial version of the diagram in order to clean up a diagram once
+the basic structure is established.  Do not feel like you need
+to start out by setting a bunch of variables.  Write the object
+definitions first, and then perhaps go back and tweak the appearance
+by adjusting some variable settings.
+
+## Lines 04 and 05 - establishing the prototype node circle
+
+Line 04 creates a circle sized to fit its label "C0".  We want
+all the circles in this diagram to be the same size, so after sizing
+the first one to fit the text, line 05 sets the new default circle radius
+for all subsequent circles to be same as the first circle.  This
+not only saves us from having to add a "fit" on every "circle" call, it means
+all circles will be of a uniform size despite containing
+varying amounts of text.
+
+## Lines 06 through 13 - the bottom row of nodes
+
+After establishing the initial diagram node, lines 06 through 13 create
+a sequence of nodes, C1, C2, C4, and C6, connected by arrows and
+moving to the right.  The default Pikchr layout direction is
+"right," so everything is placed automatically.
+
+## Line 14 - drawing the first node of the first branch
+
+We want the C3-C5 branch to be above and slightly to the right of
+the C2 node.  For a pleasing appearance, it seems best to make the
+distance from C2 to C3 be the same as the distance from C2 to C4.
+This is accomplished by setting the location of C3 using
+a clause of the form:
+
+  *  *distance* **heading** *angle* **from** *basis*
+
+The *basis* is C2.  The *distance* is the same as the distance from C2
+to C4, and so we use the expression "`dist(C2,C4)`".  Notice here that
+we are able to refer to the nodes using their text annotations because
+the text annotations have the form of a valid object label: they begin
+with a capital letter and consist of alphanumerics and underscores.
+The *angle* is a compass heading: 0 to 360 degrees clockwise from
+north.  A heading of 30 degrees means that there is a 60-degree
+angle between C2-C4 and C2-C3, thus establishing C2, C3, and C4 as
+the vertexes of an equilateral triangle.
+
+~~~ pikchr toggle
+linewid *= 0.5
+circle "C0" fit
+circlerad = previous.radius
+arrow
+circle "C1"
+arrow
+circle "C2"
+arrow
+circle "C4"
+arrow
+circle "C6"
+circle "C3" at dist(C2,C4) heading 30 from C2
+
+d1 = dist(C2,C3.ne)+2mm
+line thin color gray from d1 heading 30 from C2 \
+   to d1+1cm heading 30 from C2
+line thin color gray from d1 heading 0 from C2 \
+   to d1+1cm heading 0 from C2
+spline thin color gray <-> \
+   from d1+8mm heading 0 from C2 \
+   to d1+8mm heading 10 from C2 \
+   to d1+8mm heading 20 from C2 \
+   to d1+8mm heading 30 from C2 \
+   "30&deg;" aligned below small
+
+X1: line thin color gray from circlerad+1mm heading 300 from C3 \
+        to circlerad+6mm heading 300 from C3
+X2: line thin color gray from circlerad+1mm heading 300 from C2 \
+        to circlerad+6mm heading 300 from C2
+line thin color gray <-> from X2 to X1 "distance" aligned above small \
+    "C2 to C4" aligned below small
+~~~
+
+## Lines 15 through 17 - completing the first branch
+
+Lines 15 and 16 add the arrow and C5 node.
+
+The arrow from C2 to C3 is drawn by line 17.  The "`chop`" attribute
+causes the arrow to begin and end on node boundaries.  If you remove
+the "`chop`" (try it!) the arrow will go from the center of the first
+node to the center of the second, which isn't what we want.
+
+## Lines 18 through 21 - nodes of the second branch
+
+Lines 18 through 21 are mostly a repeat of lines 14 through 17.
+The differences are (1) the branch is connected to C6 instead of C2
+and (2) the nodes have different labels.
+Because these node labels include "prime" marks (`'`), you cannot use
+them as object labels as we could for the corresponding C3 and C5
+nodes. Therefore, the nodes of this second branch are given
+explicit labels "C3P" and "C5P".  Do not be bashful about adding
+labels to objects.  The use of labels often makes the script much
+easier to read and maintain.
+
+# Lines 22 through 26 - background color for trunk
+
+Lines 22 through 26 implement a single box object that provides background
+color for the trunk.  Note the use of backslash ("`\`") to continue the
+definition of this object across multiple lines.  It is not required to
+break up the definition of the box across multiple lines; it
+merely aids human
+understanding.  Pikchr does not care how long your source lines are.
+
+Some tricky calculations are involved here.  We need to figure out
+an appropriate width and height for the box so that it encloses the
+sequence of circles and arrows that represent the trunk, with a
+comfortable margin, and we have to position the box so that the circles
+and arrows are approximately centered.  The height is "`C3.y-C2.y`".
+That is the equivalent of the distance between the bottom and second
+row.  In this way, the division between the bottom and top row can
+occur right in the middle of the two, and the margins above and below
+the bottom row will be the same.  The width is sufficient to span the
+entire row, plus one extra "`linewid`" for margin, to be evenly divided
+between both ends.
+
+Line 24 positions the background color box.  That line says that the
+extreme western end of the background color box should be half a linewid
+to the west of the extreme western end of the first node of the graph.
+Recall that we allowed for one linewid of margin to be split between
+both ends, so the western side is half that margin to the left of the
+leftmost end of the graph.
+
+Normally, Pikchr stacks elements on the SVG canvas in the order that they appear in the
+source text, so this new background color box would normally paint
+on top of the objects that come before, which would obscure the graph nodes.
+To prevent this, the "`behind C0`" on line 25 tells Pikchr to paint 
+this box before it paints the C0 circle, so that the background color
+box occurs in the background rather than on top of the graph.
+Try commenting out the "`behind C0`" to see what happens!
+
+Finally, line 26 changes the fill color for the box to a light shade
+of blue and the border to be thin and gray.
+
+# Lines 27 through 29 - background color for the branches
+
+Lines 27 through 29 create a second box to provide background color
+to the upper branches.  The second box definition begins with the
+keyword "`same`".  The "`same`" means that all of the settings to
+the new box are initialized to values from the previous box.  That
+means we don't have to set the height, or set "`behind C0`" or
+"`thin`" or "`color gray`".  All those attributes are inherited.
+The second box only has to change the width to accommodate the shorter
+length of the branch it encloses in the diagram, to
+adjust the background color, and to set the position.
+
+We want the right edge of both background boxes to align, and we
+want the branch background to begin a little to the left of C3.
+The left edge of C2 seems like a reasonable starting point, so we
+set the width to "`previous.e.x - C2.w.x`" on line 27.  The "`previous`"
+refers to the previous background color box, of course.
+
+If you had to insert a new object in between the two boxes,
+it would change the referent of the "`previous`" qualifier, but only
+if one or more of those new objects is itself a box. Inserting a
+circle wouldn't affect it, since "`previous`" always means the previous
+object of the same type.
+
+If you had to interpose another box, you could solve the broken
+reference problem by adding a label and using that label instead
+of "`previous`":
+
+~~~~
+         ...
+/* 22 */ BG1: box height C3.y-C2.y \
+/* 23 */     width (C5P.e.x-C0.w.x)+linewid \
+/* 24 */     with .w at 0.5*linewid west of C0.w \
+/* 25 */     behind C0 \
+/* 26 */     fill 0xc6e2ff thin color gray
+/* 27 */ box same as BG1 width BG1.e.x - C2.w.x \
+/* 28 */     with .se at BG1.ne \
+/* 29 */     fill 0x9accfc
+         ...
+~~~~
+
+
+Line 28 positions the second box.  The southeast (.se) corner of
+the second box is set to align with the northeast (.ne) corner of
+the previous box.  This causes the two boxes to be flush right
+and stacked directly on top of each other.
+
+Line 29 adjusts the background color to a darker shade of blue.
+
+# Lines 30 and 31 - labeling the branches
+
+Lines 30 and 31 create a pair of text objects to identify the two
+branches depicted in the diagram.  
+
+# Summary
+
+A 31-line Pikchr script might look intimidating at first glance, but
+as we see here, it is really quite simple.  No coordinates are involved,
+nor any hard-coded distances.  Everything is laid out and sized relative
+to other elements and to the system defaults.  This makes the diagram
+portable and adjustments easy.
+
+# Exercises
+
+Practice your Pikchr-script writing skills by modifying the
+example script as follows:
+
+  1.  Add a new "C7" node to the right of "C6".
+
+  2.  Now add "C8" to the right of "C7".  This one is harder because it
+      will involve expanding the background color boxes.
+
+  3.  Put the feature branch below the trunk rather than above it.
+
+  4.  Move the "feature branch" and "trunk" labels to the left ends
+      of their respective boxes, rather than centering them.
+
+  5.  Add another branch above the "feature branch" that adds
+      nodes "C9", "C10", and "C11" that fork off from "C5'".  You
+      will probably need to find a new place to put the "feature branch"
+      label to get it out of the way.
+
+  6.  Add a new node and dashed line from "C5'" that illustrates "C5'"
+      being merged back into trunk.
+
+  7.  Rotate the graph so that it goes bottom-up rather than left-to-right.

+ 154 - 0
pikchr.mod/pikchr/doc/textattr.md

@@ -0,0 +1,154 @@
+# text-attribute
+
+Any string literal that is intended to be displayed on the
+diagram can be followed by zero or more of the following
+keywords, in any order:
+
+  * **above**
+  * **aligned**
+  * **below**
+  * **big**
+  * **bold**
+  * **center**
+  * **italic**
+  * **ljust**
+  * **rjust**
+  * **small**
+
+## Attributes "above" and "below"
+
+The "`above`" and "`below`" keywords control the location of the
+text above or below the center point of the object with which
+the text is associated.  If there is just one text on the object
+and the "`above`" and "`below`" keywords are omitted, the text is
+placed directly over the center of the object.  This causes
+the text to appear in the middle of lines:
+
+~~~~ pikchr indent
+  line "on the line" wid 150%
+~~~~
+
+So if there is just a single text label on a line, you probably
+want to include either the "`above`" or "`below`" keyword.
+
+~~~~ pikchr indent
+  line "above" above; move; line "below" below
+~~~~
+
+If there are two texts on the object, they straddle the center point
+above and below, even without the use of the "`above`" and "`below`"
+keywords:
+
+~~~~ pikchr indent
+  line wid 300% "text without \"above\"" "text without \"below\""
+~~~~
+
+The "`above`" and "`below`" attributes do not stack or accumulate.
+Each "`above`" or "`below`" overrides any previous "`above`" or "`below`"
+for the same text.
+
+If there are multiple texts and all are marked "`above`" or "`below`", then
+all are placed above or below the center point, in order of appearance.
+
+~~~~ pikchr indent
+  line width 200% "first above" above "second above" above
+  move
+  line same "first below" below "second below" below
+~~~~
+
+## Attributes "ljust" and "rjust"
+
+As the "`above`" and "`below`" keywords control up and down positioning of
+the text, so the "`ljust`" and "`rjust`" keywords control left and right
+positioning.
+
+For a line, the "`ljust`" means that the left side of the text is flush
+against the center point of the line.  And "`rjust`" means that the right
+side of the text is flush against the center point of the line.
+(In the following diagram, the red dot is at the center of the line.)
+
+~~~~ pikchr indent
+   line wid 200% "ljust" ljust above "rjust" rjust below
+   dot color red at previous.c
+~~~~
+
+For a block object, "`ljust`" shifts the text to be left justified
+against the left edge of the block (with a small margin) and
+"`rjust`" puts the text against the right side of the object (with
+the same margin).
+
+~~~~ pikchr indent
+   box "ljust" ljust "longer line" ljust "even longer line" ljust fit
+   move
+   box "rjust" rjust "longer line" rjust "even longer line" rjust fit
+~~~~
+
+The behavior of "`ljust`" and "`rjust`" for block objects in Pikchr differs
+from legacy PIC.
+In PIC, text is always justified around the center point, as in lines.
+But this means there is no easy way to left justify multiple lines of
+text within a "box" or "file", and so the behavior was changed for
+Pikchr.
+
+Pikchr allows two texts to fill the same vertical slot if one is
+"`ljust`" and the other is "`rjust`".
+
+~~~~ pikchr indent
+  box wid 300% \
+     "above-ljust" above ljust \
+     "above-rjust" above rjust \
+     "centered" center \
+     "below-ljust" below ljust \
+     "below-rjust" below rjust
+~~~~
+
+## Attribute "center"
+
+The "`center`" attribute cancels all prior "`above`", "`below`",
+"`ljust`", and "`rjust`" attributes for the current text.
+
+## Attributes "bold" and "italic"
+
+The "`bold`" and "`italic`" attributes cause the text to use a bold or
+an italic font.  Fonts can be both bold and italic at the same time.
+
+~~~~ pikchr indent
+  box "bold" bold "italic" italic "bold-italic" bold italic fit
+~~~~
+
+## Attribute "aligned"
+
+The "`aligned`" attribute causes text associated with a straight line
+to be rotated to align with that line.
+
+~~~~ pikchr indent
+  arrow go 150% heading 30 "aligned" aligned above
+  move to 1cm east of previous.end
+  arrow go 150% heading 170 "aligned" aligned above
+  move to 1cm east of previous.end
+  arrow go 150% north "aligned" aligned above
+~~~~
+
+To display rotated text not associated with a line attach the
+text to a line that is marked "`invisible`"
+
+~~~~ pikchr indent
+  box ht 200% wid 50%
+  line invis from previous.s to previous.n "rotated text" aligned
+~~~~
+
+## Attributes "big" and "small"
+
+The "`big`" and "`small`" attributes cause the text to be a little larger
+or a little smaller, respectively.  Two "`big`" attributes cause the
+text to be larger still, as do two "`small`" attributes.  But the text
+size does not increase or decrease beyond two "`big`" or "`small`" keywords.
+
+~~~~ pikchr indent
+  box "small small" small small "small" small \
+    "(normal)" italic \
+    "big" big "big big" big big ht 200%
+~~~~
+
+A "`big`" keyword cancels any prior "`small`" keywords on the same text,
+and a "`small`" keyword cancels any prior "`big`" keywords.

+ 10 - 0
pikchr.mod/pikchr/doc/thickthin.md

@@ -0,0 +1,10 @@
+# The "thick" and "thin" attributes
+
+The "`thick`" and "`thin`" attributes increase or decrease the stroke-width
+for the lines used to draw an object.  There can be multiple "`thick`" or
+"`thin`" attributes - the effects are cumulative.
+
+# The `solid` attribute
+
+The "`solid`" attribute changes the stroke-width back to its default,
+and it disables "`dashed`" and "`dotted`".

+ 122 - 0
pikchr.mod/pikchr/doc/usepikchr.md

@@ -0,0 +1,122 @@
+# How To Use Pikchr In Fossil Markdown and Fossil Wiki Markup
+
+## Embedded Pikchr In Markdown
+
+To include a Pikchr diagram in the middle of a Markdown document
+in [Fossil][fossil], simple place the Pikchr script in a [fenced code block][fcb]
+that has a class of "pikchr".  Like this:
+
+~~~~~
+   ~~~ pikchr
+   arrow; box "Hello!"; arrow
+   ~~~
+~~~~~
+
+Or like this:
+
+~~~~~
+   ``` pikchr
+   arrow; box "Hello!"; arrow
+   ```
+~~~~~
+
+The result appears as follows:
+
+``` pikchr
+arrow; box "Hello!"; arrow
+```
+
+
+[fcb]: https://spec.commonmark.org/0.29/#fenced-code-blocks
+
+## Image Placement and Source Code Toggle Options
+
+By default, the Pikchr-generated image appears left-justifed.  If you
+would prefer that the picture be centered, put the keyword "center"
+after the class tag.  Thus:
+
+~~~~~
+   ``` pikchr center
+   arrow; box "Hello" "again"; arrow <-
+   ```
+~~~~~
+
+Results in the following:
+
+``` pikchr center
+arrow; box "Hello" "again"; arrow <-
+```
+
+Pikchrs embedded in Fossil documents can be toggled between their SVG
+and raw source code forms using the `ctrl` key and left mouse button,
+noting that (A) "ctrl" is actually environment-dependent and might be
+"alt" or the Mac-specific "Command" key, and (B) this feature requires
+that JavaScript is activated. If the source code should also be
+revealed by a simple click, add the word "toggle" after the `pikchr`
+class tag, as in this click-toggleable example:
+
+``` pikchr center toggle
+arrow ->; box "Click to" "toggle"; arrow <-
+```
+
+Notice that the source code is displayed left-aligned by default. It can
+be configured to display in the same approximate position as the image
+by adding the "source-inline" modifier:
+
+``` pikchr center toggle source-inline
+arrow ->; box "Click to" "toggle" "(centered)"; arrow <-
+```
+
+The full list of such modifiers, in alphabetical order:
+
+- `center`: center-aligns the image. The default is left-aligned.
+- `float-left` and `float-right`: "float" the image to the
+  left resp. right, such that other content will flow around them.
+- `indent`: left-aligns the pikchr, indented by some CSS-specified
+  amount.
+- `source`: defaults to source code view instead of SVG view.
+- `source-inline`: places the source code view, when revealed, at
+  approximately the same position as the SVG, instead of left-aligned.
+  The source's size may vary wildly from the image's, so this
+  placement is in the same *relative* position, rather than a precise
+  fit.
+- `toggle`: indicates that a single click/tap is required to toggle
+  between SVG and source code views. This is primarily intended to be
+  used in documents which are about learning how to write pikchr
+  code. For consistency, `ctrl`-click also works on pikchrs tagged
+  with this modifier.
+
+## Embedded Pikchr In Fossil Wiki
+
+[Fossil][fossil] supports its own document markup format called
+(uncreatively) "[Fossil Wiki][fossilwiki]".  Fossil Wiki is basically
+just a safe subset of HTML with a few simple hyperlink, paragraph break, and
+list formatting enhancements. Fossil Wiki supports something
+similar to "fenced code blocks" in a special "`<verbatim>`" tag.
+All content in between "`<verbatim>`" and the next
+"`</verbatim>`", is displayed exactly as written.  Hence, just as
+fenced code blocks are used to add Pikchr support to Markdown, so too
+the "`<verbatim>`" mechanism is used to add Pikchr support to Fossil Wiki.
+
+In Fossil Wiki, Pikchr source text can be enclosed within
+"`<verbatim type="pikchr">`" or "`<verbatim type="pikchr center"`>
+through the next "`</verbatim>`" tag.  Hence, code like this:
+
+~~~~~
+   <verbatim type="pikchr center">
+   arrow; ellipse "Hi, Y'all"; arrow
+   </verbatim>
+~~~~~
+
+Will result in a display like this:
+
+~~~ pikchr center
+arrow; ellipse "Hi, Y'all"; arrow
+~~~
+
+Fossil's `verbatim` format supports the same range of modifiers as the
+Markdown format, as described above: simply add each modifier in the
+`type` attribute after the word `pikchr`.
+
+[fossil]: https://fossil-scm.org/home
+[fossilwiki]: /wiki_rules

+ 1337 - 0
pikchr.mod/pikchr/doc/userman.md

@@ -0,0 +1,1337 @@
+# Pikchr User Manual
+
+# Introduction
+
+This is a guide to generating diagrams using Pikchr
+(pronounced "Picture").  This guide is
+designed to teach you to use Pikchr.  It is not a reference for the
+Pikchr language (that is a [separate document][gram]) nor is it an explanation
+of why you might want to use Pikchr.  The goal here is to provide
+a practical and accessible tutorial on using Pikchr.
+
+[gram]: ./grammar.md
+
+# Running Pikchr Scripts <a id="running"></a>
+
+The design goal of Pikchr is to enable embedded line diagrams in Markdown or other
+simple markup languages.  The details on how to embedded Pikchr in Markdown is
+[covered separately][embed].  For the purpose of this tutorial, we will only write
+pure Pikchr scripts without the surrounding markup.  To experiment
+with Pikchr, visit the [](/pikchrshow) page on the website hosting
+this document (preferably in a separate window).  Type in the following
+script and press the Preview button:
+<a id="firstdemo"></a>
+
+~~~~~ pikchr source toggle indent
+     line; box "Hello," "World!"; arrow
+~~~~~
+
+If you do this right, the output should appear as:
+
+~~~~~ pikchr toggle indent
+     line; box "Hello," "World!"; arrow
+~~~~~
+
+So there you go: you've created and rendered your first diagram using
+Pikchr!  You will do well to keep that /pikchrshow screen handy, in a
+separate browser window, so that you can try out scripts as you proceed
+through this tutorial.
+
+[embed]: ./usepikchr.md
+
+# Viewing Pikchr Script Source Code For This Document <a name="viewsrc"></a>
+
+For this particular document, you can click on any of the diagrams
+rendered by Pikchr and the display will convert to showing you the
+original Pikchr source text.  Click again to go back to seeing the
+rendered diagram.
+
+The click-to-change-view behavior is a property of this one
+particular document and is not a general capability of Pikchr. On
+other documents containing Pikchr diagrams that are generated using Fossil
+you can use ctrl-click (alt-click on Macs) to toggle the view.
+That is, click on the diagram while holding down the Ctrl key or the Alt key.
+This is not possible if
+you are on a tablet or phone, since you don't have a Ctrl or Alt key to hold
+down there.  Other systems might not implement the view-swapping behavior
+at all.  This is a platform-depending feature that is one layer above
+Pikchr itself.
+
+# About Pikchr Scripts <a id="about"></a>
+
+Pikchr is designed to be simple.  A Pikchr script is
+just a sequence of Pikchr statements, separated by either new-lines or
+semicolons.  The "Hello, world!" example above used three statements,
+a "line", a "box", and an "arrow", each separated by semicolons.
+
+Whitespace (other than newlines) and comments are ignored.  Comments in
+pikchr can be in the style of TCL, C, or C++.  That is to say, comments
+consist of a "`#`" or "`//`"  and include all characters up to but
+not including the next new-line, or all text
+in between "`/*`" and the first following "`*/`".
+The example script above could be rewritten with each statement on
+a separate line, and with comments describing what each statement is
+doing:
+
+~~~~~ pikchr source toggle indent
+    # The first component of the drawing is a line
+    line
+    // The second component is a box with text "Hello, World!"
+    box "Hello," "World!"
+    /* Finally an arrow */
+    arrow
+~~~~~
+
+Remember that new-lines separate statements.  If you have a long statement
+that needs to be split into multiple lines, escape the newline with
+a backslash character and the new-line will be treated as any other space:
+
+~~~~~ pikchr source toggle indent
+    line
+    box \
+       "Hello," \
+       "World!"
+    arrow
+~~~~~
+
+So, a Pikchr script is just a list of statements, but what is a statement?
+
+# Pikchr Statements <a id="statements"></a>
+
+*Most* statements are descriptions of a single graphic object that
+becomes part of the diagram.  The first token of the statement is the
+object class-name.  The following classes are currently supported:
+
+~~~~~ pikchr toggle indent
+box "box"
+circle "circle" at 1 right of previous
+ellipse "ellipse" at 1 right of previous
+oval "oval" at .8 below first box
+cylinder "cylinder" at 1 right of previous
+file "file" at 1 right of previous
+line "line" above from .8 below last oval.w
+arrow "arrow" above from 1 right of previous
+spline from previous+(1.8cm,-.2cm) \
+   go right .15 then .3 heading 30 then .5 heading 160 then .4 heading 20 \
+   then right .15
+"spline" at 3rd vertex of previous
+dot at .6 below last line
+text "dot" with .s at .2cm above previous.n
+arc from 1 right of previous dot
+text "arc" at (previous.start, previous.end)
+text "text" at 1.3 right of start of previous arc
+~~~~~
+
+A statement can be only the class-name and nothing else, but the class-name
+is usually followed by one or more "attributes".  Attributes are used
+to modify the appearance of the object, or to position the object relative
+to prior objects.
+
+So to revisit the ["Hello, World" demonstration script above](#firstdemo),
+we see that that script contains three object descriptions:
+
+  1.  A "line" object with no attributes (meaning that the line is shown
+      with no changes to its default appearance).
+  2.  A "box" object with two string literal attributes.  The string
+      literal attributes cause the corresponding strings to be drawn
+      inside the box.
+  3.  An "arrow" object with no attributes.
+
+# Layout <a id="layout"></a>
+
+By default, objects are stacked beside each other from left to right.
+The Pikchr layout engine keeps track of the "layout direction", which
+can be one of "right", "down", "left", or "up".  The layout direction
+defaults to "right", but you can change it using a statement which
+consists of just the name of the new direction.  So,
+if we insert the "down" statement in front of our test script, like
+this:
+
+~~~~~ pikchr source toggle indent
+    down
+    line
+    box  "Hello,"  "World!"
+    arrow
+~~~~~
+
+Then the objects are stacked moving downward:
+
+~~~~~ pikchr toggle indent
+    down
+    line
+    box  "Hello,"  "World!"
+    arrow
+~~~~~
+
+Or, you can change the layout direction to "left":
+
+~~~~~ pikchr toggle indent
+    left
+    line
+    box  "Hello,"  "World!"
+    arrow
+~~~~~
+
+Or to "up":
+
+~~~~~ pikchr toggle indent
+    up
+    line
+    box  "Hello,"  "World!"
+    arrow
+~~~~~
+
+It is common to stack line objects (lines, arrows, splines) against
+block objects (boxes, circles, ovals, etc.), but this is not required.
+You can stack a bunch of block objects together.
+For example:
+
+~~~~~ pikchr source toggle indent
+    box; circle; cylinder
+~~~~~
+
+Yields:
+
+~~~~~ pikchr toggle indent
+    box; circle; cylinder
+~~~~~
+
+More often, you want to put space in between the block objects.
+The special "move" object exists for that purpose.  Consider:
+
+~~~~~ pikchr source toggle indent
+    box; move; circle; move; cylinder
+~~~~~
+
+This script creates the same three block objects but with 
+whitespace in between them:
+
+~~~~~ pikchr toggle indent
+    box; move; circle; move; cylinder
+~~~~~
+
+Implementation note:  A "move" is really just an invisible "line".  So
+the following script generates the same output as the previous.
+([Try it!](/pikchrshow?content=box;line%20invisible;circle;line%20invisible;cylinder))
+
+~~~~~ pikchr source toggle indent
+    box; line invisible; circle; line invisible; cylinder
+~~~~~
+
+# Controlling Layout Using Attributes <a id="attributes"></a>
+
+The automatic stacking of objects is convenient in many cases, but
+most diagrams will want some objects placed somewhere other than
+immediately adjacent to their predecessor.  For that reason, layout
+attributes are provided that allow precise placement of objects.
+
+To see how this works, consider the previous example of a box, circle,
+and cylinder separated by some space.  Suppose we want to draw an arrow
+that goes downward out of the box, then right, then up into the
+cylinder.  The complete script might look something like this:
+
+~~~~~ pikchr source toggle indent
+    box; move; circle; move; cylinder
+    arrow from first box.s \
+          down 1cm \
+          then right until even with first cylinder \
+          then to first cylinder.s
+~~~~~
+
+This script results in the following diagram:
+
+
+~~~~~ pikchr toggle indent
+    box; move; circle; move; cylinder
+    arrow from first box.s \
+          down 1cm \
+          then right until even with first cylinder \
+          then to first cylinder.s
+~~~~~
+
+That is indeed the image we want, but there are a lot of words on
+that "arrow" statement!  Don't panic, though.  It's actually pretty
+simple.  We'll take it apart and explain it piece by piece.
+
+First note that the "arrow" statement is broken up into four separate
+lines of text, with a "`\`" at the end of the first three lines to
+prevent the subsequent new-line from prematurely closing the statement.
+Splitting up the arrow into separate lines this way is purely for
+human readability.  If you are more comfortable putting the whole
+statement on one line, that is fine too.  Pikchr doesn't care.  Just
+be sure to remember the backslashes if you do split lines!
+
+The attributes on the "arrow" statement describe the path taken by
+the arrow.  The first attribute is "`from first box.s`".  This "from"
+attribute specifies where the arrow starts.  In this case, it starts
+at the "s" (or "south") anchor point of the "first box".  The "first box"
+part is probably self explanatory.  (You can also write it as
+"1st box" instead of "first box", and in fact legacy-PIC requires
+the use of "1st" instead of "first".)  But what is the ".s" part?
+
+Every block object has eight anchor points on its perimeter that are named
+for compass points, like this:
+
+~~~~~ pikchr toggle indent
+A: box
+dot color red at A.nw ".nw " rjust above
+dot same at A.w ".w " rjust
+dot same at A.sw ".sw " rjust below
+dot same at A.s ".s" below
+dot same at A.se " .se" ljust below
+dot same at A.e " .e" ljust
+dot same at A.ne " .ne" ljust above
+dot same at A.n ".n" above
+dot same at A.c " .c" ljust
+A: circle at 1.5 right of A
+dot color red at A.nw ".nw " rjust above
+dot same at A.w ".w " rjust
+dot same at A.sw ".sw " rjust below
+dot same at A.s ".s" below
+dot same at A.se " .se" ljust below
+dot same at A.e " .e" ljust
+dot same at A.ne " .ne" ljust above
+dot same at A.n ".n" above
+dot same at A.c " .c" ljust
+A: cylinder at 1.5 right of A
+dot color red at A.nw ".nw " rjust above
+dot same at A.w ".w " rjust
+dot same at A.sw ".sw " rjust below
+dot same at A.s ".s" below
+dot same at A.se " .se" ljust below
+dot same at A.e " .e" ljust
+dot same at A.ne " .ne" ljust above
+dot same at A.n ".n" above
+dot same at A.c " .c" ljust
+~~~~~
+
+As you can see, there is also a ninth point in the middle called ".c".
+Every block object has these anchor points; you can refer to them
+when positioning the object itself, or when positioning other objects
+relative to the block object.  In this case, we are starting the
+arrow at the ".s" anchor of the box.
+
+The next phrase on the "arrow" statement is "`down 1cm`".  As you
+might guess, this phrase causes the arrow to move downward from its
+previous position (its starting point) by 1 centimeter.  This phrase
+highlights a key enhancement of Pikchr over PIC, which did
+everything in inches only.  No units were allowed.  Pikchr allows
+you to attach units to measurements, as in this case where it is
+"1cm".  Internally, Pikchr still keeps track of everything in inches
+for compatibility with PIC, so the "1cm" token is really just an
+alternative spelling for the numeric constant "0.39370078740157480316",
+which is the inch-equivalent of 1 centimeter.  Surely you agree that
+"1cm" is much easier to read and write!  Other units recognized by Pikchr
+are "px" for pixels, "pt" for points, "pc" for picas, "mm" for millimeters,
+and of course "in" for inches.  Inches are assumed if no units are
+specified.
+
+Back to our arrow: we have now established a path for the arrow
+down 1 centimeter from the ".s" anchor of the box.  The next phrase
+is:  "`then right until even with first cylinder`".
+You can perhaps guess that this means that the arrow should continue
+to the right until it is lined up below the first cylinder.  You,
+the diagram designer, don't know (and don't really want to know)
+how far apart the box and the cylinder are, so you can't tell it
+exactly how far to go.  This phrase is a convenient way of telling
+Pikchr to "make the line long enough".
+
+Note that the "`first cylinder`" part of the "until even with"
+phrase is actually an abbreviation for "`first cylinder.c`" - the
+center of the cylinder.  This is what we want.  You could also
+write "`first cylinder.s`" if you want.
+
+The "until even with" phrase is not found in the original version of PIC.  In that
+system, you would have to do some extra math to figure out the
+distance for yourself, something like
+"`then right (1st cylinder.s.x - 1st box.s.x)`".  We think the
+"until even with" phrase is easier to use and understand.
+
+The final phrase in the "arrow" statement is
+"`then to first cylinder.s`".  This phrase tells the arrow to go
+from wherever it is at the moment directly to the ".s" anchor
+of the cylinder.
+
+# The Advantage Of Relative Layout <a id="relative"></a>
+
+Notice that our sample diagram contains no coordinates and only
+one hard-coded distance, the "down 1cm" bit in the "arrow" statement.  The script
+is written in such a way that the script-writer does not have
+to do a lot of distance calculation.  The layout compensates
+automatically.
+
+For example, suppose you come back to this script later and
+decide you need to insert an ellipse in between the circle and
+the cylinder.  This is easily accomplished:
+
+~~~~~ pikchr source toggle indent
+    box; move; circle; move; ellipse; move; cylinder
+    arrow from first box.s \
+          down 1cm \
+          then right until even with first cylinder \
+          then to first cylinder.s
+~~~~~
+
+We simply add the ellipse (and an extra "move") on the first line.
+Even though the coordinate positions of the objects have adjusted,
+the description of the arrow that connects the box to the
+cylinder is not based on coordinates or absolute distances,
+so it does not have to change at all.  Pikchr
+compensates automatically:
+
+~~~~~ pikchr toggle indent
+    box; move; circle; move; ellipse; move; cylinder
+    arrow from first box.s \
+          down 1cm \
+          then right until even with first cylinder \
+          then to first cylinder.s
+~~~~~
+
+Both PIC and Pikchr allow you to specify hard-coded coordinates
+and distances when laying out your diagram, but you are encouraged
+to avoid that approach.  Instead, place each new object you create
+relative to the position of prior objects.
+Pikchr provides many mechanisms for specifying the location
+of each object in terms of the locations of its predecessors.  With
+a little study of the syntax options available to you (and discussed
+further below) you will be generating complex diagrams using Pikchr
+in no time.
+
+# Single-Pass Design <a id="single-pass"></a>
+
+Both Pikchr and PIC operate on a single-pass design.  Objects
+can refer to other objects that occur before them in the script, but not
+to objects that occur later in the script.  Any computations that go
+into placing an object occur as the object definition is parsed.  As soon
+as the newline or semicolon that terminates the object definition is
+reached, the size, location, and characteristics of the object are
+fixed and cannot subsequently be altered.  (One exception:  sub-objects that
+are part of a `[]`-container (discussed later) are placed relative to the
+origin of the container.  Their shape and locations relative to each
+other are fixed, but their final absolute position is not fixed until
+the `[]`-container itself is fixed.)
+
+The single-pass approach contributes to the conceptual simplicity of
+Pikchr (and PIC).  There is no "solver" that has to work through
+forward and backward layout constraints to find a solution.  This
+simplicity of design helps to keep Pikchr scripts easy to write and
+easy to understand.
+
+# Labeling Objects <a id="labeling"></a>
+
+The previous example used the phrases like "`first box`" and "`first cylinder`"
+to refer to particular objects.  There are many variations on this naming
+scheme:
+
+  *  "`previous`" &larr; the previous object regardless of its class
+  *  "`last circle`" &larr; the most recently created circle object
+  *  "`3rd last oval`" &larr; the antipenultimate oval object
+  *  "`17th ellipse`" &larr; the seventeenth ellipse object
+  *  ... and so forth
+
+These relative and ordinal references work, but they can be fragile.
+If you go back later and insert a new
+object in the stream, you can mess up the counting.  Or, for that matter,
+you might just miscount.
+
+In a complex diagram, it often works better to assign symbolic names to
+objects, which we call “labels” in Pikchr. A label begins with a capital
+letter followed by some number of regular ASCII letters, digits or
+underscores, followed by a colon. This must come immediately before
+an object, without an intervening newline.
+Afterwards, the object can be referred to
+by that label.
+
+Consider how this simplifies our previous example:
+
+~~~~~ pikchr source toggle indent
+    B1:  box; move;
+         circle; move;
+         ellipse; move;
+    C1:  cylinder
+         arrow from B1.s \
+            down 1cm \
+            then right until even with C1 \
+            then to C1.s
+~~~~~
+
+By giving symbolic names to the box (B1) and cylinder (C1), the arrow path
+description is simplified.  Furthermore, if the ellipse gets changed
+into another cylinder, the arrow still refers to the correct cylinder.
+
+The indentation of the lines following each symbolic name
+above is syntactically unimportant: it serves only to improve human
+readability. Nevertheless, this is typical coding style for Pikchr
+and PIC before it.
+
+# Layout Of Block Objects <a id="block-objects"></a>
+
+For lines (and arrows and splines), you have to specify a path that the line
+follows, a path that might involve multiple bends and turns.  Defining the location
+of block objects is easier: you just provide a single location to place
+the object.  Ideally, you should place the object relative to some other
+object, of course.
+
+Let's say you have box, and you want to position a circle 2 centimeters to the
+right of that box.  You simply use an "`at`" attribute on the circle to tell it
+to position itself 2 cm to the right of the box:
+
+~~~~~ pikchr source toggle indent
+  B1: box
+      circle at 2cm right of B1
+~~~~~
+
+The resulting diagram is:
+
+~~~~~ pikchr toggle indent
+  B1: box
+      circle at 2cm right of B1
+
+  X1: line thin color gray down 50% from 2mm south of B1.s
+  X2: line same from (last circle.s,X1.start)
+      arrow <-> thin from 3/4<X1.start,X1.end> right until even with X2 \
+         "2cm" above color gray
+      assert( last arrow.width == 2cm )
+~~~~~
+
+(We’ve added gray dimension lines purely for illustration.  Click the
+diagram [per the instructions above](#viewsrc) to see that they do not
+change the example, only add to it.)
+
+[fossil]: https://fossil-scm.org/
+
+The circle is positioned so that its *center* is 2 centimeters to the
+right of the *center* of the box.  If what you really wanted is that the
+left (or west) side of the circle is 2 cm to the right (or east)
+of the box, then just say so:
+
+~~~~~ pikchr source toggle indent
+  B1: box
+  C1: circle with .w at 2cm right of B1.e
+~~~~~
+
+Normally an "`at`" clause will set the center of an object, but if
+you add a "`with`" prefix you can specify any other anchor
+point of the object to be the reference for positioning.  The Pikchr
+script above is saying "make the C1.w point be 2 cm right of B1.e".
+And we have:
+
+~~~~~ pikchr toggle indent
+  B1: box
+  C1: circle with .w at 2cm right of B1.e
+
+  X1: line thin color gray down 50% from 2mm south of B1.se
+  X2: line same from (C1.w,X1.start)
+      arrow <-> thin from 3/4<X1.start,X1.end> right until even with X2 \
+         "2cm" above color gray
+      assert( last arrow.width == 2cm )
+~~~~~
+
+That's the whole story behind positioning block objects on a diagram.
+You just add an attribute of the form:
+
+>  **with** *reference-point* **at** *position*
+
+And Pikchr will place the specified reference-point of the object at
+*position*.  If you omit the "`with`" clause, the center of the
+object ("`.c`") is used as the *reference-point*.  The power of Pikchr
+comes from the fact that "*position*" can be a rather complex expression.
+The previous example used a relatively simple *position*
+of "`2cm right of B1.e`".  That was sufficient for our simple diagram.
+More complex diagrams can have more complex *position* phrases.
+
+## Automatic Layout Of Block Objects <a id="auto-layout-block"></a>
+
+If you omit the "`at`" attribute from a block object, the object is positioned
+as if you had used the following:
+
+>  `with .start at previous.end`
+
+Except, the very first object in the script has no "previous" and so it
+is positioned using:
+
+>  `with .c at (0,0)`
+
+Let's talk little more about the usual case:
+"`with .start at previous.end`".  The "`previous`" keyword means the
+previous object in the script.  (You can also use the keyword "`last`"
+for this purpose.)  So we are positioning the current object relative
+to the previous object.  But what about the ".start" and ".end"?
+
+Remember that every object has 8 anchor points whose names correspond
+to compass directions:  ".n", ".ne", ".e", ".se", ".s", ".sw", ".w",
+and ".nw", plus the ninth anchor, the center point ".c".  Every object
+also has ".start" and ".end" anchor
+points, but their position varies depending on the
+layout direction that is current when the object is created.
+
+<blockquote>
+<table border="1" cellpadding="10px" cellspacing="0">
+<tr><th>Layout Direction<th>.start<th>.end
+<tr><td>right<td>.w<td>.e
+<tr><td>down<td>.n<td>.s
+<tr><td>left<td>.e<td>.w
+<tr><td>up<td>.s<td>.n
+</table></blockquote>
+
+Recall the earlier example that consisted of three objects stacked
+together:
+
+~~~~~ pikchr source toggle indent
+    right; box; circle; cylinder
+~~~~~
+
+(I added a "`right`" at the beginning to make the layout direction
+clear, but as "right" is the default layout direction, so it doesn't change
+anything.)
+
+Armed with our new knowledge of how "`at`"-less block objects are
+positioned, we can better understand what is going on.  The box is
+the first object.  It gets positioned with its center at (0,0), which
+we can show by putting a red dot at (0,0):
+
+~~~~~ pikchr source toggle indent
+    right; box; circle; cylinder
+    dot color red at (0,0)
+~~~~~
+
+~~~~~ pikchr toggle indent
+    right; box; circle; cylinder
+    dot color red at (0,0)
+~~~~~
+
+Because the layout direction is "right", the start and end of the box
+are the .w and .e anchor points.  Prove this by putting more colored dots
+at those points and rendering the result:
+
+~~~~~ pikchr source toggle indent
+    right; box; circle; cylinder
+    dot color green at 1st box.start
+    dot color blue at 1st box.end
+~~~~~
+
+~~~~~ pikchr toggle indent
+    right; box; circle; cylinder
+    dot color green at 1st box.start
+    dot color blue at 1st box.end
+~~~~~
+
+Similarly, we can show that the .start and .end of the circle are its
+.w and .e anchor points.  (Add new color dots to prove this to yourself,
+if you like.)  And clearly, the .start of the circle is directly on top
+of the .end of the box.
+
+Now consider what happens if we change the layout direction after the
+circle is created but before the cylinder is created:
+
+~~~~~ pikchr source toggle indent
+    right; box; circle; down; cylinder
+~~~~~
+
+This script works a little differently on Pikchr than it does on PIC.
+The change in behavior is deliberate, because we feel that the Pikchr
+approach is better.  On PIC, the diagram above would be rendered
+like this:
+
+~~~~~ pikchr toggle indent
+    right; box; circle; cylinder with .n at previous.e
+~~~~~
+
+But on Pikchr the placement of the cylinder is different:
+
+~~~~~ pikchr toggle indent
+    right; box; circle; cylinder with .n at previous.s
+~~~~~
+
+Let's take apart what is happening here.  In both systems, after
+the "circle" object has been parsed and positioned, the .end of
+the circle is the same as .e, because the layout direction is "right".
+If we omit the "down" and "cylinder" and draw a dot at the ".end" of
+circle to show where it is, we can see this:
+
+~~~~~ pikchr toggle indent
+    right; box; circle
+    dot color red at last circle.end
+~~~~~
+
+The next statement is "down".  The "down" statement changes the layout
+direction to "down" in both systems.  In legacy PIC the .end of the circle
+remains at the .e anchor.  Then when the "cylinder" is positioned,
+its ".start" is at .n because the layout direction is now "down",
+so the .n point of the cylinder is aligned to the .e point of
+the circle.
+
+Pikchr works like PIC with one important change: when the "down" statement
+is evaluated, Pikchr also moves the ".end" of the previous object
+to a new location that is appropriate for the new direction. In other
+words, the down command moves the .end of the circle from .e to .s.
+You can see this by setting a red dot at the .end of
+the circle *after* the "down" command:
+
+~~~~~ pikchr toggle indent
+    right; box; circle; down
+    dot color red at first circle.end
+~~~~~
+
+Or, we can "`print`" the coordinates of the .end of the circle before
+and after the "down" command to see that they shift:
+
+~~~~~ pikchr toggle indent
+    right; box; C1: circle
+    print "before: ", C1.end.x, ", ", C1.end.y
+    down
+    print "after: ", C1.end.x, ", ", C1.end.y
+~~~~~
+
+## Adjusting The Size Of Block Objects <a id="block-obj-size"></a>
+
+The size of every block object is controlled by three parameters:
+
+  *  `width` (often abbreviated as `wid`)
+  *  `height` (or `ht`)
+  *  `radius` (or `rad`)
+
+There is also a fourth convenience parameter:
+
+  *  `diameter`
+
+The `diameter` is always twice the radius. Setting the `diameter` automatically
+changes the `radius` and setting the `radius` automatically changes the
+`diameter`.
+
+Usually the meanings of these parameters are obvious.
+
+~~~~ pikchr toggle indent
+A: box thick
+line thin color gray left 70% from 2mm left of A.nw
+line same from 2mm left of A.sw
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below A.sw
+X2: line thin color gray down 50% from 2mm below A.se
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+~~~~
+
+The `radius` parameter, however, sometimes has non-obvious meanings.
+For example, on a box, the `radius` determines the rounding of corners:
+
+~~~~ pikchr toggle indent
+A: box thick rad 0.3*boxht
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.s)
+X4: line thin color gray right 70% from A.rad above start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X3) down 30% <-
+line thin color gray from (previous text,X4) up 30% <-
+~~~~
+
+For a [cylinder object](./cylinderobj.md) the `radius` determines the
+thickness of the end caps:
+
+~~~~ pikchr toggle indent
+A: cylinder thick rad 150%
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.ne)
+X4: line thin color gray right 70% from A.rad below start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X4) down 30% <-
+line thin color gray from (previous text,X3) up 30% <-
+~~~~
+
+For a [file object](./fileobj.md) the `radius` determines the size of
+the page fold-over in the upper-right corner:
+
+~~~~ pikchr toggle indent
+A: file thick rad 100%
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.n)
+X4: line thin color gray right 70% from A.rad below start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X4) down 30% <-
+line thin color gray from (previous text,X3) up 30% <-
+~~~~
+
+For a [circle object](./circleobj.md), the width, height, and diameter
+are always the same, and the radius is always half the diameter.  Changing
+any parameter automatically adjusts the other three.
+
+~~~~ pikchr toggle indent
+A: circle thick rad 120%
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+X3: line thin color gray right 70% from 2mm right of (A.e,A.s)
+X4: line thin color gray right 70% from A.rad above start of X3
+text "radius" at (6/8<X4.start,X4.end>,1/2<X3,X4>)
+line thin color gray from (previous,X3) down 30% <-
+line thin color gray from (previous text,X4) up 30% <-
+line thin color gray <-> from A.sw to A.ne
+line thin color gray from A.ne go 0.5*A.rad ne then 0.25*A.rad east
+text " diameter" ljust at end of previous line
+~~~~
+
+Even though they are curvy objects, the `radius` (and hence `diameter`)
+has no effect on [ellipse](./ellipseobj.md) and [oval](./ovalobj.md) objects.
+The size of those objects is determined purely by their width and height:
+
+~~~~ pikchr toggle indent
+A: ellipse thick
+line thin color gray left 70% from 2mm left of (A.w,A.n)
+line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<1st line,2ndline>)
+line thin color gray from previous text.n up until even with 1st line ->
+line thin color gray from previous text.s down until even with 2nd line ->
+X1: line thin color gray down 50% from 2mm below (A.w,A.s)
+X2: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X1,X2>,6/8<X1.start,X1.end>)
+line thin color gray from previous text.w left until even with X1 ->
+line thin color gray from previous text.e right until even with X2 ->
+~~~~
+
+~~~~ pikchr toggle indent
+A: oval thick
+X0: line thin color gray left 70% from 2mm left of (A.w,A.n)
+X1: line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<X0,X1>)
+line thin color gray from previous text.n up until even with X0 ->
+line thin color gray from previous text.s down until even with X1 ->
+X2: line thin color gray down 50% from 2mm below (A.w,A.s)
+X3: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" at (1/2<X2,X3>,6/8<X2.start,X2.end>)
+line thin color gray from previous text.w left until even with X2 ->
+line thin color gray from previous text.e right until even with X3 ->
+
+A: oval thick wid A.ht ht A.wid at 2.0*A.wid right of A
+X0: line thin color gray left 70% from 2mm left of (A.w,A.n)
+X1: line same from 2mm left of (A.w,A.s)
+text "height" at (7/8<previous.start,previous.end>,1/2<X0,X1>)
+line thin color gray from previous text.n up until even with X0 ->
+line thin color gray from previous text.s down until even with X1 ->
+X2: line thin color gray down 50% from 2mm below (A.w,A.s)
+X3: line thin color gray down 50% from 2mm below (A.e,A.s)
+text "width" small at (1/2<X2,X3>,6/8<X2.start,X2.end>)
+line thin color gray from previous text.w left until even with X2 ->
+line thin color gray from previous text.e right until even with X3 ->
+~~~~
+
+Notice that with an oval object, the semicircular end-cap is always
+on the narrow end of the object.  In the default configuration where
+the height is less than the width, the semicircular end-caps are on the
+left and right, but if the width and height are modified so that the
+width is less than the height, then semicircles appear on the top and
+bottom instead.
+
+
+### Default Sizes <a id="def-size"></a>
+
+Block objects have default sizes, which are determined by variables.
+For example, the width of a box is initialized with the value of the `boxwid`
+variable, which defaults to `0.75in`.
+
+It is common for Pikchr scripts
+to change these default at or near the beginning of a script in order to adjust
+the default sizes of objects defined within that script.
+
+### Setting Sizes Using Attributes <a id="size-attr"></a>
+
+Use the "`width`" (or "`wid`") attribute to change the width of an object.
+The argument to this attribute can be an expression — such as "`1cm`" or
+"`0.75*boxwid`" — or it can be a percentage of the prior value,
+such as "`75%`".  This also works for "`height`",
+"`radius`", and "`diameter`".
+
+### Automatic Sizing To Fit Text Annotations <a id="text-ann-size"></a>
+
+If a block object contains text annotations, the "`fit`" attribute causes
+the width and height to be adjusted so that the object neatly encloses that
+text.  The "`fit`" attribute only considers text that is previously defined
+for the object, or in other words text annotations that occur to the left
+of the "`fit`" keyword.  The width and height can be adjusted further after
+the "`fit`" keyword, for example to provide a larger margin around the
+text.  Click on the following script to see the difference that the
+"`width 125%`" at the end of the second box definition makes.
+
+~~~~ pikchr source toggle indent
+    down
+    box "Auto-fit text annotation" "as is" fit
+    move 50%
+    box "Auto-fix text annotation" "with 125% width" fit width 125%
+~~~~
+
+If at the end of a block object definition, either the width or height of the
+object is less than or equal to zero, then that dimension is increased so as to
+enclose all text annotations on the object.  Thus, for example, 
+you can make all of the
+boxes in your diagram auto-fit around their text annotations by prefacing
+your script with something like:
+
+~~~~ pikchr source toggle indent
+    boxwid = 0; boxht = 0;
+    box "Hello";
+    move
+    box "A longer label" "with multiple lines" "of label text"
+~~~~
+
+For all of these auto-fit features, Pikchr needs to know the dimensions of the
+text annotations after rendering.  Unfortunately, that information is not
+readily available, as Pikchr runs long before the generated SVG reaches the
+web-browser in which it will be displayed.  Hence, Pikchr has to guess at the
+text size.  Usually it does a good job of this, but it can be a little off,
+especially for unusual (read: "non-ASCII") characters or if the CSS for
+the rendering environment sets a non-standard font face or font size.  To
+compensate, the "`charwid`" and "`charht`" variables can be adjusted or
+extra spaces can be added at the beginning or end of text strings.
+
+These auto-fit features are a new innovation for Pikchr and are not available
+in other PIC family interpreters, as far as we are aware.
+
+## Attributes For Stroke Width And Drawing Colors <a id="stroke-attr"></a>
+
+Various attributes can be added to both block and line objects to influence
+how the objects are drawn.
+
+  *  `thickness` *dimension*
+  *  `thick`
+  *  `thin`
+  *  `invisible` (or `invis`)
+  *  `color` *color*
+  *  `fill` *color*
+
+The "`thickness`", "`thick`", "`thin`", and "`invisible`" attributes control
+the stroke width of the lines that construct an object.  The default stroke width
+for all objects is determined by the "`thickness`" variable, which defaults
+to "`0.015in`".  The "`thick`" and "`thin`" attributes increase or decrease
+the stroke width by fixed percentages.  These attributes can be repeated
+to make the stroke width ever thicker or thinner, up to the limit of the object’s
+dimensions where the stroke fills the entire object.  The "`invisble`" attribute
+simply sets the stroke width to 0.
+
+~~~~ pikchr toggle indent
+   boxwid = 0
+   boxht = 0
+   right
+   box "normal"
+   move
+   box "thin" thin
+   move
+   box "thick" thick
+   move
+   box "thick thick thick" thick thick thick
+   move
+   box "invisible" invisible
+~~~~
+
+Notice that “invisible” refers only to the object outline, not to the
+whole object. You therefore cancel the “invisible” attribute with
+“solid”, not “visible”:
+
+~~~~ pikchr toggle indent
+   boxwid = 0
+   boxht = 0
+   box "fully visible"
+   box invisible color gray "outline invisible"
+   box same solid "outline visible again" fit
+~~~~
+
+The "`color`" and "`fill`" attributes change the foreground and background
+colors of an object.  Colors can be expressed using any of [the 148 standard
+CSS color names][ccn] such as "Bisque" or "AliceBlue" or "LightGray".  Color
+names are not case sensitive, so "bisque", "BISQUE", and "Bisque" all mean
+the same thing.  Color names can also be expressed as an integer which is
+interpreted as a 24-bit RGB value.  It is convenient to express numeric
+color values using hexadecimal notation.  "Bisque" is the same as "0xffe4c4",
+which is the same as "16770244".  
+
+~~~~ pikchr toggle indent
+   box "Color: CadetBlue" "Fill: Bisque" fill Bisque color CadetBlue fit
+   move
+   oval "Color: White" "Fill: RoyalBlue" color White fill ROYALBLUE fit
+~~~~
+
+Setting the "`fill`" to a negative number, to "None", or to "Off" makes the
+background transparent.  That is the default.
+
+The default foreground color is black.
+
+[ccn]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
+
+### Filled Polygons <a id="filled-polys"></a>
+
+The "`fill`" attribute does not affect the rendering of lines unless the
+route of the line is terminated by the "`close`" attribute.  The "`close`"
+keyword converts the line into a polygon:
+
+~~~~ pikchr toggle indent
+   line go 3cm heading 150 then 3cm west close \
+                                      /* ^^^^^ nota bene! */ \
+       fill 0x006000 color White "green" below "triangle" below
+~~~~
+
+Polygons are not required to have a fill color. With the default fill of “none,”
+you can use the "`close`"
+keyword to convert a polygon into a line and leave the background transparent,
+but using "`fill` *color*" together with "`close`" is a common idiom.
+
+## Text Annotations <a id="text-ann"></a>
+
+Every object can have up to five lines of text annotation.  Each annotation
+is a string literal attribute on the object definition.  By default, the
+annotations are displayed around the center of the object, from top to bottom,
+in the order that they appear in the input script.
+
+~~~~ pikchr toggle indent
+   box "box containing" "three lines" "of text" fit
+   move
+   arrow "Labeled" "line" wid 200%
+~~~~
+
+## Text Attributes <a id="text-attr"></a>
+
+The layout and font style of the annotations can be modified using keywords
+that appear after each string literal.  The following modifiers are supported:
+
+  * **above**
+  * **aligned**
+  * **below**
+  * **big**
+  * **bold**
+  * **center**
+  * **italic**
+  * **ljust**
+  * **rjust**
+  * **small**
+
+### Position Text Above Or Below The Center Of The Object <a id="text-pos"></a>
+
+The "`above`" and "`below`" keywords control the location of the
+text above or below the center point of the object with which
+the text is associated.  If there is just one text on the object
+and the "`above`" and "`below`" keywords are omitted, the text is
+placed directly over the center of the object.  This causes
+the text to appear in the middle of lines:
+
+~~~~ pikchr indent toggle
+  line "on the line" wid 150%
+~~~~
+
+So, if there is just a single text label on a line, you probably
+want to include either the "`above`" or "`below`" keyword.
+
+~~~~ pikchr indent toggle
+  line "above" above; move; line "below" below
+~~~~
+
+If there are two texts on the object, they straddle the center point
+above and below, even without the use of the "`above`" and "`below`"
+keywords:
+
+~~~~ pikchr indent toggle
+  line wid 300% "text without \"above\"" "text without \"below\""
+~~~~
+
+The "`above`" and "`below`" attributes do not stack or accumulate.  Each "`above`"
+or "`below`" overrides any previous "`above`" or "`below`" for the same text.
+
+If there are multiple texts and all are marked "`above`" or "`below`", then
+all are placed above or below the center point, in order of appearance.
+
+~~~~ pikchr indent toggle
+  line width 200% "first above" above "second above" above
+  move
+  line same "first below" below "second below" below
+~~~~
+
+### Justify Text Left Or Right <a id="text-just"></a>
+
+As the "`above`" and "`below`" keywords control up and down positioning of
+the text, so the "`ljust`" and "`rjust`" keywords control left and right
+positioning.
+
+For a line, the "`ljust`" means that the left side of the text is flush
+against the center point of the line, and "`rjust`" means that the right
+side of the text is flush against the center point of the line.
+(In the following diagram, the red dot is at the center of the line.)
+
+~~~~ pikchr indent toggle
+   line wid 200% "ljust" ljust above "rjust" rjust below
+   dot color red at previous.c
+~~~~
+
+For a block object, "`ljust`" shifts the text to be left justified
+against the left edge of the block (with a small margin) and
+"`rjust`" puts the text against the right side of the object (with
+the same margin).
+
+~~~~ pikchr indent toggle
+   box "ljust" ljust "longer line" ljust "even longer line" ljust fit
+   move
+   box "rjust" rjust "longer line" rjust "even longer line" rjust fit
+~~~~
+
+The behavior of "`ljust`" and "`rjust`" for block objects in Pikchr differs
+from legacy PIC.
+In PIC, text is always justified around the center point, as in lines,
+but this means there is no easy way to left justify multiple lines of
+text within a "box" or "file", so the behavior was changed for
+Pikchr.
+
+Pikchr allows five separate text objects inside another object by combining
+"`ljust`", "`rjust`", and the default text centering:
+
+~~~~ pikchr indent toggle
+  box wid 300% \
+     "above-ljust" above ljust \
+     "above-rjust" above rjust \
+     "centered" center \
+     "below-ljust" below ljust \
+     "below-rjust" below rjust
+~~~~
+
+### Text Attribute "center" <a id="text-center"></a>
+
+The "`center`" attribute cancels all prior "`above`", "`below`", "`ljust`", and
+"`rjust`" attributes for the current text object.
+
+### Bold And Italic Font Styles <a id="font-style"></a>
+
+The "`bold`" and "`italic`" attributes cause the text object to use a bold or
+italic font.  Fonts can be both bold and italic at the same time:
+
+~~~~ pikchr indent toggle
+  box "bold" bold "italic" italic "bold-italic" bold italic fit
+~~~~
+
+### Aligned Text <a id="text-align"></a>
+
+The "`aligned`" attribute causes text associated with a straight line
+to be rotated to align with that line:
+
+~~~~ pikchr indent toggle
+  arrow go 150% heading 30 "aligned" aligned above
+  move to 1cm east of previous.end
+  arrow go 150% heading 170 "aligned" aligned above
+  move to 1cm east of previous.end
+  arrow go 150% north "aligned" aligned above
+~~~~
+
+To display rotated text not associated with a line, attach the
+text to a line that is marked "`invisible`"
+
+~~~~ pikchr indent toggle
+  box ht 200% wid 50%
+  line invis from previous.s to previous.n "rotated text" aligned
+~~~~
+
+Note that the direction of aligned text is the same as the direction of
+the line itself, so if you draw a line from right to left, the aligned
+text will appear upside down:
+
+~~~~ pikchr indent toggle
+  circle "C1" fit
+  circle "C0" at C1+(2.5cm,-0.3cm) fit
+  arrow from C0 to C1 "aligned" aligned above chop
+~~~~
+
+If you need aligned text on an arrow that goes from right to left,
+and you do not want the text to be rendered upside-down, draw
+the arrow from left to right and include the "`<-`" attribute
+so that the arrowhead is at the beginning rather than at the end:
+
+~~~~ pikchr indent toggle
+  circle "C1" fit
+  circle "C0" at C1+(2.5cm,-0.3cm) fit
+  arrow from C1 to C0 "aligned" aligned above <- chop
+~~~~
+
+### Adjusting The Font Size <a id="font-size"></a>
+
+The "`big`" and "`small`" attributes cause the text to be a little larger
+or a little smaller, respectively.  Two "`big`" attributes cause the
+text to be larger still, as do two "`small`" attributes. Text
+size does not increase or decrease beyond two "`big`" or "`small`" keywords.
+
+~~~~ pikchr indent toggle
+  box "small small" small small "small" small \
+    "(normal)" italic \
+    "big" big "big big" big big ht 200%
+~~~~
+
+A "`big`" keyword cancels any prior "`small`" keywords on the same text,
+and a "`small`" keyword cancels any prior "`big`" keywords.
+
+
+## Text Is Positioned Around The Center Of The Object <a id="text-center"></a>
+
+The anchor point for text annotations is usually the center of the bounding box for
+the whole object.  This is intuitive for block objects and straight lines.
+But for multi-segment lines, the text might not be near
+the line itself.  For example, in the following four-segment arrow,
+the red box is the bounding box and the red dot shows the center of the
+bounding box.  The text label is aligned relative to the center of the
+bounding box, which is not close to any part of the actual line.
+
+~~~~ pikchr toggle indent
+arrow up 1.5cm right 1.5cm then down .5cm right 1cm then up .5cm right .3cm \
+   then down 2.5cm right 1cm "text"
+box color red thin thin width previous.wid height previous.ht \
+   with .c at previous.c
+dot at last arrow.c color red behind last arrow
+~~~~
+
+If you need to position text beside one specific segment of a multi-segment
+line, consider creating a separate "`invis`" line over top of that line
+segment and attaching the text to the "invis" line instead.  Here is the
+same arrow as before, but with the text attached to a separate "invis" line
+that overlays the second segment of the arrow:
+
+~~~~ pikchr toggle indent
+arrow up 1.5cm right 1.5cm then down .5cm right 1cm then up .5cm right .3cm \
+   then down 2.5cm right 1cm
+box color red thin thin width previous.wid height previous.ht \
+   with .c at previous.c
+dot at last arrow.c color red behind last arrow
+line invis from 2nd vertex of last arrow to 3rd vertex of last arrow \
+   "text" below aligned
+~~~~
+
+The anchor point for text is *usually* the center of the object, but
+in some cases, the anchor point might be fudged a little.  This happens,
+for example for cylinder objects:
+
+~~~ pikchr toggle indent
+C1: cylinder "text in a" "cylinder" rad 120%
+    dot color red at C1.c
+    dot color blue at 0.75*C1.rad below C1.c
+~~~
+
+The red dot is on the center of the cylinder and the blue dot shows the
+anchor point for the text.  The text is a little lower for cylinders because
+that looks better.  With out this adjustment of the text center point, the
+cylinder text would look goofy:
+
+~~~ pikchr toggle indent
+C1: cylinder rad 120%
+    text "text in a" "cylinder" at C1.c
+~~~
+
+# Containers
+
+A "container" is a list of one or more objects contained within "`[`...`]`".
+A container is a collection of one or more objects that looks like a single
+object to the remainder of the script.
+
+For example:
+
+~~~ pikchr toggle source indent
+    A: [
+      oval "Hello"
+      arrow
+      box "World" radius 4px
+    ]
+    Border: box thin width A.width+0.5in height A.height+0.5in at A.center
+~~~
+
+The "A" container is composed of an oval, and arrow, and a box with rounded
+corners.  But to the subsequent "Border" box, the "A" container appears to
+be a single object.  The Border box can reference the overall width and height
+and the center point of the A container in order to size and position itself to
+enclose the container with a 0.25in border:
+
+~~~ pikchr toggle indent
+    A: [
+      oval "Hello"
+      arrow
+      box "World" radius 4px
+    ]
+    Border: box thin width A.width+0.5in height A.height+0.5in at A.center
+~~~
+
+A container is mostly useful for adding a border around a collection of
+objects, as shown above, or adding a caption to a diagram.  The following
+diagram shows what a caption might look like.
+(Click to see the Pikchr source text.)
+
+~~~ pikchr toggle indent
+    A: [
+      oval "Hello"
+      arrow
+      box "World" radius 4px
+    ]
+    Caption: text "Diagram Caption" italic with .n at 0.1in below A.s
+~~~
+
+
+In legacy PIC, layout direction changes (the "up", "down", "left", and "right"
+commands) and variable definitions within a container only affect subsequent
+statements within the same container.  Once the container closes, the prior direction
+and variable values are restored.  Pikchr does not work this way.  In
+Pikchr, layout direction changes and variable definitions that occur within
+a container continue to be in effect after the container.
+
+# Object Stacking Order
+
+Objects are normally drawn in the order in which they are defined.  Thus
+objects that are toward the end of the script that overlap with objects
+near the beginning will appear to be on top.
+
+Sometimes it is desirable for a later object to appear to be behind an
+early object.  This is useful, for example, when using a box to form a
+colored background for a group of prior objects.  The
+["`behind`" attribute](./behind.md) with a argument that is the label
+for any prior object can be added to cause the object that holds the
+behind attribute to be drawn before the object that the behind
+attribute references.
+
+# Summary And Conclusion
+
+Though based on the decades-old PIC language, Pikchr itself is a relatively
+new system.  However it has already proven itself to be amazingly useful for
+helping to illustrate concepts in technical documentation written using Markdown.
+
+This document has provided an overview of how Pikchr works.  For more
+details and other perspectives, see the following resources:
+
+  *  [The original Kernighan paper on PIC](/uv/pic.pdf)
+  *  [DPIC documentation](/uv/dpic-doc.pdf)
+  *  [ESR's documentation on GnuPIC](/uv/gpic.pdf)
+  *  [Pikchr Language Spec](./grammar.md)
+  *  [Differences between PIC and Pikchr](./differences.md)
+  *  [The scope and purpose of Pikchr](./purpose.md)
+  *  [Step-by-step example of writing a Pikchr document](./teardown01.md)

+ 91 - 0
pikchr.mod/pikchr/examples/_txt2js.bash

@@ -0,0 +1,91 @@
+#!/bin/bash
+########################################################################
+# Converts pikchr-format text files into a form usable by the
+# Fossil SCM's pikchrshow page's "example scripts" JS code.
+#
+# Usage: $0 [options] [file1.pikchr [...fileN.pikchr]]
+#
+# Options:
+#
+#  -o outfile, defaulting to /dev/stdout
+#
+# Its list of files defaults to $(ls -1 *.pikchr | sort).
+########################################################################
+
+function die(){
+    local rc=$1
+    shift
+    echo "$@" 1>&2
+    exit $rc
+}
+
+optOutfile=/dev/stdout
+scriptList=()
+while [[ x != "x$1" ]]; do
+    case "$1" in
+        -o) shift
+            optOutfile="$1"
+            shift
+            [[ x = "x${optOutfile}" ]] && die 1 "Missing filename for -o arg."
+            ;;
+        *) scriptList+=("$1")
+           shift
+    esac
+done
+
+[[ 0 = ${#scriptList[@]} ]] && {
+    scriptList=( $(ls -1 *.pikchr | sort) )
+    [[ 0 = ${#scriptList[@]} ]] && die 1 "Cannot find any *.pikchr files."
+}
+
+########################################################################
+# Optional *brief* user-friendly descriptive name of test files, in
+# the form desc_TESTNAME="name", where TESTNAME is the base filename
+# part of an input file. If none is set, the file is grepped for
+# a line with:
+#
+#  demo label: ...
+#
+# and if set, that is used. The default friendly name is that base
+# filename. These names are the ones shown in pikchrshow's example
+# script selection list.
+#desc_objects="Core object types"
+#desc_swimlane="Swimlanes"
+#desc_headings01="Cardinal headings"
+########################################################################
+
+#echo "scriptList=${scriptList[@]}"
+#echo optOutfile=${optOutfile}
+########################################################################
+# Output 1 JS object per file, comma-separated:
+#
+#  {name: "filename or desc_BASENAME value",
+#   code: `file content`}
+#
+# Note that the output is intended for embedding in an array but does
+# not emit the [...] part itself because of how its output is used.
+{
+    n=0 # object count
+    for f in ${scriptList[@]}; do
+        [[ -f "$f" ]] || die $? "Missing file: $f"
+        fb=${f%%.pikchr}
+        fb=${fb##*/}
+        descVar=desc_${fb}
+        desc=${!descVar}
+        if [[ x = "x${desc}" ]]; then
+            desc=$(awk -F: '/demo label: */{gsub(/^ /, "", $2); print $2}' < "$f")
+            if [[ x = "x${desc}" ]]; then
+                desc="$fb"
+            fi
+        fi
+        #echo f=${f} fb=${fb} descV=${descV} desc=$desc
+        [[ $n -gt 0 ]] && echo -n ","
+        echo -n '{name:"'${desc}'",'
+        echo -n 'code:`'
+        cat $f
+        echo -n '`}'
+        n=$((n + 1))
+    done
+    echo
+} > "${optOutfile}"
+#echo "Done: ${n} file(s) processes. Output is in ${optOutfile}."

+ 20 - 0
pikchr.mod/pikchr/examples/headings01.pikchr

@@ -0,0 +1,20 @@
+# demo label: Cardinal headings
+   linerad = 5px
+C: circle "Center" rad 150%
+   circle "N"  at 1.0 n  of C; arrow from C to last chop ->
+   circle "NE" at 1.0 ne of C; arrow from C to last chop <-
+   circle "E"  at 1.0 e  of C; arrow from C to last chop <->
+   circle "SE" at 1.0 se of C; arrow from C to last chop ->
+   circle "S"  at 1.0 s  of C; arrow from C to last chop <-
+   circle "SW" at 1.0 sw of C; arrow from C to last chop <->
+   circle "W"  at 1.0 w  of C; arrow from C to last chop ->
+   circle "NW" at 1.0 nw of C; arrow from C to last chop <-
+   arrow from 2nd circle to 3rd circle chop
+   arrow from 4th circle to 3rd circle chop
+   arrow from SW to S chop <->
+   circle "ESE" at 2.0 heading 112.5 from Center \
+      thickness 150% fill lightblue radius 75%
+   arrow from Center to ESE thickness 150% <-> chop
+   arrow from ESE up 1.35 then to NE chop
+   line dashed <- from E.e to (ESE.x,E.y)
+   line dotted <-> thickness 50% from N to NW chop

+ 52 - 0
pikchr.mod/pikchr/examples/objects.pikchr

@@ -0,0 +1,52 @@
+# demo label: Object types
+AllObjects: [
+
+# First row of objects
+box "box"
+box rad 10px "box (with" "rounded" "corners)" at 1in right of previous
+circle "circle" at 1in right of previous
+ellipse "ellipse" at 1in right of previous
+
+# second row of objects
+OVAL1: oval "oval" at 1in below first box
+oval "(tall &amp;" "thin)" "oval" width OVAL1.height height OVAL1.width \
+    at 1in right of previous
+cylinder "cylinder" at 1in right of previous
+file "file" at 1in right of previous
+
+# third row shows line-type objects
+dot "dot" above at 1in below first oval
+line right from 1.8cm right of previous "lines" above
+arrow right from 1.8cm right of previous "arrows" above
+spline from 1.8cm right of previous \
+   go right .15 then .3 heading 30 then .5 heading 160 then .4 heading 20 \
+   then right .15
+"splines" at 3rd vertex of previous
+
+# The third vertex of the spline is not actually on the drawn
+# curve.  The third vertex is a control point.  To see its actual
+# position, uncomment the following line:
+#dot color red at 3rd vertex of previous spline
+
+# Draw various lines below the first line
+line dashed right from 0.3cm below start of previous line
+line dotted right from 0.3cm below start of previous
+line thin   right from 0.3cm below start of previous
+line thick  right from 0.3cm below start of previous
+
+
+# Draw arrows with different arrowhead configurations below
+# the first arrow
+arrow <-  right from 0.4cm below start of previous arrow
+arrow <-> right from 0.4cm below start of previous
+
+# Draw splines with different arrowhead configurations below
+# the first spline
+spline same from .4cm below start of first spline ->
+spline same from .4cm below start of previous <-
+spline same from .4cm below start of previous <->
+
+] # end of AllObjects
+
+# Label the whole diagram
+text "Examples Of Pikchr Objects" big bold  at .8cm above north of AllObjects

+ 64 - 0
pikchr.mod/pikchr/examples/swimlane.pikchr

@@ -0,0 +1,64 @@
+# demo label: Swimlanes
+    $laneh = 0.75
+
+    # Draw the lanes
+    down
+    box width 3.5in height $laneh fill 0xacc9e3
+    box same fill 0xc5d8ef
+    box same as first box
+    box same as 2nd box
+    line from 1st box.sw+(0.2,0) up until even with 1st box.n \
+      "Alan" above aligned
+    line from 2nd box.sw+(0.2,0) up until even with 2nd box.n \
+      "Betty" above aligned
+    line from 3rd box.sw+(0.2,0) up until even with 3rd box.n \
+      "Charlie" above aligned
+    line from 4th box.sw+(0.2,0) up until even with 4th box.n \
+       "Darlene" above aligned
+
+    # fill in content for the Alice lane
+    right
+A1: circle rad 0.1in at end of first line + (0.2,-0.2) \
+       fill white thickness 1.5px "1" 
+    arrow right 50%
+    circle same "2"
+    arrow right until even with first box.e - (0.65,0.0)
+    ellipse "future" fit fill white height 0.2 width 0.5 thickness 1.5px
+A3: circle same at A1+(0.8,-0.3) "3" fill 0xc0c0c0
+    arrow from A1 to last circle chop "fork!" below aligned
+
+    # content for the Betty lane
+B1: circle same as A1 at A1-(0,$laneh) "1"
+    arrow right 50%
+    circle same "2"
+    arrow right until even with first ellipse.w
+    ellipse same "future"
+B3: circle same at A3-(0,$laneh) "3"
+    arrow right 50%
+    circle same as A3 "4"
+    arrow from B1 to 2nd last circle chop
+
+    # content for the Charlie lane
+C1: circle same as A1 at B1-(0,$laneh) "1"
+    arrow 50%
+    circle same "2"
+    arrow right 0.8in "goes" "offline"
+C5: circle same as A3 "5"
+    arrow right until even with first ellipse.w \
+      "back online" above "pushes 5" below "pulls 3 &amp; 4" below
+    ellipse same "future"
+
+    # content for the Darlene lane
+D1: circle same as A1 at C1-(0,$laneh) "1"
+    arrow 50%
+    circle same "2"
+    arrow right until even with C5.w
+    circle same "5"
+    arrow 50%
+    circle same as A3 "6"
+    arrow right until even with first ellipse.w
+    ellipse same "future"
+D3: circle same as B3 at B3-(0,2*$laneh) "3"
+    arrow 50%
+    circle same "4"
+    arrow from D1 to D3 chop

+ 66 - 0
pikchr.mod/pikchr/fiddle/GNUmakefile

@@ -0,0 +1,66 @@
+# This GNU makefile exists primarily to simplify/speed up development
+# from emacs. It is not part of the canonical build process.
+default: fiddle
+
+fiddle_module_js := pikchr.js
+fiddle_module_wasm := pikchr.wasm
+fiddle_generated := $(fiddle_module_js) $(fiddle_module_wasm)
+pikchr_c := ../pikchr.c
+
+ifneq (,$(wildcard /home/stephan))
+  emcc_opt ?= -O0
+else
+  emcc_opt ?= -Oz
+endif
+
+$(pikchr_c):
+	$(MAKE) -C ..
+
+$(fiddle_module_js): $(pikchr_c) GNUmakefile
+	emcc -o $@ $(emcc_opt) --no-entry \
+        -sEXPORTED_RUNTIME_METHODS=cwrap,setValue,getValue,stackSave,stackRestore \
+        -sEXPORTED_FUNCTIONS=_pikchr $(pikchr_c) \
+        -sENVIRONMENT=web \
+        -sMODULARIZE \
+        -sEXPORT_NAME=initPikchrModule \
+        --minify 0;
+	gzip -c < $@ > [email protected]
+	gzip -c < pikchr.wasm > pikchr.wasm.gz
+pikchr.wasm: $(fiddle_module_js)
+pikchr.js.gz: $(fiddle_module_js)
+pikchr.wasm.gz: $(fiddle_module_js)
+fiddle.js:
+fiddle.js.gz: fiddle.js
+	gzip -c < $< > $@
+
+fiddle_compressed := pikchr.js.gz pikchr.wasm.gz fiddle.js.gz
+fiddle_generated += $(fiddle_compressed)
+fiddle_files = emscripten.css fiddle.html \
+             fiddle.js pikchr.js \
+             pikchr.wasm pikchr-worker.js \
+             $(fiddle_compressed)
+
+fiddle: $(fiddle_compressed)
+clean:
+	rm -f $(fiddle_generated)
+disclean: clean
+
+# fiddle_remote is the remote destination for the fiddle app. It
+# must be a [user@]HOST:/path for rsync.
+# Note that the target "should probably" contain a symlink of
+# index.html -> fiddle.html.
+fiddle_remote ?=
+ifeq (,$(fiddle_remote))
+ifneq (,$(wildcard /home/stephan))
+  fiddle_remote = wh2:www/wh/pikchr/.
+else ifneq (,$(wildcard /home/drh))
+  #fiddle_remote = if appropriate, add that user@host:/path here
+endif
+endif
+
+push-fiddle: $(fiddle_files)
+	@if [ x = "x$(fiddle_remote)" ]; then \
+		echo "fiddle_remote must be a [user@]HOST:/path for rsync"; \
+		exit 1; \
+	fi
+	rsync -va $(fiddle_files) $(fiddle_remote)

+ 110 - 0
pikchr.mod/pikchr/fiddle/README.md

@@ -0,0 +1,110 @@
+# Building and Running the pikchr Fiddle App
+
+This application uses a [WebAssembly][wasm], a.k.a. WASM, build of
+pikchr to provide a so-called "fiddle" app, for "fiddling around" with
+pikchr. It requires:
+
+- The [Emscripten][emscripten] SDK (see below)
+- A modern browser
+
+## Setting up Emscripten
+
+First, install the Emscripten SDK, as documented
+[here](https://emscripten.org/docs/getting_started/downloads.html) and summarized
+below for Linux environments:
+
+```
+# Clone the emscripten repository:
+$ git clone https://github.com/emscripten-core/emsdk.git
+$ cd emsdk
+
+# Download and install the latest SDK tools:
+$ ./emsdk install latest
+
+# Make the "latest" SDK "active" for the current user:
+$ ./emsdk activate latest
+```
+
+Those parts only need to be run once. To update that tree to
+the latest version, use:
+
+```
+$ git pull
+$ ./emsdk install latest
+$ ./emsdk activate latest
+```
+
+## Setting up the EMSDK Environment
+
+The following needs to be run for each shell instance which needs the
+`emcc` compiler:
+
+```
+# Activate PATH and other environment variables in the current terminal:
+$ source /path/tp/emsdk/emsdk_env.sh
+
+$ which emcc
+/path/to/emsdk/upstream/emscripten/emcc
+```
+
+## Building with non-Emscripten WASM Toolchains
+
+Adding alternate builds for non-Emscripten toolchains is on the
+TODO list. Doing so requires:
+
+- WASM builds of libc and libm. [wasi-sdk][] "should" be suitable but
+  is as yet untested.
+- Replacing much of the "glue code" which Emscripten installs. This
+  work has already been done in other trees closely related to this
+  project.
+
+
+# Building Fiddle
+
+The makefile for the fiddle app requires GNU Make (installed as "make"
+on Linux and typically "gmake" on BSD systems).  From the top of the
+pikchr tree, simply do:
+
+
+```
+$ source /path/to/emsdk/emsdk_env.sh # see previous section
+$ cd fiddle
+$ make
+# or, depending on the platform (e.g. BSD):
+$ gmake
+```
+
+The EMSDK environment setup is only needed for building, not running
+the app.
+
+# Running Fiddle (on Unix Systems)
+
+Due to limitations in WASM module loading in some browsers,
+the fiddle app must be run via an HTTP server rather than by
+opening its HTML file directly. There are many options for doing so,
+but one of the simplest is to use [althttpd][]:
+
+```
+$ althttpd -page fiddle.html
+```
+
+That will start up a local HTTP server on the next available network
+port at or above 8080, start up your browser, and point it to
+`http://localhost:THAT_PORT/fiddle.html`.
+
+# Exploring the Code
+
+From highest level to lowest:
+
+- `fiddle.html` is the entry point for...
+- `fiddle.js` is the main hand-written JS part of the app. It loads...
+- `pikchr-worker.js` is a hand-written Worker-API binding to...
+- `pikchr.js` is the Emscripten-generated module code. It loads
+  `pikchr.wasm` (the WASM-compiled from of `pikchr.c`) and makes it
+  available to JS.
+
+
+[wasm]: https://developer.mozilla.org/en-US/docs/WebAssembly
+[emscripten]: https://emscripten.org/
+[althttpd]: https://sqlite.org/althttpd
+[wasi-sdk]: https://github.com/WebAssembly/wasi-sdk

+ 24 - 0
pikchr.mod/pikchr/fiddle/emscripten.css

@@ -0,0 +1,24 @@
+/* emcscript-related styling, used during the module load/intialization processes... */
+.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
+div.emscripten { text-align: center; }
+div.emscripten_border { border: 1px solid black; }
+#module-spinner { overflow: visible; }
+#module-spinner > * {
+    margin-top: 1em;
+}
+.spinner {
+    height: 50px;
+    width: 50px;
+    margin: 0px auto;
+    animation: rotation 0.8s linear infinite;
+    border-left: 10px solid rgb(0,150,240);
+    border-right: 10px solid rgb(0,150,240);
+    border-bottom: 10px solid rgb(0,150,240);
+    border-top: 10px solid rgb(100,0,200);
+    border-radius: 100%;
+    background-color: rgb(200,100,250);
+}
+@keyframes rotation {
+    from {transform: rotate(0deg);}
+    to {transform: rotate(360deg);}
+}

+ 264 - 0
pikchr.mod/pikchr/fiddle/fiddle.html

@@ -0,0 +1,264 @@
+<!doctype html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>pikchr fiddle</title>
+    <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
+    <link rel="stylesheet" href="emscripten.css"/>
+    <style>
+      /* The following styles are for app-level use. */
+      textarea {
+          font-family: monospace;
+          flex: 1 1 auto;
+      }
+      header {
+          font-size: 130%;
+          font-weight: bold;
+      }
+      #main-wrapper {
+          display: flex;
+          flex-direction: column-reverse;
+          flex: 1 1 auto;
+      }
+      #main-wrapper.side-by-side {
+          flex-direction: row-reverse;
+      }
+      #main-wrapper.swapio {
+          flex-direction: column;
+      }
+      #main-wrapper.side-by-side.swapio {
+          flex-direction: row;
+      }
+      .zone-wrapper{
+          display: flex;
+          flex-direction: column;
+          align-items: stretch;
+          margin: 0 0.25em;
+          flex: 1 1 auto;
+      }
+      .zone-wrapper.output {
+      }
+      #pikchr-output {
+          /*flex: 1 1 auto;*/
+          /*overflow: auto;*/
+          padding: 0;
+          margin: 0;
+      }
+      #pikchr-output-wrapper {
+          flex: 1 1 auto;
+          overflow: auto;
+      }
+      #pikchr-output-wrapper.text {
+          display: flex;
+          align-items: stretch;
+      }
+      #pikchr-output-wrapper.text > #pikchr-output {
+          display: flex;
+          align-items: stretch;
+          flex: 1 1 auto;
+      }
+      #pikchr-output-wrapper.text > #pikchr-output > textarea {
+          flex: 1 1 auto;
+      }
+      .zone-wrapper textarea {
+          font-size: 110%;
+          filter: invert(100%);
+          flex: 10 1 auto;
+          font-family: monospace;
+      }
+      .zone-wrapper.input {
+          /*min-height: 10em;*/
+          min-width: 20em;
+      }
+      .zone-wrapper.output {
+          overflow: auto;
+          justify-content: space-between;
+      }
+      .button-bar {
+          display: flex;
+          justify-content: center;
+          flex: 0 1 auto;
+          flex-wrap: wrap;
+      }
+      .button-bar button {
+          margin: 0.25em 1em;
+      }
+      label[for] {
+          cursor: pointer;
+      }
+      .error {
+          color: red;
+          background-color: yellow;
+      }
+      .hidden, .initially-hidden {
+          position: absolute !important;
+          opacity: 0 !important;
+          pointer-events: none !important;
+          display: none !important;
+      }
+      /* Safari supports neither styling of nor event handling on a
+         fieldset legend, so we emulate a fieldset-like widget. */
+      .fieldset {
+          border-radius: 0.5em;
+          border: 1px inset;
+          display: flex;
+          flex-direction: column;
+      }
+      .fieldset > .legend {
+          position: relative;
+          top: -1.5ex;
+          padding: 0 0.5em;
+          font-size: 85%;
+          margin-left: 0.5em;
+          flex: 0 1 auto;
+          align-self: self-start;
+          cursor: pointer;
+      }
+      .fieldset.options > div {
+          display: flex;
+          flex-wrap: wrap;
+          font-size: 70%;
+          margin: 0 0.5em 0.5em 0.5em;
+      }
+      .fieldset > .legend > span {
+          position: relative;
+      }
+      .fieldset > .legend::before {
+          /* Hide the parent element's top border where this
+             element intersects it. */
+          content: ' '; 
+          width: 100%; 
+          height: 100%; 
+          background-color: white
+           /* REALLY want to 'inherit' the color from the fieldset's
+              parent, but inherit leads to a transparent bg, which is
+              exactly what we're trying to avoid here. */;
+          opacity: 1;
+          position: absolute; 
+          top: 0; 
+          left: 0; 
+      }
+      .fieldset > .legend::after {
+          content: " [hide]";
+          position: relative;
+      }
+      .fieldset.collapsed > .legend::after {
+          content: " [show]";
+          position: relative;
+      }
+      span.labeled-input {
+          padding: 0.25em;
+          margin: 0.25em 0.5em;
+          border-radius: 0.25em;
+          white-space: nowrap;
+          background: #0002;
+      }
+      .center { text-align: center; }
+      .app-view {
+          flex: 20 1 auto;
+      }
+      #titlebar {
+          display: flex;
+          justify-content: space-between;
+          margin-bottom: 0.5em;
+      }
+      #view-split {
+          display: flex;
+          flex-direction: column-reverse;
+      }
+      #view-split > .fieldset.options {
+          margin-top: 0.5em;
+      }
+    </style>
+  </head>
+  <body>
+    <header id='titlebar'><span>pikchr fiddle</span></header>
+    <!-- emscripten bits -->
+    <figure id="module-spinner">
+      <div class="spinner"></div>
+      <div class='center'><strong>Initializing app...</strong></div>
+      <div class='center'>
+        On a slow internet connection this may take a moment.  If this
+        message displays for "a long time", intialization may have
+        failed and the JavaScript console may contain clues as to why.
+      </div>
+    </figure>
+    <div class="emscripten" id="module-status">Downloading...</div>
+    <div class="emscripten">
+      <progress value="0" max="100" id="module-progress" hidden='1'></progress>
+    </div><!-- /emscripten bits -->
+    <div id='view-split' class='app-view initially-hidden'>
+      <div class='fieldset options collapsible'>
+        <span class='legend'><span>Options</span></span>
+        <div>
+          <span class='labeled-input'>
+            <input type='checkbox' id='opt-cb-sbs'
+                   data-csstgt='#main-wrapper'
+                   data-cssclass='side-by-side'
+                   data-config='sideBySide'>
+            <label for='opt-cb-sbs'>Side-by-side</label>
+          </span>
+          <span class='labeled-input'>
+            <input type='checkbox' id='opt-cb-swapio'
+                   data-csstgt='#main-wrapper'
+                   data-cssclass='swapio'
+                   data-config='swapInOut'>
+            <label for='opt-cb-swapio'>Swap in/out</label>
+          </span>
+          <span class='labeled-input'>
+            <input type='checkbox' id='opt-cb-autoscale'
+                   data-config='renderAutoScale'>
+            <label for='opt-cb-autoscale'>Auto-scale SVG</label>
+          </span>
+          <span class='labeled-input'>
+            <input type='checkbox' id='opt-cb-autorender'
+                   data-config='renderWhileTyping'>
+            <label for='opt-cb-autorender'>Render while typing</label>
+          </span>
+          <!--span class='labeled-input'>
+            <input type='file' id='load-pikchr'/>
+            <label>Load pikchr</label>
+          </span-->
+          <!--span class='labeled-input'>
+            <select id='select-examples'></select>
+          </span-->
+        </div>
+      </div><!-- .fieldset -->
+      <div id='main-wrapper' class=''>
+        <div class='zone-wrapper input'>
+          <textarea id="input"
+                    placeholder="Shell input. Ctrl-enter/shift-enter runs it.">
+/**
+  Use ctrl-enter or shift-enter to execute
+  pikchr code. If only a subset is currently
+  selected, only that part is evaluated.
+
+  TODOs include, but are not limited to...:
+  - Solve various layout quirks, in particular
+    in auto-render mode.
+*/
+arrow right 200% "Markdown" "Source"
+box rad 10px "Markdown" "Formatter" "(markdown.c)" fit
+arrow right 200% "HTML+SVG" "Output"
+arrow <-> down from last box.s
+box same "Pikchr" "Formatter" "(pikchr.c)" fit
+</textarea>
+          <div class='button-bar'>
+            <button id='btn-render'>Render</button>
+            <button id='btn-clear'>Clear Input</button>
+          </div>
+        </div>
+        <div class='zone-wrapper output'>
+          <div id='pikchr-output-wrapper'>
+            <div id='pikchr-output'></div>
+          </div>
+          <div class='button-bar'>
+            <button id='btn-render-mode'>Toggle Render Mode</button>
+          </div>
+        </div>
+      </div>
+    </div> <!-- #view-split -->
+    <script src="fiddle.js"></script>
+  </body>
+</html>

+ 691 - 0
pikchr.mod/pikchr/fiddle/fiddle.js

@@ -0,0 +1,691 @@
+/*
+  2022-05-20
+
+  The author disclaims copyright to this source code.  In place of a
+  legal notice, here is a blessing:
+
+  *   May you do good and not evil.
+  *   May you find forgiveness for yourself and forgive others.
+  *   May you share freely, never taking more than you give.
+
+  ***********************************************************************
+
+  This is the main entry point for the pikchr fiddle app. It sets up the
+  various UI bits, loads a Worker for the pikchr process, and manages the
+  communication between the UI and worker.
+*/
+(function(){
+    'use strict';
+
+    /* Recall that the 'self' symbol, except where locally
+       overwritten, refers to the global window or worker object. */
+
+    const storage = (function(NS/*namespace object in which to store this module*/){
+        /* Pedantic licensing note: this code originated in the Fossil SCM
+           source tree, where it has a different license, but the person who
+           ported it into here is the same one who wrote it for fossil. */
+        'use strict';
+        NS = NS||{};
+
+        /**
+           This module provides a basic wrapper around localStorage
+           or sessionStorage or a dummy proxy object if neither
+           of those are available.
+        */
+        const tryStorage = function f(obj){
+            if(!f.key) f.key = 'storage.access.check';
+            try{
+                obj.setItem(f.key, 'f');
+                const x = obj.getItem(f.key);
+                obj.removeItem(f.key);
+                if(x!=='f') throw new Error(f.key+" failed")
+                return obj;
+            }catch(e){
+                return undefined;
+            }
+        };
+
+        /** Internal storage impl for this module. */
+        const $storage =
+              tryStorage(window.localStorage)
+              || tryStorage(window.sessionStorage)
+              || tryStorage({
+                  // A basic dummy xyzStorage stand-in
+                  $$$:{},
+                  setItem: function(k,v){this.$$$[k]=v},
+                  getItem: function(k){
+                      return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined;
+                  },
+                  removeItem: function(k){delete this.$$$[k]},
+                  clear: function(){this.$$$={}}
+              });
+
+        /**
+           For the dummy storage we need to differentiate between
+           $storage and its real property storage for hasOwnProperty()
+           to work properly...
+        */
+        const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage;
+
+        /**
+           A prefix which gets internally applied to all storage module
+           property keys so that localStorage and sessionStorage across the
+           same browser profile instance do not "leak" across multiple apps
+           being hosted by the same origin server. Such cross-polination is
+           still there but, with this key prefix applied, it won't be
+           immediately visible via the storage API.
+
+           With this in place we can justify using localStorage instead of
+           sessionStorage.
+
+           One implication of using localStorage and sessionStorage is that
+           their scope (the same "origin" and client application/profile)
+           allows multiple apps on the same origin to use the same
+           storage. Thus /appA/foo could then see changes made via
+           /appB/foo. The data do not cross user- or browser boundaries,
+           though, so it "might" arguably be called a
+           feature. storageKeyPrefix was added so that we can sandbox that
+           state for each separate app which shares an origin.
+
+           See: https://fossil-scm.org/forum/forumpost/4afc4d34de
+
+           Sidebar: it might seem odd to provide a key prefix and stick all
+           properties in the topmost level of the storage object. We do that
+           because adding a layer of object to sandbox each app would mean
+           (de)serializing that whole tree on every storage property change.
+           e.g. instead of storageObject.projectName.foo we have
+           storageObject[storageKeyPrefix+'foo']. That's soley for
+           efficiency's sake (in terms of battery life and
+           environment-internal storage-level effort).
+        */
+        const storageKeyPrefix = (
+            $storageHolder===$storage/*localStorage or sessionStorage*/
+                ? (
+                    (NS.config ?
+                     (NS.config.projectCode || NS.config.projectName
+                      || NS.config.shortProjectName)
+                     : false)
+                        || window.location.pathname
+                )+'::' : (
+                    '' /* transient storage */
+                )
+        );
+
+        /**
+           A proxy for localStorage or sessionStorage or a
+           page-instance-local proxy, if neither one is availble.
+
+           Which exact storage implementation is uses is unspecified, and
+           apps must not rely on it.
+        */
+        NS.storage = {
+            storageKeyPrefix: storageKeyPrefix,
+            /** Sets the storage key k to value v, implicitly converting
+                it to a string. */
+            set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
+            /** Sets storage key k to JSON.stringify(v). */
+            setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)),
+            /** Returns the value for the given storage key, or
+                dflt if the key is not found in the storage. */
+            get: (k,dflt)=>$storageHolder.hasOwnProperty(
+                storageKeyPrefix+k
+            ) ? $storage.getItem(storageKeyPrefix+k) : dflt,
+            /** Returns true if the given key has a value of "true".  If the
+                key is not found, it returns true if the boolean value of dflt
+                is "true". (Remember that JS persistent storage values are all
+                strings.) */
+            getBool: function(k,dflt){
+                return 'true'===this.get(k,''+(!!dflt));
+            },
+            /** Returns the JSON.parse()'d value of the given
+                storage key's value, or dflt is the key is not
+                found or JSON.parse() fails. */
+            getJSON: function f(k,dflt){
+                try {
+                    const x = this.get(k,f);
+                    return x===f ? dflt : JSON.parse(x);
+                }
+                catch(e){return dflt}
+            },
+            /** Returns true if the storage contains the given key,
+                else false. */
+            contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k),
+            /** Removes the given key from the storage. Returns this. */
+            remove: function(k){
+                $storage.removeItem(storageKeyPrefix+k);
+                return this;
+            },
+            /** Clears ALL keys from the storage. Returns this. */
+            clear: function(){
+                this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k));
+                return this;
+            },
+            /** Returns an array of all keys currently in the storage. */
+            keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)),
+            /** Returns true if this storage is transient (only available
+                until the page is reloaded), indicating that fileStorage
+                and sessionStorage are unavailable. */
+            isTransient: ()=>$storageHolder!==$storage,
+            /** Returns a symbolic name for the current storage mechanism. */
+            storageImplName: function(){
+                if($storage===window.localStorage) return 'localStorage';
+                else if($storage===window.sessionStorage) return 'sessionStorage';
+                else return 'transient';
+            },
+
+            /**
+               Returns a brief help text string for the currently-selected
+               storage type.
+            */
+            storageHelpDescription: function(){
+                return {
+                    localStorage: "Browser-local persistent storage with an "+
+                        "unspecified long-term lifetime (survives closing the browser, "+
+                        "but maybe not a browser upgrade).",
+                    sessionStorage: "Storage local to this browser tab, "+
+                        "lost if this tab is closed.",
+                    "transient": "Transient storage local to this invocation of this page."
+                }[this.storageImplName()];
+            }
+        };
+        return NS.storage;
+    })({})/*storage API setup*/;
+
+    /** Name of the stored copy of PikchrFiddle.config. */
+    const configStorageKey = 'pikchr-fiddle-config';
+
+    /**
+       The PikchrFiddle object is intended to be the primary app-level
+       object for the main-thread side of the fiddle application. It
+       uses a worker thread to load the WASM module and communicate
+       with it.
+    */
+    const PF/*local convenience alias*/
+    = self.PikchrFiddle/*canonical name*/ = {
+        /* Config options. */
+        config: {
+            /* If true, display input/output areas side-by-side. */
+            sideBySide: true,
+            /* If true, swap positions of the input/output areas. */
+            swapInOut: true,
+            /* If true, the SVG is allowed to resize to fit the
+               parent content area, else the parent is resized to
+               fit the rendered SVG. */
+            renderAutoScale: false,
+            /* If true, automatically render while the user is
+               typing. */
+            renderWhileTyping: false
+        },
+        renderMode: 'html'/*one of: 'text','html'*/,
+        _msgMap: {},
+        /** Adds a worker message handler for messages of the given
+            type. */
+        addMsgHandler: function f(type,callback){
+            if(Array.isArray(type)){
+                type.forEach((t)=>this.addMsgHandler(t, callback));
+                return this;
+            }
+            (this._msgMap.hasOwnProperty(type)
+             ? this._msgMap[type]
+             : (this._msgMap[type] = [])).push(callback);
+            return this;
+        },
+        /** Given a worker message, runs all handlers for msg.type. */
+        runMsgHandlers: function(msg){
+            const list = (this._msgMap.hasOwnProperty(msg.type)
+                          ? this._msgMap[msg.type] : false);
+            if(!list){
+                console.warn("No handlers found for message type:",msg);
+                return false;
+            }
+            list.forEach((f)=>f(msg));
+            return true;
+        },
+        /** Removes all message handlers for the given message type. */
+        clearMsgHandlers: function(type){
+            delete this._msgMap[type];
+            return this;
+        },
+        /* Posts a message in the form {type, data} to the db worker. Returns this. */
+        wMsg: function(type,data){
+            this.worker.postMessage({type, data});
+            return this;
+        },
+        /** Stores this object's config in the browser's storage. */
+        storeConfig: function(){
+            storage.setJSON(configStorageKey,this.config);
+        }
+    };
+
+    if(1){ /* set up PF.config */
+        const storedConfig = storage.getJSON(configStorageKey);
+        if(storedConfig){
+            /* Copy all properties to PF.config which are currently in
+               storedConfig. We don't bother copying any other
+               properties: those have been removed from the app in the
+               meantime. */
+            Object.keys(PF.config).forEach(function(k){
+                if(storedConfig.hasOwnProperty(k)){
+                    PF.config[k] = storedConfig[k];
+                }
+            });
+        }
+    }
+
+    PF.worker = new Worker('pikchr-worker.js');
+    PF.worker.onmessage = (ev)=>PF.runMsgHandlers(ev.data);
+    PF.addMsgHandler('stdout', console.log.bind(console));
+    PF.addMsgHandler('stderr', console.error.bind(console));
+
+    /* querySelectorAll() proxy */
+    const EAll = function(/*[element=document,] cssSelector*/){
+        return (arguments.length>1 ? arguments[0] : document)
+            .querySelectorAll(arguments[arguments.length-1]);
+    };
+    /* querySelector() proxy */
+    const E = function(/*[element=document,] cssSelector*/){
+        return (arguments.length>1 ? arguments[0] : document)
+            .querySelector(arguments[arguments.length-1]);
+    };
+
+    /** Handles status updates from the Module object. */
+    PF.addMsgHandler('module', function f(ev){
+        ev = ev.data;
+        if('status'!==ev.type){
+            console.warn("Unexpected module-type message:",ev);
+            return;
+        }
+        if(!f.ui){
+            f.ui = {
+                status: E('#module-status'),
+                progress: E('#module-progress'),
+                spinner: E('#module-spinner')
+            };
+        }
+        const msg = ev.data;
+        if(f.ui.progres){
+            progress.value = msg.step;
+            progress.max = msg.step + 1/*we don't know how many steps to expect*/;
+        }
+        if(1==msg.step){
+            f.ui.progress.classList.remove('hidden');
+            f.ui.spinner.classList.remove('hidden');
+        }
+        if(msg.text){
+            f.ui.status.classList.remove('hidden');
+            f.ui.status.innerText = msg.text;
+        }else{
+            if(f.ui.progress){
+                f.ui.progress.remove();
+                f.ui.spinner.remove();
+                delete f.ui.progress;
+                delete f.ui.spinner;
+            }
+            f.ui.status.classList.add('hidden');
+            /* The module can post messages about fatal problems,
+               e.g. an exit() being triggered or assertion failure,
+               after the last "load" message has arrived, so
+               leave f.ui.status and message listener intact. */
+        }
+    });
+
+    /**
+       The 'pikchr-ready' event is fired (with no payload) when the
+       wasm module has finished loading. Interestingly, that happens
+       _before_ the final module:status event */
+    PF.addMsgHandler('pikchr-ready', function(){
+        PF.clearMsgHandlers('pikchr-ready');
+        self.onPFLoaded();
+    });
+
+    /**
+       Performs all app initialization which must wait until after the
+       worker module is loaded. This function removes itself when it's
+       called.
+    */
+    self.onPFLoaded = function(){
+        delete this.onPFLoaded;
+        // Unhide all elements which start out hidden
+        EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
+        const taInput = E('#input');
+        const btnClearIn = E('#btn-clear');
+        btnClearIn.addEventListener('click',function(){
+            taInput.value = '';
+        },false);
+        const taOutput = E('#output');
+        const btnRender = E('#btn-render');
+        const getCurrentText = function(){
+            let text;
+            if(taInput.selectionStart<taInput.selectionEnd){
+                text = taInput.value.substring(taInput.selectionStart,taInput.selectionEnd).trim();
+            }else{
+                text = taInput.value.trim();
+            }
+            return text;;
+        }
+        const renderCurrentText = function(){
+            const text = getCurrentText();
+            if(text) PF.render(text);
+        };
+        btnRender.addEventListener('click',function(ev){
+            ev.preventDefault();
+            renderCurrentText();
+        },false);
+
+        /** To be called immediately before work is sent to the
+            worker. Updates some UI elements. The 'working'/'end'
+            event will apply the inverse, undoing the bits this
+            function does. This impl is not in the 'working'/'start'
+            event handler because that event is given to us
+            asynchronously _after_ we need to have performed this
+            work.
+        */
+        const preStartWork = function f(){
+            if(!f._){
+                const title = E('title');
+                f._ = {
+                    btnLabel: btnRender.innerText,
+                    pageTitle: title,
+                    pageTitleOrig: title.innerText
+                };
+            }
+            //f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig;
+            btnRender.setAttribute('disabled','disabled');
+        };
+
+        /**
+           Submits the current input text to pikchr and renders the
+           result. */
+        PF.render = function f(txt){
+            preStartWork();
+            this.wMsg('pikchr',txt);
+        };
+
+        const eOut = E('#pikchr-output');
+        const eOutWrapper = E('#pikchr-output-wrapper');
+        PF.addMsgHandler('pikchr', function(ev){
+            const m = ev.data;
+            eOut.classList[m.isError ? 'add' : 'remove']('error');
+            eOut.dataset.pikchr = m.pikchr;
+            let content;
+            let sz;
+            switch(PF.renderMode){
+                case 'text':
+                    content = '<textarea>'+m.result+'</textarea>';
+                    eOut.classList.add('text');
+                    eOutWrapper.classList.add('text');
+                    break;
+                default:
+                    content = m.result;
+                    eOut.classList.remove('text');
+                    eOutWrapper.classList.remove('text');
+                    break;
+            }
+            eOut.innerHTML = content;
+            let vw = null, vh = null;
+            if(!PF.config.renderAutoScale
+               && !m.isError && 'html'===PF.renderMode){
+                const svg = E(eOut,':scope > svg');
+                const vb = svg ? svg.getAttribute('viewBox').split(' ') : false;
+                if(vb && 4===vb.length){
+                    vw = (+vb[2] + 10)+'px';
+                    vh = (+vb[3] + 10)+'px';
+                }else if(svg){
+                    console.warn("SVG element is missing viewBox attribute.");
+                }
+            }
+            eOut.style.width = vw;
+            eOut.style.height = vh;
+        })/*'pikchr' msg handler*/;
+
+        E('#btn-render-mode').addEventListener('click',function(){
+            let mode = PF.renderMode;
+            const modes = ['text','html'];
+            let ndx = modes.indexOf(mode) + 1;
+            if(ndx>=modes.length) ndx = 0;
+            PF.renderMode = modes[ndx];
+            if(eOut.dataset.pikchr){
+                PF.render(eOut.dataset.pikchr);
+            }
+        });
+
+        PF.addMsgHandler('working',function f(ev){
+            switch(ev.data){
+                case 'start': /* See notes in preStartWork(). */; return;
+                case 'end':
+                    //preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
+                    btnRender.innerText = preStartWork._.btnLabel;
+                    btnRender.removeAttribute('disabled');
+                    return;
+            }
+            console.warn("Unhandled 'working' event:",ev.data);
+        });
+
+        /* For each checkbox with data-csstgt, set up a handler which
+           toggles the given CSS class on the element matching
+           E(data-csstgt). */
+        EAll('input[type=checkbox][data-csstgt]')
+            .forEach(function(e){
+                const tgt = E(e.dataset.csstgt);
+                const cssClass = e.dataset.cssclass || 'error';
+                e.checked = tgt.classList.contains(cssClass);
+                e.addEventListener('change', function(){
+                    tgt.classList[
+                        this.checked ? 'add' : 'remove'
+                    ](cssClass)
+                }, false);
+            });
+        /* For each checkbox with data-config=X, set up a binding to
+           PF.config[X]. These must be set up AFTER data-csstgt
+           checkboxes so that those two states can be synced properly. */
+        EAll('input[type=checkbox][data-config]')
+            .forEach(function(e){
+                const confVal = !!PF.config[e.dataset.config];
+                if(e.checked !== confVal){
+                    /* Ensure that data-csstgt mappings (if any) get
+                       synced properly. */
+                    e.checked = confVal;
+                    e.dispatchEvent(new Event('change'));
+                }
+                e.addEventListener('change', function(){
+                    PF.config[this.dataset.config] = this.checked;
+                    PF.storeConfig();
+                }, false);
+            });
+        E('#opt-cb-autoscale').addEventListener('change',function(){
+            /* PF.config.renderAutoScale was set by the data-config
+               event handler. */
+            if('html'==PF.renderMode && eOut.dataset.pikchr){
+                PF.render(eOut.dataset.pikchr);
+            }
+        });
+        /* For each button with data-cmd=X, map a click handler which
+           calls PF.render(X). */
+        const cmdClick = function(){PF.render(this.dataset.cmd);};
+        EAll('button[data-cmd]').forEach(
+            e => e.addEventListener('click', cmdClick, false)
+        );
+
+        /**
+           TODO: Handle load/import of an external pikchr file.
+        */
+        if(0) E('#load-pikchr').addEventListener('change',function(){
+            const f = this.files[0];
+            const r = new FileReader();
+            const status = {loaded: 0, total: 0};
+            this.setAttribute('disabled','disabled');
+            const that = this;
+            r.addEventListener('load', function(){
+                that.removeAttribute('disabled');
+                stdout("Loaded",f.name+". Opening pikchr...");
+                PF.wMsg('open',{
+                    filename: f.name,
+                    buffer: this.result
+                });
+            });
+            r.addEventListener('error',function(){
+                that.removeAttribute('disabled');
+                stderr("Loading",f.name,"failed for unknown reasons.");
+            });
+            r.addEventListener('abort',function(){
+                that.removeAttribute('disabled');
+                stdout("Cancelled loading of",f.name+".");
+            });
+            r.readAsArrayBuffer(f);
+        });
+
+        EAll('.fieldset.collapsible').forEach(function(fs){
+            const legend = E(fs,'span.legend'),
+                  content = EAll(fs,':scope > div');
+            legend.addEventListener('click', function(){
+                fs.classList.toggle('collapsed');
+                content.forEach((d)=>d.classList.toggle('hidden'));
+            }, false);
+        });
+        
+        /**
+           Given a DOM element, this routine measures its "effective
+           height", which is the bounding top/bottom range of this element
+           and all of its children, recursively. For some DOM structure
+           cases, a parent may have a reported height of 0 even though
+           children have non-0 sizes.
+
+           Returns 0 if !e or if the element really has no height.
+        */
+        const effectiveHeight = function f(e){
+            if(!e) return 0;
+            if(!f.measure){
+                f.measure = function callee(e, depth){
+                    if(!e) return;
+                    const m = e.getBoundingClientRect();
+                    if(0===depth){
+                        callee.top = m.top;
+                        callee.bottom = m.bottom;
+                    }else{
+                        callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
+                        callee.bottom = Math.max(callee.bottom, m.bottom);
+                    }
+                    Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
+                    if(0===depth){
+                        //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
+                        f.extra += callee.bottom - callee.top;
+                    }
+                    return f.extra;
+                };
+            }
+            f.extra = 0;
+            f.measure(e,0);
+            return f.extra;
+        };
+
+        btnRender.click();
+        
+        /**
+           Returns a function, that, as long as it continues to be invoked,
+           will not be triggered. The function will be called after it stops
+           being called for N milliseconds. If `immediate` is passed, call
+           the callback immediately and hinder future invocations until at
+           least the given time has passed.
+
+           If passed only 1 argument, or passed a falsy 2nd argument,
+           the default wait time set in this function's $defaultDelay
+           property is used.
+
+           Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function
+        */
+        const debounce = function f(func, wait, immediate) {
+            var timeout;
+            if(!wait) wait = f.$defaultDelay;
+            return function() {
+                const context = this, args = Array.prototype.slice.call(arguments);
+                const later = function() {
+                    timeout = undefined;
+                    if(!immediate) func.apply(context, args);
+                };
+                const callNow = immediate && !timeout;
+                clearTimeout(timeout);
+                timeout = setTimeout(later, wait);
+                if(callNow) func.apply(context, args);
+            };
+        };
+        debounce.$defaultDelay = 500 /*arbitrary*/;
+
+        /** Debounce handler for auto-rendering while typing. */
+        const debounceAutoRender = debounce(function f(){
+            if(!PF._isDirty) return;
+            const text = getCurrentText();
+            if(f._ === text){
+                PF._isDirty = false;
+                return;
+            }
+            f._ = text;
+            PF._isDirty = false;
+            PF.render(text || '');
+        }, 800, false);
+
+        taInput.addEventListener('keydown',function f(ev){
+            if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){
+                // Ctrl-enter and shift-enter both run the current input
+                PF._isDirty = false/*prevent a pending debounce from re-rendering*/;
+                ev.preventDefault();
+                ev.stopPropagation();
+                renderCurrentText();
+                return;
+            }
+            if(!PF.config.renderWhileTyping) return;
+            /* Auto-render while typing... */
+            switch(ev.keyCode){
+                case (ev.keyCode<32): /*any ctrl char*/
+                    /* ^^^ w/o that, simply tapping ctrl is enough to
+                       force a re-render. Similarly, TAB-ing focus away
+                       should not re-render. */
+                case 33: case 34: /* page up/down */
+                case 35: case 36: /* home/end */
+                case 37: case 38: case 39: case 40: /* arrows */
+                    return;
+            }
+            PF._isDirty = true;
+            debounceAutoRender();
+        }, false);
+
+        const ForceResizeKludge = (function(){
+            /* Workaround for Safari mayhem regarding use of vh CSS
+               units....  We cannot use vh units to set the main view
+               size because Safari chokes on that, so we calculate
+               that height here. Larger than ~95% is too big for
+               Firefox on Android, causing the input area to move
+               off-screen. */
+            const appViews = EAll('.app-view');
+            const elemsToCount = [
+                /* Elements which we need to always count in the
+                   visible body size. */
+                E('body > header'),
+                E('body > footer')
+            ];
+            const resized = function f(){
+                if(f.$disabled) return;
+                const wh = window.innerHeight;
+                var ht;
+                var extra = 0;
+                elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false);
+                ht = wh - extra;
+                appViews.forEach(function(e){
+                    e.style.height =
+                        e.style.maxHeight = [
+                            "calc(", (ht>=100 ? ht : 100), "px",
+                            " - 2em"/*fudge value*/,")"
+                            /* ^^^^ hypothetically not needed, but both
+                               Chrome/FF on Linux will force scrollbars on the
+                               body if this value is too small. */
+                        ].join('');
+                });
+            };
+            resized.$disabled = true/*gets deleted when setup is finished*/;
+            window.addEventListener('resize', debounce(resized, 250), false);
+            return resized;
+        })()/*ForceResizeKludge*/;
+
+        delete ForceResizeKludge.$disabled;
+        ForceResizeKludge();
+    }/*onPFLoaded()*/;
+})();

+ 221 - 0
pikchr.mod/pikchr/fiddle/pikchr-worker.js

@@ -0,0 +1,221 @@
+/*
+  2022-05-20
+
+  The author disclaims copyright to this source code.  In place of a
+  legal notice, here is a blessing:
+
+  *   May you do good and not evil.
+  *   May you find forgiveness for yourself and forgive others.
+  *   May you share freely, never taking more than you give.
+
+  ***********************************************************************
+
+  This is a JS Worker file for use with the pikchr wasm build. It
+  loads the pikchr wasm module and offers access to it via the Worker
+  message-passing interface.
+
+  Because we can have only a single message handler, as opposed to an
+  arbitrary number of discrete event listeners like with DOM elements,
+  we have to define a lower-level message API. Messages abstractly
+  look like:
+
+  { type: string, data: type-specific value }
+
+  Where 'type' is used for dispatching and 'data' is a
+  'type'-dependent value.
+
+  The 'type' values expected by each side of the main/worker
+  connection vary. The types are described below but subject to
+  change at any time as this experiment evolves.
+
+  Main-to-Worker message types:
+
+  - pikchr: data=pikchr-format text to render or an object:
+
+  {
+    pikchr: source code for the pikchr,
+    darkMode: boolean true to adjust colors for a dark color scheme,
+    cssClass: CSS class name to add to the SVG
+  }
+
+  Workers-to-Main types
+
+  - stdout, stderr: indicate stdout/stderr output from the wasm
+  layer. The data property is the string of the output, noting
+  that the emscripten binding emits these one line at a time. Thus,
+  if a C-side puts() emits multiple lines in a single call, the JS
+  side will see that as multiple calls. Example:
+
+  {type:'stdout', data: 'Hi, world.'}
+
+  - module: Status text. This is intended to alert the main thread
+  about module loading status so that, e.g., the main thread can
+  update a progress widget and DTRT when the module is finished
+  loading and available for work. Status messages come in the form
+  
+  {type:'module', data:{
+  type:'status',
+  data: {text:string|null, step:1-based-integer}
+  }
+
+  with an incrementing step value for each subsequent message. When
+  the module loading is complete, a message with a text value of
+  null is posted.
+
+  - pikchr: 
+
+  {type: 'pikchr',
+    data:{
+      pikchr: input text,
+      result: rendered result (SVG on success, HTML on error),
+      isError: bool, true if .pikchr holds an error report,
+      flags: integer: flags used to configure the pikchr rendering,
+      width: if !isError, width (integer pixels) of the SVG,
+      height: if !isError, height (integer pixels) of the SVG
+    }
+  }
+
+*/
+
+"use strict";
+(function(){
+  /**
+     Posts a message in the form {type,data} unless passed more than
+     2 args, in which case it posts {type, data:[arg1...argN]}.
+  */
+  const wMsg = function(type,data){
+    postMessage({
+      type,
+      data: arguments.length<3
+        ? data
+        : Array.prototype.slice.call(arguments,1)
+    });
+  };
+
+  const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));};
+
+  self.onerror = function(/*message, source, lineno, colno, error*/) {
+    const err = arguments[4];
+    if(err && 'ExitStatus'==err.name){
+      /* This "cannot happen" for this wasm binding, but just in
+         case... */
+      pikchrModule.isDead = true;
+      stderr("FATAL ERROR:", err.message);
+      stderr("Restarting the app requires reloading the page.");
+      wMsg('error', err);
+    }
+    pikchrModule.setStatus('Exception thrown, see JavaScript console: '+err);
+  };
+
+  self.onmessage = function f(ev){
+    ev = ev.data;
+    switch(ev.type){
+          /**
+             Runs the given text through pikchr and emits a 'pikchr'
+             message result (output format documented above).
+
+             Fires a working/start event before it starts and
+             working/end event when it finishes.
+          */
+        case 'pikchr':
+          if(pikchrModule.isDead){
+            stderr("wasm module has exit()ed. Cannot pikchr.");
+            return;
+          }
+          if(!f._){
+            f._ = pikchrModule.cwrap('pikchr', 'string', [
+              'string'/*script*/, 'string'/*CSS class*/, 'number'/*flags*/,
+              'number'/*output: SVG width*/, 'number'/*output: SVG height*/
+            ]);
+          }
+          wMsg('working','start');
+          const stack = pikchrModule.stackSave();
+          try {
+            const pnWidth = pikchrModule.stackAlloc(4),
+                  pnHeight = pikchrModule.stackAlloc(4);
+            let script = '', flags = 0, cssClass = null;
+            if('string'===typeof ev.data){
+              script = ev.data;
+            }else if(ev.data && 'object'===typeof ev.data){
+              script = ev.data.pikchr;
+              flags = ev.data.darkMode ? 0x02 : 0;
+              if(ev.data.cssClass) cssClass = ev.data.cssClass;
+            }
+            pikchrModule.setValue(pnWidth, 0, "i32");
+            pikchrModule.setValue(pnHeight, 0, "i32");
+            const msg = {
+              pikchr: script,
+              result: (f._(script, cssClass, flags, pnWidth, pnHeight) || "").trim(),
+              flags: flags
+            };
+            msg.isError = !!(msg.result && msg.result.startsWith('<div'));
+            if(msg.isError){
+              msg.width = msg.height = null;
+            }else{
+              msg.width = pikchrModule.getValue(pnWidth, "i32");
+              msg.height = pikchrModule.getValue(pnHeight, "i32");
+            }
+            wMsg('pikchr', msg);
+          } finally {
+            pikchrModule.stackRestore(stack);
+            wMsg('working','end');
+          }
+          return;
+    };
+    console.warn("Unknown pikchr-worker message type:",ev);
+  };
+  
+  /**
+     emscripten module for use with build mode -sMODULARIZE.
+  */
+  const pikchrModule = {
+    print: function(){wMsg('stdout', Array.prototype.slice.call(arguments));},
+    printErr: stderr,
+    /**
+       Intercepts status updates from the emscripting module init
+       and fires worker events with a type of 'status' and a
+       payload of:
+
+       {
+       text: string | null, // null at end of load process
+       step: integer // starts at 1, increments 1 per call
+       }
+
+       We have no way of knowing in advance how many steps will
+       be processed/posted, so creating a "percentage done" view is
+       not really practical. One can be approximated by giving it a
+       current value of message.step and max value of message.step+1,
+       though.
+
+       When work is finished, a message with a text value of null is
+       submitted.
+
+       After a message with text==null is posted, the module may later
+       post messages about fatal problems, e.g. an exit() being
+       triggered, so it is recommended that UI elements for posting
+       status messages not be outright removed from the DOM when
+       text==null, and that they instead be hidden until/unless
+       text!=null.
+    */
+    setStatus: function f(text){
+      if(!f.last) f.last = { step: 0, text: '' };
+      else if(text === f.last.text) return;
+      f.last.text = text;
+      wMsg('module',{
+        type:'status',
+        data:{step: ++f.last.step, text: text||null}
+      });
+    }
+  };
+
+  importScripts('pikchr.js');
+  /**
+     initPikchrModule() is installed via pikchr.js due to
+     building with:
+
+     emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initPikchrModule
+  */
+  initPikchrModule(pikchrModule).then(function(thisModule){
+    wMsg('pikchr-ready');
+  });
+})();

+ 32 - 0
pikchr.mod/pikchr/fuzzcases/divzero.pikchr

@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+














#
#x
+C: circle rad 0%
+C: circle rad 0%
+ circle ""  at 0n  of C;; arrow from C to last chop <-
+ ""  at C;
+



arrow from C to last chop <-
+ ""  at C;
+














#x
+C: circle rad 0%
+ circle ""  at 0n  of C;; arrow from C to last chop <-
+ ""  at C;; arrow from C to last chop <-# x
+C: circle rad 0%
+C: circle rad 0%
+ circle ""  at 0n  of C;; arrow from C to last chop <-
+ ""  at C;
+



arrow from C to last chop <-
+ ""  at C;
+














#x
+C: circle rad 0%
+ circle ""  at 0n  of C;; arrow from C to last chop <-
+ ""  at C;; arrow from C to last chop <-# d
+       box wid

+ 1 - 0
pikchr.mod/pikchr/fuzzcases/nan.pikchr

@@ -0,0 +1 @@
+arc-419877777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777775777777777777777777777588410070624751641193177777777777777777777777777777777777777588410070624751641193177777777777777777777777588410070624751641193177777777777777777777777588410070624751641193177062475164119317

+ 43 - 0
pikchr.mod/pikchr/grammar/gram01.pikchr

@@ -0,0 +1,43 @@
+$r = 0.2in
+linerad = 0.75*$r
+linewid = 0.25
+
+# Start and end blocks
+#
+box "statement-list" bold fit
+line down 75% from last box.sw
+dot rad 250% color black
+X0: last.e + (0.3,0)
+arrow from last dot to X0
+move right 2in
+box wid 5% ht 25% fill black
+X9: last.w - (0.3,0)
+arrow from X9 to last box.w
+
+
+# The main rule that goes straight through from start to finish
+#
+box "statement" italic fit at 0.5<X0,X9>
+arrow to X9
+arrow from X0 to last box.w
+
+# The by-pass line
+#
+arrow right $r from X0 then up $r \
+  then right until even with 1/2 way between X0 and X9
+line right until even with X9 - ($r,0) \
+  then down until even with X9 then right $r
+
+# The Loop-back rule
+#
+oval "\"&#92;n\"" fit at $r*1.2 below 1/2 way between X0 and X9
+line right $r from X9-($r/2,0) then down until even with last oval \
+   then to last oval.e ->
+line from last oval.w left until even with X0-($r,0) \
+   then up until even with X0 then right $r
+oval "\";\"" fit at $r*1.2 below last oval
+line from 2*$r right of 2nd last oval.e left $r \
+   then down until even with last oval \
+   then to last oval.e ->
+line from last oval.w left $r then up until even with 2nd last oval \
+   then left 2*$r ->

+ 89 - 0
pikchr.mod/pikchr/grammar/gram02.pikchr

@@ -0,0 +1,89 @@
+$r = 0.2in
+linerad = 0.75*$r
+linewid = 0.25
+
+# Start and end blocks
+#
+box "statement" bold fit
+line down 50% from last box.sw
+START: dot rad 250% color black
+X0: last.e
+move right 4.5in
+END: box wid 5% ht 25% fill black
+X9: last.w
+
+
+# The LABEL: rule
+#
+arrow right $r from X0 then down 1.25*$r then right $r
+oval "LABEL" fit
+arrow 50%
+oval "\":\"" fit
+arrow 200%
+box "position" italic fit
+arrow
+line right until even with X9 - ($r,0) \
+  then up until even with X9 then to X9
+arrow from last oval.e right $r*0.5 then up $r*0.8 right $r*0.8
+line up $r*0.45 right $r*0.45 then right
+X2: previous.end
+
+# The main top-line rule
+arrow from START.e to linerad right of X2
+box "object-definition" fit
+arrow to X9
+
+# The VARIABLE = rule
+#
+arrow right $r from X0 then down 2.5*$r then right $r
+oval "VARIABLE" fit
+arrow 70%
+box "assignment-operator" italic fit
+arrow 70%
+box "expr" italic fit
+line right until even with X9 - ($r,0) \
+  then up until even with X9 then to X9
+
+# The macro rule
+#
+arrow right $r from X0 then down 3.75*$r then right $r
+oval "\"define\""fit
+arrow
+oval "MACRONAME" fit
+arrow
+oval "{...}" fit
+line right until even with X9-($r,0) \
+  then up even with X9 then to X9
+
+# The PRINT rule
+#
+arrow right $r from X0 then down 5.0*$r then right $r
+oval "\"print\"" fit
+arrow
+box "print-args" italic fit
+line right until even with X9 - ($r,0) \
+  then up until even with X9 then to X9
+
+# The ASSERT rule
+#
+arrow right $r from X0 then down 6.25*$r then right $r
+oval "\"assert\"" fit
+arrow 2*arrowht
+oval "\"(\"" fit
+A1: arrow right 2*linerad + arrowht
+box "position" fit
+arrow 2*arrowht
+oval "\"==\"" fit
+arrow 2*arrowht
+box "position" fit
+A2: arrow same as A1
+oval "\")\"" fit
+line right even with $r left of X9 then up until even with VARIABLE.n
+arrow from A1.start right linerad then down 1.25*$r then right linerad+arrowht
+box "expr" fit
+arrow 2*arrowht
+oval "\"==\"" fit
+arrow same
+box "expr" fit
+line right even with linerad right of A2.start \
+    then up even with A2 then right linerad

+ 33 - 0
pikchr.mod/pikchr/grammar/gram03.pikchr

@@ -0,0 +1,33 @@
+$r = 0.2in
+linerad = 0.75*$r
+linewid = 0.25
+
+# Start and end blocks
+#
+box "object-definition" bold fit
+line down 50% from last box.sw
+START: dot rad 250% color black
+X0: last.e
+move right 3.2in
+END: box wid 5% ht 25% fill black
+X9: last.w
+
+# The main rule
+#
+arrow from X0 right 2*linerad+arrowht
+TYPENAME: box "object-type-name" fit
+
+# The text-attribute rule
+#
+arrow right linerad from X0 then down 1.25*$r then right linerad+arrowht
+TEXT: box "text-attribute" fit
+line right even with linerad right of TYPENAME.e \
+     then up even with TYPENAME then right linerad
+X3: previous.end
+
+# The attribute loop
+ATTR: box "attribute" fit with .w at X3 + (2*linerad+arrowht, -1.25*$r)
+arrow from TYPENAME.e right even with ATTR
+arrow to X9
+arrow from (ATTR.e,X9) right linerad then down even with ATTR then to ATTR.e
+line from ATTR.w left linerad then up even with X9 then right linerad

+ 50 - 0
pikchr.mod/pikchr/grammar/gram04.pikchr

@@ -0,0 +1,50 @@
+$r = 0.2in
+linerad = 0.75*$r
+linewid = 0.25
+
+# Start and end blocks
+#
+box "object-type-name" bold fit
+line down 50% from last box.sw
+START: dot rad 250% color black
+X0: last.e
+X1: X0+(linerad,0)
+X2: X1+(linerad+arrowht,0)
+move right 1.7in
+END: box wid 5% ht 25% fill black
+X9: last.w
+X8: linerad+arrowht west of X9
+X7: linerad west of X8
+
+# The choices
+#
+arrow from X0 to X2
+oval "\"arc\"" fit
+arrow to X7
+arrow to X9
+
+define keyword {
+  right
+  oval $1 fit with .w at 1.25*$r below last oval.w
+  arrow right even with X7
+  line right even with X8 then up linerad
+  arrow from (X1,last oval.n) down even with last oval then to last oval.w
+}
+keyword("\"arrow\"")
+keyword("\"box\"")
+keyword("\"circle\"")
+keyword("\"cylinder\"")
+keyword("\"dot\"")
+keyword("\"ellipse\"")
+keyword("\"file\"")
+keyword("\"line\"")
+keyword("\"move\"")
+keyword("\"oval\"")
+keyword("\"spline\"")
+
+right
+oval "\"text\"" fit with .w at 1.25*$r below last oval.w
+arrow right even with X7
+line right even with X8 then up even with X9 then right linerad
+arrow from X0 right even with X1 then down even with last oval \
+    then right to last oval.w

+ 112 - 0
pikchr.mod/pikchr/homepage.md

@@ -0,0 +1,112 @@
+Pikchr (pronounced "picture") is a [PIC][1]-like markup
+language for diagrams in technical documentation.  Pikchr is
+designed to be embedded in [fenced code blocks][2] of
+Markdown or similar mechanisms of other documentation markup languages.
+
+[1]: https://en.wikipedia.org/wiki/Pic_language
+[2]: https://spec.commonmark.org/0.29/#fenced-code-blocks
+
+For example, the diagram:
+
+``` pikchr
+arrow right 200% "Markdown" "Source"
+box rad 10px "Markdown" "Formatter" "(markdown.c)" fit
+arrow right 200% "HTML+SVG" "Output"
+arrow <-> down 70% from last box.s
+box same "Pikchr" "Formatter" "(pikchr.c)" fit
+```
+
+Is generated by 7 lines of Markdown:
+
+~~~~~~
+   ``` pikchr
+   arrow right 200% "Markdown" "Source"
+   box rad 10px "Markdown" "Formatter" "(markdown.c)" fit
+   arrow right 200% "HTML+SVG" "Output"
+   arrow <-> down 70% from last box.s
+   box same "Pikchr" "Formatter" "(pikchr.c)" fit
+   ```
+~~~~~~
+
+Pikchr diagrams can appear in:
+
+  *  Documentation
+  *  Wiki pages
+  *  Tickets and bug reports
+  *  Forum posts
+  *  Check-in comments
+  *  Anywhere else that Markdown or similar markup languages are used
+
+Pikchr diagrams are easy to generate.  The language is simple.
+There is lots of documentation and examples on-line (links below).
+Anyone who is comfortable using Markdown should be able to pick up Pikchr
+with minimal extra effort.
+
+Pikchr is safe for use in internet-facing applications.  Hostile
+Pikchr scripts cause no harm (apart from generating ugly diagrams).
+
+## Demos
+
+  *  [Example Pikchr Scripts](./doc/examples.md)
+     ([light-mode](/doc/trunk/doc/examples.md?skin=default&once) or
+      [dark-mode](/doc/trunk/doc/examples.md?skin=darkmode&once))
+  *  [](/pikchrshow) &larr; Enter Pikchr text and see the result on-line
+  *  [Source text for this page](./homepage.md?mimetype=text/plain)
+
+## Pikchr Documentation
+
+  *  [Purpose And Scope Of Pikchr](./doc/purpose.md)
+  *  [Pikchr User Manual](./doc/userman.md)
+  *  [An Example Of How A Pikchr Script Is Written](./doc/teardown01.md)
+  *  [Pikchr Language Spec](./doc/grammar.md)
+  *  [Differences From PIC](./doc/differences.md)
+  *  [Invoking Pikchr From Markdown](./doc/usepikchr.md)
+  *  [How To Build Pikchr From Source](./doc/build.md)
+  *  [Use Case: SQLite Syntax Diagrams](./doc/sqlitesyntax.md)
+
+## Copies Of Historical PIC Documentation
+
+  *  [BWK paper on the original PIC](/uv/pic.pdf)
+  *  [DPIC Documentation](/uv/dpic-doc.pdf)
+  *  [ESR GnuPIC Docs](/uv/gpic.pdf)
+
+## Source Code
+
+  *  [Download](./doc/download.md)
+  *  [How To Build Pikchr From Source](./doc/build.md)
+  *  [How To Integrate Pikchr Into Other Applications][integ]
+
+## External Links
+
+  *  [Go port of pikchr](https://github.com/gopikchr/gopikchr) and
+     a [blog post](https://zellyn.com/gopikchr-a-yakshave/) about how
+     that port came to be, both by Zellyn Hunter.
+  *  [mdbook-pikchr](https://crates.io/crates/mdbook-pikchr), by
+     Konstantin Podsvirov, is a pikchr-aware preprocessor for mdBook.
+  *  Support for pikchr in [IntelliJ-based
+     IDEs](https://github.com/YannCebron/IntelliPikchr/) by Yann Cébron.
+  *  pikchr has been integrated into the [kroki.io](https://kroki.io/)
+     diagram-rendering service.
+  *  The 8th programming language added pikchr support [in version
+     22.04](https://8th-dev.com/forum/index.php/topic,2473.0.html).
+  *  [Node.js pikchr module](https://github.com/tenuki/pikchr) by
+     David Weil.
+  *  A [pikchr filter for
+     pandoc](https://code.jboy.space/pikchr-filter/doc/trunk/README.md)
+     implemented in Lua.
+  *  [Adamantine Pick](https://github.com/notlibrary/obsidian-adamantine-pick)
+     adds pikchr support to the Obsidian knowledge-base app.
+
+## Source-Code License: 0-clause BSD
+
+The Pikchr source code is a self-contained original work.  It has no
+external dependencies apart from the standard C library and does not
+use code taken from the internet or other external sources.  All of the Pikchr
+source code is released under a [zero-clause BSD license][0BSD].  After being
+processed using [Lemon][lemon], the Pikchr source code is a single
+file of C89 named "`pikchr.c`".  These features
+are designed to make Pikchr [easy to integrate into other systems][integ].
+
+[0BSD]: https://spdx.org/licenses/0BSD.html
+[lemon]: https://www.sqlite.org/lemon.html
+[integ]: doc/integrate.md

+ 5893 - 0
pikchr.mod/pikchr/lemon.c

@@ -0,0 +1,5893 @@
+/*
+** This file contains all sources (including headers) to the LEMON
+** LALR(1) parser generator.  The sources have been combined into a
+** single file to make it easy to include LEMON in the source tree
+** and Makefile of another program.
+**
+** The author of this program disclaims copyright.
+*/
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define ISSPACE(X) isspace((unsigned char)(X))
+#define ISDIGIT(X) isdigit((unsigned char)(X))
+#define ISALNUM(X) isalnum((unsigned char)(X))
+#define ISALPHA(X) isalpha((unsigned char)(X))
+#define ISUPPER(X) isupper((unsigned char)(X))
+#define ISLOWER(X) islower((unsigned char)(X))
+
+
+#ifndef __WIN32__
+#   if defined(_WIN32) || defined(WIN32)
+#       define __WIN32__
+#   endif
+#endif
+
+#ifdef __WIN32__
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern int access(const char *path, int mode);
+#ifdef __cplusplus
+}
+#endif
+#else
+#include <unistd.h>
+#endif
+
+/* #define PRIVATE static */
+#define PRIVATE
+
+#ifdef TEST
+#define MAXRHS 5       /* Set low to exercise exception code */
+#else
+#define MAXRHS 1000
+#endif
+
+extern void memory_error();
+static int showPrecedenceConflict = 0;
+static char *msort(char*,char**,int(*)(const char*,const char*));
+
+/*
+** Compilers are getting increasingly pedantic about type conversions
+** as C evolves ever closer to Ada....  To work around the latest problems
+** we have to define the following variant of strlen().
+*/
+#define lemonStrlen(X)   ((int)strlen(X))
+
+/*
+** Compilers are starting to complain about the use of sprintf() and strcpy(),
+** saying they are unsafe.  So we define our own versions of those routines too.
+**
+** There are three routines here:  lemon_sprintf(), lemon_vsprintf(), and
+** lemon_addtext(). The first two are replacements for sprintf() and vsprintf().
+** The third is a helper routine for vsnprintf() that adds texts to the end of a
+** buffer, making sure the buffer is always zero-terminated.
+**
+** The string formatter is a minimal subset of stdlib sprintf() supporting only
+** a few simply conversions:
+**
+**   %d
+**   %s
+**   %.*s
+**
+*/
+static void lemon_addtext(
+  char *zBuf,           /* The buffer to which text is added */
+  int *pnUsed,          /* Slots of the buffer used so far */
+  const char *zIn,      /* Text to add */
+  int nIn,              /* Bytes of text to add.  -1 to use strlen() */
+  int iWidth            /* Field width.  Negative to left justify */
+){
+  if( nIn<0 ) for(nIn=0; zIn[nIn]; nIn++){}
+  while( iWidth>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth--; }
+  if( nIn==0 ) return;
+  memcpy(&zBuf[*pnUsed], zIn, nIn);
+  *pnUsed += nIn;
+  while( (-iWidth)>nIn ){ zBuf[(*pnUsed)++] = ' '; iWidth++; }
+  zBuf[*pnUsed] = 0;
+}
+static int lemon_vsprintf(char *str, const char *zFormat, va_list ap){
+  int i, j, k, c;
+  int nUsed = 0;
+  const char *z;
+  char zTemp[50];
+  str[0] = 0;
+  for(i=j=0; (c = zFormat[i])!=0; i++){
+    if( c=='%' ){
+      int iWidth = 0;
+      lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0);
+      c = zFormat[++i];
+      if( ISDIGIT(c) || (c=='-' && ISDIGIT(zFormat[i+1])) ){
+        if( c=='-' ) i++;
+        while( ISDIGIT(zFormat[i]) ) iWidth = iWidth*10 + zFormat[i++] - '0';
+        if( c=='-' ) iWidth = -iWidth;
+        c = zFormat[i];
+      }
+      if( c=='d' ){
+        int v = va_arg(ap, int);
+        if( v<0 ){
+          lemon_addtext(str, &nUsed, "-", 1, iWidth);
+          v = -v;
+        }else if( v==0 ){
+          lemon_addtext(str, &nUsed, "0", 1, iWidth);
+        }
+        k = 0;
+        while( v>0 ){
+          k++;
+          zTemp[sizeof(zTemp)-k] = (v%10) + '0';
+          v /= 10;
+        }
+        lemon_addtext(str, &nUsed, &zTemp[sizeof(zTemp)-k], k, iWidth);
+      }else if( c=='s' ){
+        z = va_arg(ap, const char*);
+        lemon_addtext(str, &nUsed, z, -1, iWidth);
+      }else if( c=='.' && memcmp(&zFormat[i], ".*s", 3)==0 ){
+        i += 2;
+        k = va_arg(ap, int);
+        z = va_arg(ap, const char*);
+        lemon_addtext(str, &nUsed, z, k, iWidth);
+      }else if( c=='%' ){
+        lemon_addtext(str, &nUsed, "%", 1, 0);
+      }else{
+        fprintf(stderr, "illegal format\n");
+        exit(1);
+      }
+      j = i+1;
+    }
+  }
+  lemon_addtext(str, &nUsed, &zFormat[j], i-j, 0);
+  return nUsed;
+}
+static int lemon_sprintf(char *str, const char *format, ...){
+  va_list ap;
+  int rc;
+  va_start(ap, format);
+  rc = lemon_vsprintf(str, format, ap);
+  va_end(ap);
+  return rc;
+}
+static void lemon_strcpy(char *dest, const char *src){
+  while( (*(dest++) = *(src++))!=0 ){}
+}
+static void lemon_strcat(char *dest, const char *src){
+  while( *dest ) dest++;
+  lemon_strcpy(dest, src);
+}
+
+
+/* a few forward declarations... */
+struct rule;
+struct lemon;
+struct action;
+
+static struct action *Action_new(void);
+static struct action *Action_sort(struct action *);
+
+/********** From the file "build.h" ************************************/
+void FindRulePrecedences(struct lemon*);
+void FindFirstSets(struct lemon*);
+void FindStates(struct lemon*);
+void FindLinks(struct lemon*);
+void FindFollowSets(struct lemon*);
+void FindActions(struct lemon*);
+
+/********* From the file "configlist.h" *********************************/
+void Configlist_init(void);
+struct config *Configlist_add(struct rule *, int);
+struct config *Configlist_addbasis(struct rule *, int);
+void Configlist_closure(struct lemon *);
+void Configlist_sort(void);
+void Configlist_sortbasis(void);
+struct config *Configlist_return(void);
+struct config *Configlist_basis(void);
+void Configlist_eat(struct config *);
+void Configlist_reset(void);
+
+/********* From the file "error.h" ***************************************/
+void ErrorMsg(const char *, int,const char *, ...);
+
+/****** From the file "option.h" ******************************************/
+enum option_type { OPT_FLAG=1,  OPT_INT,  OPT_DBL,  OPT_STR,
+         OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR};
+struct s_options {
+  enum option_type type;
+  const char *label;
+  char *arg;
+  const char *message;
+};
+int    OptInit(char**,struct s_options*,FILE*);
+int    OptNArgs(void);
+char  *OptArg(int);
+void   OptErr(int);
+void   OptPrint(void);
+
+/******** From the file "parse.h" *****************************************/
+void Parse(struct lemon *lemp);
+
+/********* From the file "plink.h" ***************************************/
+struct plink *Plink_new(void);
+void Plink_add(struct plink **, struct config *);
+void Plink_copy(struct plink **, struct plink *);
+void Plink_delete(struct plink *);
+
+/********** From the file "report.h" *************************************/
+void Reprint(struct lemon *);
+void ReportOutput(struct lemon *);
+void ReportTable(struct lemon *, int, int);
+void ReportHeader(struct lemon *);
+void CompressTables(struct lemon *);
+void ResortStates(struct lemon *);
+
+/********** From the file "set.h" ****************************************/
+void  SetSize(int);             /* All sets will be of size N */
+char *SetNew(void);               /* A new set for element 0..N */
+void  SetFree(char*);             /* Deallocate a set */
+int SetAdd(char*,int);            /* Add element to a set */
+int SetUnion(char *,char *);    /* A <- A U B, thru element N */
+#define SetFind(X,Y) (X[Y])       /* True if Y is in set X */
+
+/********** From the file "struct.h" *************************************/
+/*
+** Principal data structures for the LEMON parser generator.
+*/
+
+typedef enum {LEMON_FALSE=0, LEMON_TRUE} Boolean;
+
+/* Symbols (terminals and nonterminals) of the grammar are stored
+** in the following: */
+enum symbol_type {
+  TERMINAL,
+  NONTERMINAL,
+  MULTITERMINAL
+};
+enum e_assoc {
+    LEFT,
+    RIGHT,
+    NONE,
+    UNK
+};
+struct symbol {
+  const char *name;        /* Name of the symbol */
+  int index;               /* Index number for this symbol */
+  enum symbol_type type;   /* Symbols are all either TERMINALS or NTs */
+  struct rule *rule;       /* Linked list of rules of this (if an NT) */
+  struct symbol *fallback; /* fallback token in case this token doesn't parse */
+  int prec;                /* Precedence if defined (-1 otherwise) */
+  enum e_assoc assoc;      /* Associativity if precedence is defined */
+  char *firstset;          /* First-set for all rules of this symbol */
+  Boolean lambda;          /* True if NT and can generate an empty string */
+  int useCnt;              /* Number of times used */
+  char *destructor;        /* Code which executes whenever this symbol is
+                           ** popped from the stack during error processing */
+  int destLineno;          /* Line number for start of destructor.  Set to
+                           ** -1 for duplicate destructors. */
+  char *datatype;          /* The data type of information held by this
+                           ** object. Only used if type==NONTERMINAL */
+  int dtnum;               /* The data type number.  In the parser, the value
+                           ** stack is a union.  The .yy%d element of this
+                           ** union is the correct data type for this object */
+  int bContent;            /* True if this symbol ever carries content - if
+                           ** it is ever more than just syntax */
+  /* The following fields are used by MULTITERMINALs only */
+  int nsubsym;             /* Number of constituent symbols in the MULTI */
+  struct symbol **subsym;  /* Array of constituent symbols */
+};
+
+/* Each production rule in the grammar is stored in the following
+** structure.  */
+struct rule {
+  struct symbol *lhs;      /* Left-hand side of the rule */
+  const char *lhsalias;    /* Alias for the LHS (NULL if none) */
+  int lhsStart;            /* True if left-hand side is the start symbol */
+  int ruleline;            /* Line number for the rule */
+  int nrhs;                /* Number of RHS symbols */
+  struct symbol **rhs;     /* The RHS symbols */
+  const char **rhsalias;   /* An alias for each RHS symbol (NULL if none) */
+  int line;                /* Line number at which code begins */
+  const char *code;        /* The code executed when this rule is reduced */
+  const char *codePrefix;  /* Setup code before code[] above */
+  const char *codeSuffix;  /* Breakdown code after code[] above */
+  struct symbol *precsym;  /* Precedence symbol for this rule */
+  int index;               /* An index number for this rule */
+  int iRule;               /* Rule number as used in the generated tables */
+  Boolean noCode;          /* True if this rule has no associated C code */
+  Boolean codeEmitted;     /* True if the code has been emitted already */
+  Boolean canReduce;       /* True if this rule is ever reduced */
+  Boolean doesReduce;      /* Reduce actions occur after optimization */
+  Boolean neverReduce;     /* Reduce is theoretically possible, but prevented
+                           ** by actions or other outside implementation */
+  struct rule *nextlhs;    /* Next rule with the same LHS */
+  struct rule *next;       /* Next rule in the global list */
+};
+
+/* A configuration is a production rule of the grammar together with
+** a mark (dot) showing how much of that rule has been processed so far.
+** Configurations also contain a follow-set which is a list of terminal
+** symbols which are allowed to immediately follow the end of the rule.
+** Every configuration is recorded as an instance of the following: */
+enum cfgstatus {
+  COMPLETE,
+  INCOMPLETE
+};
+struct config {
+  struct rule *rp;         /* The rule upon which the configuration is based */
+  int dot;                 /* The parse point */
+  char *fws;               /* Follow-set for this configuration only */
+  struct plink *fplp;      /* Follow-set forward propagation links */
+  struct plink *bplp;      /* Follow-set backwards propagation links */
+  struct state *stp;       /* Pointer to state which contains this */
+  enum cfgstatus status;   /* used during followset and shift computations */
+  struct config *next;     /* Next configuration in the state */
+  struct config *bp;       /* The next basis configuration */
+};
+
+enum e_action {
+  SHIFT,
+  ACCEPT,
+  REDUCE,
+  ERROR,
+  SSCONFLICT,              /* A shift/shift conflict */
+  SRCONFLICT,              /* Was a reduce, but part of a conflict */
+  RRCONFLICT,              /* Was a reduce, but part of a conflict */
+  SH_RESOLVED,             /* Was a shift.  Precedence resolved conflict */
+  RD_RESOLVED,             /* Was reduce.  Precedence resolved conflict */
+  NOT_USED,                /* Deleted by compression */
+  SHIFTREDUCE              /* Shift first, then reduce */
+};
+
+/* Every shift or reduce operation is stored as one of the following */
+struct action {
+  struct symbol *sp;       /* The look-ahead symbol */
+  enum e_action type;
+  union {
+    struct state *stp;     /* The new state, if a shift */
+    struct rule *rp;       /* The rule, if a reduce */
+  } x;
+  struct symbol *spOpt;    /* SHIFTREDUCE optimization to this symbol */
+  struct action *next;     /* Next action for this state */
+  struct action *collide;  /* Next action with the same hash */
+};
+
+/* Each state of the generated parser's finite state machine
+** is encoded as an instance of the following structure. */
+struct state {
+  struct config *bp;       /* The basis configurations for this state */
+  struct config *cfp;      /* All configurations in this set */
+  int statenum;            /* Sequential number for this state */
+  struct action *ap;       /* List of actions for this state */
+  int nTknAct, nNtAct;     /* Number of actions on terminals and nonterminals */
+  int iTknOfst, iNtOfst;   /* yy_action[] offset for terminals and nonterms */
+  int iDfltReduce;         /* Default action is to REDUCE by this rule */
+  struct rule *pDfltReduce;/* The default REDUCE rule. */
+  int autoReduce;          /* True if this is an auto-reduce state */
+};
+#define NO_OFFSET (-2147483647)
+
+/* A followset propagation link indicates that the contents of one
+** configuration followset should be propagated to another whenever
+** the first changes. */
+struct plink {
+  struct config *cfp;      /* The configuration to which linked */
+  struct plink *next;      /* The next propagate link */
+};
+
+/* The state vector for the entire parser generator is recorded as
+** follows.  (LEMON uses no global variables and makes little use of
+** static variables.  Fields in the following structure can be thought
+** of as begin global variables in the program.) */
+struct lemon {
+  struct state **sorted;   /* Table of states sorted by state number */
+  struct rule *rule;       /* List of all rules */
+  struct rule *startRule;  /* First rule */
+  int nstate;              /* Number of states */
+  int nxstate;             /* nstate with tail degenerate states removed */
+  int nrule;               /* Number of rules */
+  int nruleWithAction;     /* Number of rules with actions */
+  int nsymbol;             /* Number of terminal and nonterminal symbols */
+  int nterminal;           /* Number of terminal symbols */
+  int minShiftReduce;      /* Minimum shift-reduce action value */
+  int errAction;           /* Error action value */
+  int accAction;           /* Accept action value */
+  int noAction;            /* No-op action value */
+  int minReduce;           /* Minimum reduce action */
+  int maxAction;           /* Maximum action value of any kind */
+  struct symbol **symbols; /* Sorted array of pointers to symbols */
+  int errorcnt;            /* Number of errors */
+  struct symbol *errsym;   /* The error symbol */
+  struct symbol *wildcard; /* Token that matches anything */
+  char *name;              /* Name of the generated parser */
+  char *arg;               /* Declaration of the 3rd argument to parser */
+  char *ctx;               /* Declaration of 2nd argument to constructor */
+  char *tokentype;         /* Type of terminal symbols in the parser stack */
+  char *vartype;           /* The default type of non-terminal symbols */
+  char *start;             /* Name of the start symbol for the grammar */
+  char *stacksize;         /* Size of the parser stack */
+  char *include;           /* Code to put at the start of the C file */
+  char *error;             /* Code to execute when an error is seen */
+  char *overflow;          /* Code to execute on a stack overflow */
+  char *failure;           /* Code to execute on parser failure */
+  char *accept;            /* Code to execute when the parser excepts */
+  char *extracode;         /* Code appended to the generated file */
+  char *tokendest;         /* Code to execute to destroy token data */
+  char *vardest;           /* Code for the default non-terminal destructor */
+  char *filename;          /* Name of the input file */
+  char *outname;           /* Name of the current output file */
+  char *tokenprefix;       /* A prefix added to token names in the .h file */
+  int nconflict;           /* Number of parsing conflicts */
+  int nactiontab;          /* Number of entries in the yy_action[] table */
+  int nlookaheadtab;       /* Number of entries in yy_lookahead[] */
+  int tablesize;           /* Total table size of all tables in bytes */
+  int basisflag;           /* Print only basis configurations */
+  int printPreprocessed;   /* Show preprocessor output on stdout */
+  int has_fallback;        /* True if any %fallback is seen in the grammar */
+  int nolinenosflag;       /* True if #line statements should not be printed */
+  char *argv0;             /* Name of the program */
+};
+
+#define MemoryCheck(X) if((X)==0){ \
+  extern void memory_error(); \
+  memory_error(); \
+}
+
+/**************** From the file "table.h" *********************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+**              "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file!  Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+/* Routines for handling a strings */
+
+const char *Strsafe(const char *);
+
+void Strsafe_init(void);
+int Strsafe_insert(const char *);
+const char *Strsafe_find(const char *);
+
+/* Routines for handling symbols of the grammar */
+
+struct symbol *Symbol_new(const char *);
+int Symbolcmpp(const void *, const void *);
+void Symbol_init(void);
+int Symbol_insert(struct symbol *, const char *);
+struct symbol *Symbol_find(const char *);
+struct symbol *Symbol_Nth(int);
+int Symbol_count(void);
+struct symbol **Symbol_arrayof(void);
+
+/* Routines to manage the state table */
+
+int Configcmp(const char *, const char *);
+struct state *State_new(void);
+void State_init(void);
+int State_insert(struct state *, struct config *);
+struct state *State_find(struct config *);
+struct state **State_arrayof(void);
+
+/* Routines used for efficiency in Configlist_add */
+
+void Configtable_init(void);
+int Configtable_insert(struct config *);
+struct config *Configtable_find(struct config *);
+void Configtable_clear(int(*)(struct config *));
+
+/****************** From the file "action.c" *******************************/
+/*
+** Routines processing parser actions in the LEMON parser generator.
+*/
+
+/* Allocate a new parser action */
+static struct action *Action_new(void){
+  static struct action *actionfreelist = 0;
+  struct action *newaction;
+
+  if( actionfreelist==0 ){
+    int i;
+    int amt = 100;
+    actionfreelist = (struct action *)calloc(amt, sizeof(struct action));
+    if( actionfreelist==0 ){
+      fprintf(stderr,"Unable to allocate memory for a new parser action.");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) actionfreelist[i].next = &actionfreelist[i+1];
+    actionfreelist[amt-1].next = 0;
+  }
+  newaction = actionfreelist;
+  actionfreelist = actionfreelist->next;
+  return newaction;
+}
+
+/* Compare two actions for sorting purposes.  Return negative, zero, or
+** positive if the first action is less than, equal to, or greater than
+** the first
+*/
+static int actioncmp(
+  struct action *ap1,
+  struct action *ap2
+){
+  int rc;
+  rc = ap1->sp->index - ap2->sp->index;
+  if( rc==0 ){
+    rc = (int)ap1->type - (int)ap2->type;
+  }
+  if( rc==0 && (ap1->type==REDUCE || ap1->type==SHIFTREDUCE) ){
+    rc = ap1->x.rp->index - ap2->x.rp->index;
+  }
+  if( rc==0 ){
+    rc = (int) (ap2 - ap1);
+  }
+  return rc;
+}
+
+/* Sort parser actions */
+static struct action *Action_sort(
+  struct action *ap
+){
+  ap = (struct action *)msort((char *)ap,(char **)&ap->next,
+                              (int(*)(const char*,const char*))actioncmp);
+  return ap;
+}
+
+void Action_add(
+  struct action **app,
+  enum e_action type,
+  struct symbol *sp,
+  char *arg
+){
+  struct action *newaction;
+  newaction = Action_new();
+  newaction->next = *app;
+  *app = newaction;
+  newaction->type = type;
+  newaction->sp = sp;
+  newaction->spOpt = 0;
+  if( type==SHIFT ){
+    newaction->x.stp = (struct state *)arg;
+  }else{
+    newaction->x.rp = (struct rule *)arg;
+  }
+}
+/********************** New code to implement the "acttab" module ***********/
+/*
+** This module implements routines use to construct the yy_action[] table.
+*/
+
+/*
+** The state of the yy_action table under construction is an instance of
+** the following structure.
+**
+** The yy_action table maps the pair (state_number, lookahead) into an
+** action_number.  The table is an array of integers pairs.  The state_number
+** determines an initial offset into the yy_action array.  The lookahead
+** value is then added to this initial offset to get an index X into the
+** yy_action array. If the aAction[X].lookahead equals the value of the
+** of the lookahead input, then the value of the action_number output is
+** aAction[X].action.  If the lookaheads do not match then the
+** default action for the state_number is returned.
+**
+** All actions associated with a single state_number are first entered
+** into aLookahead[] using multiple calls to acttab_action().  Then the
+** actions for that single state_number are placed into the aAction[]
+** array with a single call to acttab_insert().  The acttab_insert() call
+** also resets the aLookahead[] array in preparation for the next
+** state number.
+*/
+struct lookahead_action {
+  int lookahead;             /* Value of the lookahead token */
+  int action;                /* Action to take on the given lookahead */
+};
+typedef struct acttab acttab;
+struct acttab {
+  int nAction;                 /* Number of used slots in aAction[] */
+  int nActionAlloc;            /* Slots allocated for aAction[] */
+  struct lookahead_action
+    *aAction,                  /* The yy_action[] table under construction */
+    *aLookahead;               /* A single new transaction set */
+  int mnLookahead;             /* Minimum aLookahead[].lookahead */
+  int mnAction;                /* Action associated with mnLookahead */
+  int mxLookahead;             /* Maximum aLookahead[].lookahead */
+  int nLookahead;              /* Used slots in aLookahead[] */
+  int nLookaheadAlloc;         /* Slots allocated in aLookahead[] */
+  int nterminal;               /* Number of terminal symbols */
+  int nsymbol;                 /* total number of symbols */
+};
+
+/* Return the number of entries in the yy_action table */
+#define acttab_lookahead_size(X) ((X)->nAction)
+
+/* The value for the N-th entry in yy_action */
+#define acttab_yyaction(X,N)  ((X)->aAction[N].action)
+
+/* The value for the N-th entry in yy_lookahead */
+#define acttab_yylookahead(X,N)  ((X)->aAction[N].lookahead)
+
+/* Free all memory associated with the given acttab */
+void acttab_free(acttab *p){
+  free( p->aAction );
+  free( p->aLookahead );
+  free( p );
+}
+
+/* Allocate a new acttab structure */
+acttab *acttab_alloc(int nsymbol, int nterminal){
+  acttab *p = (acttab *) calloc( 1, sizeof(*p) );
+  if( p==0 ){
+    fprintf(stderr,"Unable to allocate memory for a new acttab.");
+    exit(1);
+  }
+  memset(p, 0, sizeof(*p));
+  p->nsymbol = nsymbol;
+  p->nterminal = nterminal;
+  return p;
+}
+
+/* Add a new action to the current transaction set.
+**
+** This routine is called once for each lookahead for a particular
+** state.
+*/
+void acttab_action(acttab *p, int lookahead, int action){
+  if( p->nLookahead>=p->nLookaheadAlloc ){
+    p->nLookaheadAlloc += 25;
+    p->aLookahead = (struct lookahead_action *) realloc( p->aLookahead,
+                             sizeof(p->aLookahead[0])*p->nLookaheadAlloc );
+    if( p->aLookahead==0 ){
+      fprintf(stderr,"malloc failed\n");
+      exit(1);
+    }
+  }
+  if( p->nLookahead==0 ){
+    p->mxLookahead = lookahead;
+    p->mnLookahead = lookahead;
+    p->mnAction = action;
+  }else{
+    if( p->mxLookahead<lookahead ) p->mxLookahead = lookahead;
+    if( p->mnLookahead>lookahead ){
+      p->mnLookahead = lookahead;
+      p->mnAction = action;
+    }
+  }
+  p->aLookahead[p->nLookahead].lookahead = lookahead;
+  p->aLookahead[p->nLookahead].action = action;
+  p->nLookahead++;
+}
+
+/*
+** Add the transaction set built up with prior calls to acttab_action()
+** into the current action table.  Then reset the transaction set back
+** to an empty set in preparation for a new round of acttab_action() calls.
+**
+** Return the offset into the action table of the new transaction.
+**
+** If the makeItSafe parameter is true, then the offset is chosen so that
+** it is impossible to overread the yy_lookaside[] table regardless of
+** the lookaside token.  This is done for the terminal symbols, as they
+** come from external inputs and can contain syntax errors.  When makeItSafe
+** is false, there is more flexibility in selecting offsets, resulting in
+** a smaller table.  For non-terminal symbols, which are never syntax errors,
+** makeItSafe can be false.
+*/
+int acttab_insert(acttab *p, int makeItSafe){
+  int i, j, k, n, end;
+  assert( p->nLookahead>0 );
+
+  /* Make sure we have enough space to hold the expanded action table
+  ** in the worst case.  The worst case occurs if the transaction set
+  ** must be appended to the current action table
+  */
+  n = p->nsymbol + 1;
+  if( p->nAction + n >= p->nActionAlloc ){
+    int oldAlloc = p->nActionAlloc;
+    p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20;
+    p->aAction = (struct lookahead_action *) realloc( p->aAction,
+                          sizeof(p->aAction[0])*p->nActionAlloc);
+    if( p->aAction==0 ){
+      fprintf(stderr,"malloc failed\n");
+      exit(1);
+    }
+    for(i=oldAlloc; i<p->nActionAlloc; i++){
+      p->aAction[i].lookahead = -1;
+      p->aAction[i].action = -1;
+    }
+  }
+
+  /* Scan the existing action table looking for an offset that is a
+  ** duplicate of the current transaction set.  Fall out of the loop
+  ** if and when the duplicate is found.
+  **
+  ** i is the index in p->aAction[] where p->mnLookahead is inserted.
+  */
+  end = makeItSafe ? p->mnLookahead : 0;
+  for(i=p->nAction-1; i>=end; i--){
+    if( p->aAction[i].lookahead==p->mnLookahead ){
+      /* All lookaheads and actions in the aLookahead[] transaction
+      ** must match against the candidate aAction[i] entry. */
+      if( p->aAction[i].action!=p->mnAction ) continue;
+      for(j=0; j<p->nLookahead; j++){
+        k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+        if( k<0 || k>=p->nAction ) break;
+        if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break;
+        if( p->aLookahead[j].action!=p->aAction[k].action ) break;
+      }
+      if( j<p->nLookahead ) continue;
+
+      /* No possible lookahead value that is not in the aLookahead[]
+      ** transaction is allowed to match aAction[i] */
+      n = 0;
+      for(j=0; j<p->nAction; j++){
+        if( p->aAction[j].lookahead<0 ) continue;
+        if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++;
+      }
+      if( n==p->nLookahead ){
+        break;  /* An exact match is found at offset i */
+      }
+    }
+  }
+
+  /* If no existing offsets exactly match the current transaction, find an
+  ** an empty offset in the aAction[] table in which we can add the
+  ** aLookahead[] transaction.
+  */
+  if( i<end ){
+    /* Look for holes in the aAction[] table that fit the current
+    ** aLookahead[] transaction.  Leave i set to the offset of the hole.
+    ** If no holes are found, i is left at p->nAction, which means the
+    ** transaction will be appended. */
+    i = makeItSafe ? p->mnLookahead : 0;
+    for(; i<p->nActionAlloc - p->mxLookahead; i++){
+      if( p->aAction[i].lookahead<0 ){
+        for(j=0; j<p->nLookahead; j++){
+          k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+          if( k<0 ) break;
+          if( p->aAction[k].lookahead>=0 ) break;
+        }
+        if( j<p->nLookahead ) continue;
+        for(j=0; j<p->nAction; j++){
+          if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break;
+        }
+        if( j==p->nAction ){
+          break;  /* Fits in empty slots */
+        }
+      }
+    }
+  }
+  /* Insert transaction set at index i. */
+#if 0
+  printf("Acttab:");
+  for(j=0; j<p->nLookahead; j++){
+    printf(" %d", p->aLookahead[j].lookahead);
+  }
+  printf(" inserted at %d\n", i);
+#endif
+  for(j=0; j<p->nLookahead; j++){
+    k = p->aLookahead[j].lookahead - p->mnLookahead + i;
+    p->aAction[k] = p->aLookahead[j];
+    if( k>=p->nAction ) p->nAction = k+1;
+  }
+  if( makeItSafe && i+p->nterminal>=p->nAction ) p->nAction = i+p->nterminal+1;
+  p->nLookahead = 0;
+
+  /* Return the offset that is added to the lookahead in order to get the
+  ** index into yy_action of the action */
+  return i - p->mnLookahead;
+}
+
+/*
+** Return the size of the action table without the trailing syntax error
+** entries.
+*/
+int acttab_action_size(acttab *p){
+  int n = p->nAction;
+  while( n>0 && p->aAction[n-1].lookahead<0 ){ n--; }
+  return n;
+}
+
+/********************** From the file "build.c" *****************************/
+/*
+** Routines to construction the finite state machine for the LEMON
+** parser generator.
+*/
+
+/* Find a precedence symbol of every rule in the grammar.
+**
+** Those rules which have a precedence symbol coded in the input
+** grammar using the "[symbol]" construct will already have the
+** rp->precsym field filled.  Other rules take as their precedence
+** symbol the first RHS symbol with a defined precedence.  If there
+** are not RHS symbols with a defined precedence, the precedence
+** symbol field is left blank.
+*/
+void FindRulePrecedences(struct lemon *xp)
+{
+  struct rule *rp;
+  for(rp=xp->rule; rp; rp=rp->next){
+    if( rp->precsym==0 ){
+      int i, j;
+      for(i=0; i<rp->nrhs && rp->precsym==0; i++){
+        struct symbol *sp = rp->rhs[i];
+        if( sp->type==MULTITERMINAL ){
+          for(j=0; j<sp->nsubsym; j++){
+            if( sp->subsym[j]->prec>=0 ){
+              rp->precsym = sp->subsym[j];
+              break;
+            }
+          }
+        }else if( sp->prec>=0 ){
+          rp->precsym = rp->rhs[i];
+        }
+      }
+    }
+  }
+  return;
+}
+
+/* Find all nonterminals which will generate the empty string.
+** Then go back and compute the first sets of every nonterminal.
+** The first set is the set of all terminal symbols which can begin
+** a string generated by that nonterminal.
+*/
+void FindFirstSets(struct lemon *lemp)
+{
+  int i, j;
+  struct rule *rp;
+  int progress;
+
+  for(i=0; i<lemp->nsymbol; i++){
+    lemp->symbols[i]->lambda = LEMON_FALSE;
+  }
+  for(i=lemp->nterminal; i<lemp->nsymbol; i++){
+    lemp->symbols[i]->firstset = SetNew();
+  }
+
+  /* First compute all lambdas */
+  do{
+    progress = 0;
+    for(rp=lemp->rule; rp; rp=rp->next){
+      if( rp->lhs->lambda ) continue;
+      for(i=0; i<rp->nrhs; i++){
+        struct symbol *sp = rp->rhs[i];
+        assert( sp->type==NONTERMINAL || sp->lambda==LEMON_FALSE );
+        if( sp->lambda==LEMON_FALSE ) break;
+      }
+      if( i==rp->nrhs ){
+        rp->lhs->lambda = LEMON_TRUE;
+        progress = 1;
+      }
+    }
+  }while( progress );
+
+  /* Now compute all first sets */
+  do{
+    struct symbol *s1, *s2;
+    progress = 0;
+    for(rp=lemp->rule; rp; rp=rp->next){
+      s1 = rp->lhs;
+      for(i=0; i<rp->nrhs; i++){
+        s2 = rp->rhs[i];
+        if( s2->type==TERMINAL ){
+          progress += SetAdd(s1->firstset,s2->index);
+          break;
+        }else if( s2->type==MULTITERMINAL ){
+          for(j=0; j<s2->nsubsym; j++){
+            progress += SetAdd(s1->firstset,s2->subsym[j]->index);
+          }
+          break;
+        }else if( s1==s2 ){
+          if( s1->lambda==LEMON_FALSE ) break;
+        }else{
+          progress += SetUnion(s1->firstset,s2->firstset);
+          if( s2->lambda==LEMON_FALSE ) break;
+        }
+      }
+    }
+  }while( progress );
+  return;
+}
+
+/* Compute all LR(0) states for the grammar.  Links
+** are added to between some states so that the LR(1) follow sets
+** can be computed later.
+*/
+PRIVATE struct state *getstate(struct lemon *);  /* forward reference */
+void FindStates(struct lemon *lemp)
+{
+  struct symbol *sp;
+  struct rule *rp;
+
+  Configlist_init();
+
+  /* Find the start symbol */
+  if( lemp->start ){
+    sp = Symbol_find(lemp->start);
+    if( sp==0 ){
+      ErrorMsg(lemp->filename,0,
+        "The specified start symbol \"%s\" is not "
+        "in a nonterminal of the grammar.  \"%s\" will be used as the start "
+        "symbol instead.",lemp->start,lemp->startRule->lhs->name);
+      lemp->errorcnt++;
+      sp = lemp->startRule->lhs;
+    }
+  }else if( lemp->startRule ){
+    sp = lemp->startRule->lhs;
+  }else{
+    ErrorMsg(lemp->filename,0,"Internal error - no start rule\n");
+    exit(1);
+  }
+
+  /* Make sure the start symbol doesn't occur on the right-hand side of
+  ** any rule.  Report an error if it does.  (YACC would generate a new
+  ** start symbol in this case.) */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    int i;
+    for(i=0; i<rp->nrhs; i++){
+      if( rp->rhs[i]==sp ){   /* FIX ME:  Deal with multiterminals */
+        ErrorMsg(lemp->filename,0,
+          "The start symbol \"%s\" occurs on the "
+          "right-hand side of a rule. This will result in a parser which "
+          "does not work properly.",sp->name);
+        lemp->errorcnt++;
+      }
+    }
+  }
+
+  /* The basis configuration set for the first state
+  ** is all rules which have the start symbol as their
+  ** left-hand side */
+  for(rp=sp->rule; rp; rp=rp->nextlhs){
+    struct config *newcfp;
+    rp->lhsStart = 1;
+    newcfp = Configlist_addbasis(rp,0);
+    SetAdd(newcfp->fws,0);
+  }
+
+  /* Compute the first state.  All other states will be
+  ** computed automatically during the computation of the first one.
+  ** The returned pointer to the first state is not used. */
+  (void)getstate(lemp);
+  return;
+}
+
+/* Return a pointer to a state which is described by the configuration
+** list which has been built from calls to Configlist_add.
+*/
+PRIVATE void buildshifts(struct lemon *, struct state *); /* Forwd ref */
+PRIVATE struct state *getstate(struct lemon *lemp)
+{
+  struct config *cfp, *bp;
+  struct state *stp;
+
+  /* Extract the sorted basis of the new state.  The basis was constructed
+  ** by prior calls to "Configlist_addbasis()". */
+  Configlist_sortbasis();
+  bp = Configlist_basis();
+
+  /* Get a state with the same basis */
+  stp = State_find(bp);
+  if( stp ){
+    /* A state with the same basis already exists!  Copy all the follow-set
+    ** propagation links from the state under construction into the
+    ** preexisting state, then return a pointer to the preexisting state */
+    struct config *x, *y;
+    for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){
+      Plink_copy(&y->bplp,x->bplp);
+      Plink_delete(x->fplp);
+      x->fplp = x->bplp = 0;
+    }
+    cfp = Configlist_return();
+    Configlist_eat(cfp);
+  }else{
+    /* This really is a new state.  Construct all the details */
+    Configlist_closure(lemp);    /* Compute the configuration closure */
+    Configlist_sort();           /* Sort the configuration closure */
+    cfp = Configlist_return();   /* Get a pointer to the config list */
+    stp = State_new();           /* A new state structure */
+    MemoryCheck(stp);
+    stp->bp = bp;                /* Remember the configuration basis */
+    stp->cfp = cfp;              /* Remember the configuration closure */
+    stp->statenum = lemp->nstate++; /* Every state gets a sequence number */
+    stp->ap = 0;                 /* No actions, yet. */
+    State_insert(stp,stp->bp);   /* Add to the state table */
+    buildshifts(lemp,stp);       /* Recursively compute successor states */
+  }
+  return stp;
+}
+
+/*
+** Return true if two symbols are the same.
+*/
+int same_symbol(struct symbol *a, struct symbol *b)
+{
+  int i;
+  if( a==b ) return 1;
+  if( a->type!=MULTITERMINAL ) return 0;
+  if( b->type!=MULTITERMINAL ) return 0;
+  if( a->nsubsym!=b->nsubsym ) return 0;
+  for(i=0; i<a->nsubsym; i++){
+    if( a->subsym[i]!=b->subsym[i] ) return 0;
+  }
+  return 1;
+}
+
+/* Construct all successor states to the given state.  A "successor"
+** state is any state which can be reached by a shift action.
+*/
+PRIVATE void buildshifts(struct lemon *lemp, struct state *stp)
+{
+  struct config *cfp;  /* For looping thru the config closure of "stp" */
+  struct config *bcfp; /* For the inner loop on config closure of "stp" */
+  struct config *newcfg;  /* */
+  struct symbol *sp;   /* Symbol following the dot in configuration "cfp" */
+  struct symbol *bsp;  /* Symbol following the dot in configuration "bcfp" */
+  struct state *newstp; /* A pointer to a successor state */
+
+  /* Each configuration becomes complete after it contributes to a successor
+  ** state.  Initially, all configurations are incomplete */
+  for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE;
+
+  /* Loop through all configurations of the state "stp" */
+  for(cfp=stp->cfp; cfp; cfp=cfp->next){
+    if( cfp->status==COMPLETE ) continue;    /* Already used by inner loop */
+    if( cfp->dot>=cfp->rp->nrhs ) continue;  /* Can't shift this config */
+    Configlist_reset();                      /* Reset the new config set */
+    sp = cfp->rp->rhs[cfp->dot];             /* Symbol after the dot */
+
+    /* For every configuration in the state "stp" which has the symbol "sp"
+    ** following its dot, add the same configuration to the basis set under
+    ** construction but with the dot shifted one symbol to the right. */
+    for(bcfp=cfp; bcfp; bcfp=bcfp->next){
+      if( bcfp->status==COMPLETE ) continue;    /* Already used */
+      if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */
+      bsp = bcfp->rp->rhs[bcfp->dot];           /* Get symbol after dot */
+      if( !same_symbol(bsp,sp) ) continue;      /* Must be same as for "cfp" */
+      bcfp->status = COMPLETE;                  /* Mark this config as used */
+      newcfg = Configlist_addbasis(bcfp->rp,bcfp->dot+1);
+      Plink_add(&newcfg->bplp,bcfp);
+    }
+
+    /* Get a pointer to the state described by the basis configuration set
+    ** constructed in the preceding loop */
+    newstp = getstate(lemp);
+
+    /* The state "newstp" is reached from the state "stp" by a shift action
+    ** on the symbol "sp" */
+    if( sp->type==MULTITERMINAL ){
+      int i;
+      for(i=0; i<sp->nsubsym; i++){
+        Action_add(&stp->ap,SHIFT,sp->subsym[i],(char*)newstp);
+      }
+    }else{
+      Action_add(&stp->ap,SHIFT,sp,(char *)newstp);
+    }
+  }
+}
+
+/*
+** Construct the propagation links
+*/
+void FindLinks(struct lemon *lemp)
+{
+  int i;
+  struct config *cfp, *other;
+  struct state *stp;
+  struct plink *plp;
+
+  /* Housekeeping detail:
+  ** Add to every propagate link a pointer back to the state to
+  ** which the link is attached. */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(cfp=stp?stp->cfp:0; cfp; cfp=cfp->next){
+      cfp->stp = stp;
+    }
+  }
+
+  /* Convert all backlinks into forward links.  Only the forward
+  ** links are used in the follow-set computation. */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(cfp=stp?stp->cfp:0; cfp; cfp=cfp->next){
+      for(plp=cfp->bplp; plp; plp=plp->next){
+        other = plp->cfp;
+        Plink_add(&other->fplp,cfp);
+      }
+    }
+  }
+}
+
+/* Compute all followsets.
+**
+** A followset is the set of all symbols which can come immediately
+** after a configuration.
+*/
+void FindFollowSets(struct lemon *lemp)
+{
+  int i;
+  struct config *cfp;
+  struct plink *plp;
+  int progress;
+  int change;
+
+  for(i=0; i<lemp->nstate; i++){
+    assert( lemp->sorted[i]!=0 );
+    for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+      cfp->status = INCOMPLETE;
+    }
+  }
+
+  do{
+    progress = 0;
+    for(i=0; i<lemp->nstate; i++){
+      assert( lemp->sorted[i]!=0 );
+      for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){
+        if( cfp->status==COMPLETE ) continue;
+        for(plp=cfp->fplp; plp; plp=plp->next){
+          change = SetUnion(plp->cfp->fws,cfp->fws);
+          if( change ){
+            plp->cfp->status = INCOMPLETE;
+            progress = 1;
+          }
+        }
+        cfp->status = COMPLETE;
+      }
+    }
+  }while( progress );
+}
+
+static int resolve_conflict(struct action *,struct action *);
+
+/* Compute the reduce actions, and resolve conflicts.
+*/
+void FindActions(struct lemon *lemp)
+{
+  int i,j;
+  struct config *cfp;
+  struct state *stp;
+  struct symbol *sp;
+  struct rule *rp;
+
+  /* Add all of the reduce actions
+  ** A reduce action is added for each element of the followset of
+  ** a configuration which has its dot at the extreme right.
+  */
+  for(i=0; i<lemp->nstate; i++){   /* Loop over all states */
+    stp = lemp->sorted[i];
+    for(cfp=stp->cfp; cfp; cfp=cfp->next){  /* Loop over all configurations */
+      if( cfp->rp->nrhs==cfp->dot ){        /* Is dot at extreme right? */
+        for(j=0; j<lemp->nterminal; j++){
+          if( SetFind(cfp->fws,j) ){
+            /* Add a reduce action to the state "stp" which will reduce by the
+            ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */
+            Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp);
+          }
+        }
+      }
+    }
+  }
+
+  /* Add the accepting token */
+  if( lemp->start ){
+    sp = Symbol_find(lemp->start);
+    if( sp==0 ){
+      if( lemp->startRule==0 ){
+        fprintf(stderr, "internal error on source line %d: no start rule\n",
+                __LINE__);
+        exit(1);
+      }
+      sp = lemp->startRule->lhs;
+    }
+  }else{
+    sp = lemp->startRule->lhs;
+  }
+  /* Add to the first state (which is always the starting state of the
+  ** finite state machine) an action to ACCEPT if the lookahead is the
+  ** start nonterminal.  */
+  Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0);
+
+  /* Resolve conflicts */
+  for(i=0; i<lemp->nstate; i++){
+    struct action *ap, *nap;
+    stp = lemp->sorted[i];
+    /* assert( stp->ap ); */
+    stp->ap = Action_sort(stp->ap);
+    for(ap=stp->ap; ap && ap->next; ap=ap->next){
+      for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){
+         /* The two actions "ap" and "nap" have the same lookahead.
+         ** Figure out which one should be used */
+         lemp->nconflict += resolve_conflict(ap,nap);
+      }
+    }
+  }
+
+  /* Report an error for each rule that can never be reduced. */
+  for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = LEMON_FALSE;
+  for(i=0; i<lemp->nstate; i++){
+    struct action *ap;
+    for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+      if( ap->type==REDUCE ) ap->x.rp->canReduce = LEMON_TRUE;
+    }
+  }
+  for(rp=lemp->rule; rp; rp=rp->next){
+    if( rp->canReduce ) continue;
+    ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n");
+    lemp->errorcnt++;
+  }
+}
+
+/* Resolve a conflict between the two given actions.  If the
+** conflict can't be resolved, return non-zero.
+**
+** NO LONGER TRUE:
+**   To resolve a conflict, first look to see if either action
+**   is on an error rule.  In that case, take the action which
+**   is not associated with the error rule.  If neither or both
+**   actions are associated with an error rule, then try to
+**   use precedence to resolve the conflict.
+**
+** If either action is a SHIFT, then it must be apx.  This
+** function won't work if apx->type==REDUCE and apy->type==SHIFT.
+*/
+static int resolve_conflict(
+  struct action *apx,
+  struct action *apy
+){
+  struct symbol *spx, *spy;
+  int errcnt = 0;
+  assert( apx->sp==apy->sp );  /* Otherwise there would be no conflict */
+  if( apx->type==SHIFT && apy->type==SHIFT ){
+    apy->type = SSCONFLICT;
+    errcnt++;
+  }
+  if( apx->type==SHIFT && apy->type==REDUCE ){
+    spx = apx->sp;
+    spy = apy->x.rp->precsym;
+    if( spy==0 || spx->prec<0 || spy->prec<0 ){
+      /* Not enough precedence information. */
+      apy->type = SRCONFLICT;
+      errcnt++;
+    }else if( spx->prec>spy->prec ){    /* higher precedence wins */
+      apy->type = RD_RESOLVED;
+    }else if( spx->prec<spy->prec ){
+      apx->type = SH_RESOLVED;
+    }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */
+      apy->type = RD_RESOLVED;                             /* associativity */
+    }else if( spx->prec==spy->prec && spx->assoc==LEFT ){  /* to break tie */
+      apx->type = SH_RESOLVED;
+    }else{
+      assert( spx->prec==spy->prec && spx->assoc==NONE );
+      apx->type = ERROR;
+    }
+  }else if( apx->type==REDUCE && apy->type==REDUCE ){
+    spx = apx->x.rp->precsym;
+    spy = apy->x.rp->precsym;
+    if( spx==0 || spy==0 || spx->prec<0 ||
+    spy->prec<0 || spx->prec==spy->prec ){
+      apy->type = RRCONFLICT;
+      errcnt++;
+    }else if( spx->prec>spy->prec ){
+      apy->type = RD_RESOLVED;
+    }else if( spx->prec<spy->prec ){
+      apx->type = RD_RESOLVED;
+    }
+  }else{
+    assert(
+      apx->type==SH_RESOLVED ||
+      apx->type==RD_RESOLVED ||
+      apx->type==SSCONFLICT ||
+      apx->type==SRCONFLICT ||
+      apx->type==RRCONFLICT ||
+      apy->type==SH_RESOLVED ||
+      apy->type==RD_RESOLVED ||
+      apy->type==SSCONFLICT ||
+      apy->type==SRCONFLICT ||
+      apy->type==RRCONFLICT
+    );
+    /* The REDUCE/SHIFT case cannot happen because SHIFTs come before
+    ** REDUCEs on the list.  If we reach this point it must be because
+    ** the parser conflict had already been resolved. */
+  }
+  return errcnt;
+}
+/********************* From the file "configlist.c" *************************/
+/*
+** Routines to processing a configuration list and building a state
+** in the LEMON parser generator.
+*/
+
+static struct config *freelist = 0;      /* List of free configurations */
+static struct config *current = 0;       /* Top of list of configurations */
+static struct config **currentend = 0;   /* Last on list of configs */
+static struct config *basis = 0;         /* Top of list of basis configs */
+static struct config **basisend = 0;     /* End of list of basis configs */
+
+/* Return a pointer to a new configuration */
+PRIVATE struct config *newconfig(void){
+  return (struct config*)calloc(1, sizeof(struct config));
+}
+
+/* The configuration "old" is no longer used */
+PRIVATE void deleteconfig(struct config *old)
+{
+  old->next = freelist;
+  freelist = old;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_init(void){
+  current = 0;
+  currentend = &current;
+  basis = 0;
+  basisend = &basis;
+  Configtable_init();
+  return;
+}
+
+/* Initialized the configuration list builder */
+void Configlist_reset(void){
+  current = 0;
+  currentend = &current;
+  basis = 0;
+  basisend = &basis;
+  Configtable_clear(0);
+  return;
+}
+
+/* Add another configuration to the configuration list */
+struct config *Configlist_add(
+  struct rule *rp,    /* The rule */
+  int dot             /* Index into the RHS of the rule where the dot goes */
+){
+  struct config *cfp, model;
+
+  assert( currentend!=0 );
+  model.rp = rp;
+  model.dot = dot;
+  cfp = Configtable_find(&model);
+  if( cfp==0 ){
+    cfp = newconfig();
+    cfp->rp = rp;
+    cfp->dot = dot;
+    cfp->fws = SetNew();
+    cfp->stp = 0;
+    cfp->fplp = cfp->bplp = 0;
+    cfp->next = 0;
+    cfp->bp = 0;
+    *currentend = cfp;
+    currentend = &cfp->next;
+    Configtable_insert(cfp);
+  }
+  return cfp;
+}
+
+/* Add a basis configuration to the configuration list */
+struct config *Configlist_addbasis(struct rule *rp, int dot)
+{
+  struct config *cfp, model;
+
+  assert( basisend!=0 );
+  assert( currentend!=0 );
+  model.rp = rp;
+  model.dot = dot;
+  cfp = Configtable_find(&model);
+  if( cfp==0 ){
+    cfp = newconfig();
+    cfp->rp = rp;
+    cfp->dot = dot;
+    cfp->fws = SetNew();
+    cfp->stp = 0;
+    cfp->fplp = cfp->bplp = 0;
+    cfp->next = 0;
+    cfp->bp = 0;
+    *currentend = cfp;
+    currentend = &cfp->next;
+    *basisend = cfp;
+    basisend = &cfp->bp;
+    Configtable_insert(cfp);
+  }
+  return cfp;
+}
+
+/* Compute the closure of the configuration list */
+void Configlist_closure(struct lemon *lemp)
+{
+  struct config *cfp, *newcfp;
+  struct rule *rp, *newrp;
+  struct symbol *sp, *xsp;
+  int i, dot;
+
+  assert( currentend!=0 );
+  for(cfp=current; cfp; cfp=cfp->next){
+    rp = cfp->rp;
+    dot = cfp->dot;
+    if( dot>=rp->nrhs ) continue;
+    sp = rp->rhs[dot];
+    if( sp->type==NONTERMINAL ){
+      if( sp->rule==0 && sp!=lemp->errsym ){
+        ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.",
+          sp->name);
+        lemp->errorcnt++;
+      }
+      for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){
+        newcfp = Configlist_add(newrp,0);
+        for(i=dot+1; i<rp->nrhs; i++){
+          xsp = rp->rhs[i];
+          if( xsp->type==TERMINAL ){
+            SetAdd(newcfp->fws,xsp->index);
+            break;
+          }else if( xsp->type==MULTITERMINAL ){
+            int k;
+            for(k=0; k<xsp->nsubsym; k++){
+              SetAdd(newcfp->fws, xsp->subsym[k]->index);
+            }
+            break;
+          }else{
+            SetUnion(newcfp->fws,xsp->firstset);
+            if( xsp->lambda==LEMON_FALSE ) break;
+          }
+        }
+        if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp);
+      }
+    }
+  }
+  return;
+}
+
+/* Sort the configuration list */
+void Configlist_sort(void){
+  current = (struct config*)msort((char*)current,(char**)&(current->next),
+                                  Configcmp);
+  currentend = 0;
+  return;
+}
+
+/* Sort the basis configuration list */
+void Configlist_sortbasis(void){
+  basis = (struct config*)msort((char*)current,(char**)&(current->bp),
+                                Configcmp);
+  basisend = 0;
+  return;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_return(void){
+  struct config *old;
+  old = current;
+  current = 0;
+  currentend = 0;
+  return old;
+}
+
+/* Return a pointer to the head of the configuration list and
+** reset the list */
+struct config *Configlist_basis(void){
+  struct config *old;
+  old = basis;
+  basis = 0;
+  basisend = 0;
+  return old;
+}
+
+/* Free all elements of the given configuration list */
+void Configlist_eat(struct config *cfp)
+{
+  struct config *nextcfp;
+  for(; cfp; cfp=nextcfp){
+    nextcfp = cfp->next;
+    assert( cfp->fplp==0 );
+    assert( cfp->bplp==0 );
+    if( cfp->fws ) SetFree(cfp->fws);
+    deleteconfig(cfp);
+  }
+  return;
+}
+/***************** From the file "error.c" *********************************/
+/*
+** Code for printing error message.
+*/
+
+void ErrorMsg(const char *filename, int lineno, const char *format, ...){
+  va_list ap;
+  fprintf(stderr, "%s:%d: ", filename, lineno);
+  va_start(ap, format);
+  vfprintf(stderr,format,ap);
+  va_end(ap);
+  fprintf(stderr, "\n");
+}
+/**************** From the file "main.c" ************************************/
+/*
+** Main program file for the LEMON parser generator.
+*/
+
+/* Report an out-of-memory condition and abort.  This function
+** is used mostly by the "MemoryCheck" macro in struct.h
+*/
+void memory_error(void){
+  fprintf(stderr,"Out of memory.  Aborting...\n");
+  exit(1);
+}
+
+static int nDefine = 0;      /* Number of -D options on the command line */
+static char **azDefine = 0;  /* Name of the -D macros */
+
+/* This routine is called with the argument to each -D command-line option.
+** Add the macro defined to the azDefine array.
+*/
+static void handle_D_option(char *z){
+  char **paz;
+  nDefine++;
+  azDefine = (char **) realloc(azDefine, sizeof(azDefine[0])*nDefine);
+  if( azDefine==0 ){
+    fprintf(stderr,"out of memory\n");
+    exit(1);
+  }
+  paz = &azDefine[nDefine-1];
+  *paz = (char *) malloc( lemonStrlen(z)+1 );
+  if( *paz==0 ){
+    fprintf(stderr,"out of memory\n");
+    exit(1);
+  }
+  lemon_strcpy(*paz, z);
+  for(z=*paz; *z && *z!='='; z++){}
+  *z = 0;
+}
+
+/* Rember the name of the output directory 
+*/
+static char *outputDir = NULL;
+static void handle_d_option(char *z){
+  outputDir = (char *) malloc( lemonStrlen(z)+1 );
+  if( outputDir==0 ){
+    fprintf(stderr,"out of memory\n");
+    exit(1);
+  }
+  lemon_strcpy(outputDir, z);
+}
+
+static char *user_templatename = NULL;
+static void handle_T_option(char *z){
+  user_templatename = (char *) malloc( lemonStrlen(z)+1 );
+  if( user_templatename==0 ){
+    memory_error();
+  }
+  lemon_strcpy(user_templatename, z);
+}
+
+/* Merge together to lists of rules ordered by rule.iRule */
+static struct rule *Rule_merge(struct rule *pA, struct rule *pB){
+  struct rule *pFirst = 0;
+  struct rule **ppPrev = &pFirst;
+  while( pA && pB ){
+    if( pA->iRule<pB->iRule ){
+      *ppPrev = pA;
+      ppPrev = &pA->next;
+      pA = pA->next;
+    }else{
+      *ppPrev = pB;
+      ppPrev = &pB->next;
+      pB = pB->next;
+    }
+  }
+  if( pA ){
+    *ppPrev = pA;
+  }else{
+    *ppPrev = pB;
+  }
+  return pFirst;
+}
+
+/*
+** Sort a list of rules in order of increasing iRule value
+*/
+static struct rule *Rule_sort(struct rule *rp){
+  unsigned int i;
+  struct rule *pNext;
+  struct rule *x[32];
+  memset(x, 0, sizeof(x));
+  while( rp ){
+    pNext = rp->next;
+    rp->next = 0;
+    for(i=0; i<sizeof(x)/sizeof(x[0])-1 && x[i]; i++){
+      rp = Rule_merge(x[i], rp);
+      x[i] = 0;
+    }
+    x[i] = rp;
+    rp = pNext;
+  }
+  rp = 0;
+  for(i=0; i<sizeof(x)/sizeof(x[0]); i++){
+    rp = Rule_merge(x[i], rp);
+  }
+  return rp;
+}
+
+/* forward reference */
+static const char *minimum_size_type(int lwr, int upr, int *pnByte);
+
+/* Print a single line of the "Parser Stats" output
+*/
+static void stats_line(const char *zLabel, int iValue){
+  int nLabel = lemonStrlen(zLabel);
+  printf("  %s%.*s %5d\n", zLabel,
+         35-nLabel, "................................",
+         iValue);
+}
+
+/* The main program.  Parse the command line and do it... */
+int main(int argc, char **argv){
+  static int version = 0;
+  static int rpflag = 0;
+  static int basisflag = 0;
+  static int compress = 0;
+  static int quiet = 0;
+  static int statistics = 0;
+  static int mhflag = 0;
+  static int nolinenosflag = 0;
+  static int noResort = 0;
+  static int sqlFlag = 0;
+  static int printPP = 0;
+  
+  static struct s_options options[] = {
+    {OPT_FLAG, "b", (char*)&basisflag, "Print only the basis in report."},
+    {OPT_FLAG, "c", (char*)&compress, "Don't compress the action table."},
+    {OPT_FSTR, "d", (char*)&handle_d_option, "Output directory.  Default '.'"},
+    {OPT_FSTR, "D", (char*)handle_D_option, "Define an %ifdef macro."},
+    {OPT_FLAG, "E", (char*)&printPP, "Print input file after preprocessing."},
+    {OPT_FSTR, "f", 0, "Ignored.  (Placeholder for -f compiler options.)"},
+    {OPT_FLAG, "g", (char*)&rpflag, "Print grammar without actions."},
+    {OPT_FSTR, "I", 0, "Ignored.  (Placeholder for '-I' compiler options.)"},
+    {OPT_FLAG, "m", (char*)&mhflag, "Output a makeheaders compatible file."},
+    {OPT_FLAG, "l", (char*)&nolinenosflag, "Do not print #line statements."},
+    {OPT_FSTR, "O", 0, "Ignored.  (Placeholder for '-O' compiler options.)"},
+    {OPT_FLAG, "p", (char*)&showPrecedenceConflict,
+                    "Show conflicts resolved by precedence rules"},
+    {OPT_FLAG, "q", (char*)&quiet, "(Quiet) Don't print the report file."},
+    {OPT_FLAG, "r", (char*)&noResort, "Do not sort or renumber states"},
+    {OPT_FLAG, "s", (char*)&statistics,
+                                   "Print parser stats to standard output."},
+    {OPT_FLAG, "S", (char*)&sqlFlag,
+                    "Generate the *.sql file describing the parser tables."},
+    {OPT_FLAG, "x", (char*)&version, "Print the version number."},
+    {OPT_FSTR, "T", (char*)handle_T_option, "Specify a template file."},
+    {OPT_FSTR, "W", 0, "Ignored.  (Placeholder for '-W' compiler options.)"},
+    {OPT_FLAG,0,0,0}
+  };
+  int i;
+  int exitcode;
+  struct lemon lem;
+  struct rule *rp;
+
+  (void)argc;
+  OptInit(argv,options,stderr);
+  if( version ){
+     printf("Lemon version 1.0\n");
+     exit(0);
+  }
+  if( OptNArgs()!=1 ){
+    fprintf(stderr,"Exactly one filename argument is required.\n");
+    exit(1);
+  }
+  memset(&lem, 0, sizeof(lem));
+  lem.errorcnt = 0;
+
+  /* Initialize the machine */
+  Strsafe_init();
+  Symbol_init();
+  State_init();
+  lem.argv0 = argv[0];
+  lem.filename = OptArg(0);
+  lem.basisflag = basisflag;
+  lem.nolinenosflag = nolinenosflag;
+  lem.printPreprocessed = printPP;
+  Symbol_new("$");
+
+  /* Parse the input file */
+  Parse(&lem);
+  if( lem.printPreprocessed || lem.errorcnt ) exit(lem.errorcnt);
+  if( lem.nrule==0 ){
+    fprintf(stderr,"Empty grammar.\n");
+    exit(1);
+  }
+  lem.errsym = Symbol_find("error");
+
+  /* Count and index the symbols of the grammar */
+  Symbol_new("{default}");
+  lem.nsymbol = Symbol_count();
+  lem.symbols = Symbol_arrayof();
+  for(i=0; i<lem.nsymbol; i++) lem.symbols[i]->index = i;
+  qsort(lem.symbols,lem.nsymbol,sizeof(struct symbol*), Symbolcmpp);
+  for(i=0; i<lem.nsymbol; i++) lem.symbols[i]->index = i;
+  while( lem.symbols[i-1]->type==MULTITERMINAL ){ i--; }
+  assert( strcmp(lem.symbols[i-1]->name,"{default}")==0 );
+  lem.nsymbol = i - 1;
+  for(i=1; ISUPPER(lem.symbols[i]->name[0]); i++);
+  lem.nterminal = i;
+
+  /* Assign sequential rule numbers.  Start with 0.  Put rules that have no
+  ** reduce action C-code associated with them last, so that the switch()
+  ** statement that selects reduction actions will have a smaller jump table.
+  */
+  for(i=0, rp=lem.rule; rp; rp=rp->next){
+    rp->iRule = rp->code ? i++ : -1;
+  }
+  lem.nruleWithAction = i;
+  for(rp=lem.rule; rp; rp=rp->next){
+    if( rp->iRule<0 ) rp->iRule = i++;
+  }
+  lem.startRule = lem.rule;
+  lem.rule = Rule_sort(lem.rule);
+
+  /* Generate a reprint of the grammar, if requested on the command line */
+  if( rpflag ){
+    Reprint(&lem);
+  }else{
+    /* Initialize the size for all follow and first sets */
+    SetSize(lem.nterminal+1);
+
+    /* Find the precedence for every production rule (that has one) */
+    FindRulePrecedences(&lem);
+
+    /* Compute the lambda-nonterminals and the first-sets for every
+    ** nonterminal */
+    FindFirstSets(&lem);
+
+    /* Compute all LR(0) states.  Also record follow-set propagation
+    ** links so that the follow-set can be computed later */
+    lem.nstate = 0;
+    FindStates(&lem);
+    lem.sorted = State_arrayof();
+
+    /* Tie up loose ends on the propagation links */
+    FindLinks(&lem);
+
+    /* Compute the follow set of every reducible configuration */
+    FindFollowSets(&lem);
+
+    /* Compute the action tables */
+    FindActions(&lem);
+
+    /* Compress the action tables */
+    if( compress==0 ) CompressTables(&lem);
+
+    /* Reorder and renumber the states so that states with fewer choices
+    ** occur at the end.  This is an optimization that helps make the
+    ** generated parser tables smaller. */
+    if( noResort==0 ) ResortStates(&lem);
+
+    /* Generate a report of the parser generated.  (the "y.output" file) */
+    if( !quiet ) ReportOutput(&lem);
+
+    /* Generate the source code for the parser */
+    ReportTable(&lem, mhflag, sqlFlag);
+
+    /* Produce a header file for use by the scanner.  (This step is
+    ** omitted if the "-m" option is used because makeheaders will
+    ** generate the file for us.) */
+    if( !mhflag ) ReportHeader(&lem);
+  }
+  if( statistics ){
+    printf("Parser statistics:\n");
+    stats_line("terminal symbols", lem.nterminal);
+    stats_line("non-terminal symbols", lem.nsymbol - lem.nterminal);
+    stats_line("total symbols", lem.nsymbol);
+    stats_line("rules", lem.nrule);
+    stats_line("states", lem.nxstate);
+    stats_line("conflicts", lem.nconflict);
+    stats_line("action table entries", lem.nactiontab);
+    stats_line("lookahead table entries", lem.nlookaheadtab);
+    stats_line("total table size (bytes)", lem.tablesize);
+  }
+  if( lem.nconflict > 0 ){
+    fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict);
+  }
+
+  /* return 0 on success, 1 on failure. */
+  exitcode = ((lem.errorcnt > 0) || (lem.nconflict > 0)) ? 1 : 0;
+  exit(exitcode);
+  return (exitcode);
+}
+/******************** From the file "msort.c" *******************************/
+/*
+** A generic merge-sort program.
+**
+** USAGE:
+** Let "ptr" be a pointer to some structure which is at the head of
+** a null-terminated list.  Then to sort the list call:
+**
+**     ptr = msort(ptr,&(ptr->next),cmpfnc);
+**
+** In the above, "cmpfnc" is a pointer to a function which compares
+** two instances of the structure and returns an integer, as in
+** strcmp.  The second argument is a pointer to the pointer to the
+** second element of the linked list.  This address is used to compute
+** the offset to the "next" field within the structure.  The offset to
+** the "next" field must be constant for all structures in the list.
+**
+** The function returns a new pointer which is the head of the list
+** after sorting.
+**
+** ALGORITHM:
+** Merge-sort.
+*/
+
+/*
+** Return a pointer to the next structure in the linked list.
+*/
+#define NEXT(A) (*(char**)(((char*)A)+offset))
+
+/*
+** Inputs:
+**   a:       A sorted, null-terminated linked list.  (May be null).
+**   b:       A sorted, null-terminated linked list.  (May be null).
+**   cmp:     A pointer to the comparison function.
+**   offset:  Offset in the structure to the "next" field.
+**
+** Return Value:
+**   A pointer to the head of a sorted list containing the elements
+**   of both a and b.
+**
+** Side effects:
+**   The "next" pointers for elements in the lists a and b are
+**   changed.
+*/
+static char *merge(
+  char *a,
+  char *b,
+  int (*cmp)(const char*,const char*),
+  int offset
+){
+  char *ptr, *head;
+
+  if( a==0 ){
+    head = b;
+  }else if( b==0 ){
+    head = a;
+  }else{
+    if( (*cmp)(a,b)<=0 ){
+      ptr = a;
+      a = NEXT(a);
+    }else{
+      ptr = b;
+      b = NEXT(b);
+    }
+    head = ptr;
+    while( a && b ){
+      if( (*cmp)(a,b)<=0 ){
+        NEXT(ptr) = a;
+        ptr = a;
+        a = NEXT(a);
+      }else{
+        NEXT(ptr) = b;
+        ptr = b;
+        b = NEXT(b);
+      }
+    }
+    if( a ) NEXT(ptr) = a;
+    else    NEXT(ptr) = b;
+  }
+  return head;
+}
+
+/*
+** Inputs:
+**   list:      Pointer to a singly-linked list of structures.
+**   next:      Pointer to pointer to the second element of the list.
+**   cmp:       A comparison function.
+**
+** Return Value:
+**   A pointer to the head of a sorted list containing the elements
+**   originally in list.
+**
+** Side effects:
+**   The "next" pointers for elements in list are changed.
+*/
+#define LISTSIZE 30
+static char *msort(
+  char *list,
+  char **next,
+  int (*cmp)(const char*,const char*)
+){
+  unsigned long offset;
+  char *ep;
+  char *set[LISTSIZE];
+  int i;
+  offset = (unsigned long)((char*)next - (char*)list);
+  for(i=0; i<LISTSIZE; i++) set[i] = 0;
+  while( list ){
+    ep = list;
+    list = NEXT(list);
+    NEXT(ep) = 0;
+    for(i=0; i<LISTSIZE-1 && set[i]!=0; i++){
+      ep = merge(ep,set[i],cmp,offset);
+      set[i] = 0;
+    }
+    set[i] = ep;
+  }
+  ep = 0;
+  for(i=0; i<LISTSIZE; i++) if( set[i] ) ep = merge(set[i],ep,cmp,offset);
+  return ep;
+}
+/************************ From the file "option.c" **************************/
+static char **g_argv;
+static struct s_options *op;
+static FILE *errstream;
+
+#define ISOPT(X) ((X)[0]=='-'||(X)[0]=='+'||strchr((X),'=')!=0)
+
+/*
+** Print the command line with a carrot pointing to the k-th character
+** of the n-th field.
+*/
+static void errline(int n, int k, FILE *err)
+{
+  int spcnt, i;
+  if( g_argv[0] ){
+    fprintf(err,"%s",g_argv[0]);
+    spcnt = lemonStrlen(g_argv[0]) + 1;
+  }else{
+    spcnt = 0;
+  }
+  for(i=1; i<n && g_argv[i]; i++){
+    fprintf(err," %s",g_argv[i]);
+    spcnt += lemonStrlen(g_argv[i])+1;
+  }
+  spcnt += k;
+  for(; g_argv[i]; i++) fprintf(err," %s",g_argv[i]);
+  if( spcnt<20 ){
+    fprintf(err,"\n%*s^-- here\n",spcnt,"");
+  }else{
+    fprintf(err,"\n%*shere --^\n",spcnt-7,"");
+  }
+}
+
+/*
+** Return the index of the N-th non-switch argument.  Return -1
+** if N is out of range.
+*/
+static int argindex(int n)
+{
+  int i;
+  int dashdash = 0;
+  if( g_argv!=0 && *g_argv!=0 ){
+    for(i=1; g_argv[i]; i++){
+      if( dashdash || !ISOPT(g_argv[i]) ){
+        if( n==0 ) return i;
+        n--;
+      }
+      if( strcmp(g_argv[i],"--")==0 ) dashdash = 1;
+    }
+  }
+  return -1;
+}
+
+static char emsg[] = "Command line syntax error: ";
+
+/*
+** Process a flag command line argument.
+*/
+static int handleflags(int i, FILE *err)
+{
+  int v;
+  int errcnt = 0;
+  int j;
+  for(j=0; op[j].label; j++){
+    if( strncmp(&g_argv[i][1],op[j].label,lemonStrlen(op[j].label))==0 ) break;
+  }
+  v = g_argv[i][0]=='-' ? 1 : 0;
+  if( op[j].label==0 ){
+    if( err ){
+      fprintf(err,"%sundefined option.\n",emsg);
+      errline(i,1,err);
+    }
+    errcnt++;
+  }else if( op[j].arg==0 ){
+    /* Ignore this option */
+  }else if( op[j].type==OPT_FLAG ){
+    *((int*)op[j].arg) = v;
+  }else if( op[j].type==OPT_FFLAG ){
+    (*(void(*)(int))(op[j].arg))(v);
+  }else if( op[j].type==OPT_FSTR ){
+    (*(void(*)(char *))(op[j].arg))(&g_argv[i][2]);
+  }else{
+    if( err ){
+      fprintf(err,"%smissing argument on switch.\n",emsg);
+      errline(i,1,err);
+    }
+    errcnt++;
+  }
+  return errcnt;
+}
+
+/*
+** Process a command line switch which has an argument.
+*/
+static int handleswitch(int i, FILE *err)
+{
+  int lv = 0;
+  double dv = 0.0;
+  char *sv = 0, *end;
+  char *cp;
+  int j;
+  int errcnt = 0;
+  cp = strchr(g_argv[i],'=');
+  assert( cp!=0 );
+  *cp = 0;
+  for(j=0; op[j].label; j++){
+    if( strcmp(g_argv[i],op[j].label)==0 ) break;
+  }
+  *cp = '=';
+  if( op[j].label==0 ){
+    if( err ){
+      fprintf(err,"%sundefined option.\n",emsg);
+      errline(i,0,err);
+    }
+    errcnt++;
+  }else{
+    cp++;
+    switch( op[j].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        if( err ){
+          fprintf(err,"%soption requires an argument.\n",emsg);
+          errline(i,0,err);
+        }
+        errcnt++;
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        dv = strtod(cp,&end);
+        if( *end ){
+          if( err ){
+            fprintf(err,
+               "%sillegal character in floating-point argument.\n",emsg);
+            errline(i,(int)((char*)end-(char*)g_argv[i]),err);
+          }
+          errcnt++;
+        }
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        lv = strtol(cp,&end,0);
+        if( *end ){
+          if( err ){
+            fprintf(err,"%sillegal character in integer argument.\n",emsg);
+            errline(i,(int)((char*)end-(char*)g_argv[i]),err);
+          }
+          errcnt++;
+        }
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        sv = cp;
+        break;
+    }
+    switch( op[j].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        break;
+      case OPT_DBL:
+        *(double*)(op[j].arg) = dv;
+        break;
+      case OPT_FDBL:
+        (*(void(*)(double))(op[j].arg))(dv);
+        break;
+      case OPT_INT:
+        *(int*)(op[j].arg) = lv;
+        break;
+      case OPT_FINT:
+        (*(void(*)(int))(op[j].arg))((int)lv);
+        break;
+      case OPT_STR:
+        *(char**)(op[j].arg) = sv;
+        break;
+      case OPT_FSTR:
+        (*(void(*)(char *))(op[j].arg))(sv);
+        break;
+    }
+  }
+  return errcnt;
+}
+
+int OptInit(char **a, struct s_options *o, FILE *err)
+{
+  int errcnt = 0;
+  g_argv = a;
+  op = o;
+  errstream = err;
+  if( g_argv && *g_argv && op ){
+    int i;
+    for(i=1; g_argv[i]; i++){
+      if( g_argv[i][0]=='+' || g_argv[i][0]=='-' ){
+        errcnt += handleflags(i,err);
+      }else if( strchr(g_argv[i],'=') ){
+        errcnt += handleswitch(i,err);
+      }
+    }
+  }
+  if( errcnt>0 ){
+    fprintf(err,"Valid command line options for \"%s\" are:\n",*a);
+    OptPrint();
+    exit(1);
+  }
+  return 0;
+}
+
+int OptNArgs(void){
+  int cnt = 0;
+  int dashdash = 0;
+  int i;
+  if( g_argv!=0 && g_argv[0]!=0 ){
+    for(i=1; g_argv[i]; i++){
+      if( dashdash || !ISOPT(g_argv[i]) ) cnt++;
+      if( strcmp(g_argv[i],"--")==0 ) dashdash = 1;
+    }
+  }
+  return cnt;
+}
+
+char *OptArg(int n)
+{
+  int i;
+  i = argindex(n);
+  return i>=0 ? g_argv[i] : 0;
+}
+
+void OptErr(int n)
+{
+  int i;
+  i = argindex(n);
+  if( i>=0 ) errline(i,0,errstream);
+}
+
+void OptPrint(void){
+  int i;
+  int max, len;
+  max = 0;
+  for(i=0; op[i].label; i++){
+    len = lemonStrlen(op[i].label) + 1;
+    switch( op[i].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        len += 9;       /* length of "<integer>" */
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        len += 6;       /* length of "<real>" */
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        len += 8;       /* length of "<string>" */
+        break;
+    }
+    if( len>max ) max = len;
+  }
+  for(i=0; op[i].label; i++){
+    switch( op[i].type ){
+      case OPT_FLAG:
+      case OPT_FFLAG:
+        fprintf(errstream,"  -%-*s  %s\n",max,op[i].label,op[i].message);
+        break;
+      case OPT_INT:
+      case OPT_FINT:
+        fprintf(errstream,"  -%s<integer>%*s  %s\n",op[i].label,
+          (int)(max-lemonStrlen(op[i].label)-9),"",op[i].message);
+        break;
+      case OPT_DBL:
+      case OPT_FDBL:
+        fprintf(errstream,"  -%s<real>%*s  %s\n",op[i].label,
+          (int)(max-lemonStrlen(op[i].label)-6),"",op[i].message);
+        break;
+      case OPT_STR:
+      case OPT_FSTR:
+        fprintf(errstream,"  -%s<string>%*s  %s\n",op[i].label,
+          (int)(max-lemonStrlen(op[i].label)-8),"",op[i].message);
+        break;
+    }
+  }
+}
+/*********************** From the file "parse.c" ****************************/
+/*
+** Input file parser for the LEMON parser generator.
+*/
+
+/* The state of the parser */
+enum e_state {
+  INITIALIZE,
+  WAITING_FOR_DECL_OR_RULE,
+  WAITING_FOR_DECL_KEYWORD,
+  WAITING_FOR_DECL_ARG,
+  WAITING_FOR_PRECEDENCE_SYMBOL,
+  WAITING_FOR_ARROW,
+  IN_RHS,
+  LHS_ALIAS_1,
+  LHS_ALIAS_2,
+  LHS_ALIAS_3,
+  RHS_ALIAS_1,
+  RHS_ALIAS_2,
+  PRECEDENCE_MARK_1,
+  PRECEDENCE_MARK_2,
+  RESYNC_AFTER_RULE_ERROR,
+  RESYNC_AFTER_DECL_ERROR,
+  WAITING_FOR_DESTRUCTOR_SYMBOL,
+  WAITING_FOR_DATATYPE_SYMBOL,
+  WAITING_FOR_FALLBACK_ID,
+  WAITING_FOR_WILDCARD_ID,
+  WAITING_FOR_CLASS_ID,
+  WAITING_FOR_CLASS_TOKEN,
+  WAITING_FOR_TOKEN_NAME
+};
+struct pstate {
+  char *filename;       /* Name of the input file */
+  int tokenlineno;      /* Linenumber at which current token starts */
+  int errorcnt;         /* Number of errors so far */
+  char *tokenstart;     /* Text of current token */
+  struct lemon *gp;     /* Global state vector */
+  enum e_state state;        /* The state of the parser */
+  struct symbol *fallback;   /* The fallback token */
+  struct symbol *tkclass;    /* Token class symbol */
+  struct symbol *lhs;        /* Left-hand side of current rule */
+  const char *lhsalias;      /* Alias for the LHS */
+  int nrhs;                  /* Number of right-hand side symbols seen */
+  struct symbol *rhs[MAXRHS];  /* RHS symbols */
+  const char *alias[MAXRHS]; /* Aliases for each RHS symbol (or NULL) */
+  struct rule *prevrule;     /* Previous rule parsed */
+  const char *declkeyword;   /* Keyword of a declaration */
+  char **declargslot;        /* Where the declaration argument should be put */
+  int insertLineMacro;       /* Add #line before declaration insert */
+  int *decllinenoslot;       /* Where to write declaration line number */
+  enum e_assoc declassoc;    /* Assign this association to decl arguments */
+  int preccounter;           /* Assign this precedence to decl arguments */
+  struct rule *firstrule;    /* Pointer to first rule in the grammar */
+  struct rule *lastrule;     /* Pointer to the most recently parsed rule */
+};
+
+/* Parse a single token */
+static void parseonetoken(struct pstate *psp)
+{
+  const char *x;
+  x = Strsafe(psp->tokenstart);     /* Save the token permanently */
+#if 0
+  printf("%s:%d: Token=[%s] state=%d\n",psp->filename,psp->tokenlineno,
+    x,psp->state);
+#endif
+  switch( psp->state ){
+    case INITIALIZE:
+      psp->prevrule = 0;
+      psp->preccounter = 0;
+      psp->firstrule = psp->lastrule = 0;
+      psp->gp->nrule = 0;
+      /* fall through */
+    case WAITING_FOR_DECL_OR_RULE:
+      if( x[0]=='%' ){
+        psp->state = WAITING_FOR_DECL_KEYWORD;
+      }else if( ISLOWER(x[0]) ){
+        psp->lhs = Symbol_new(x);
+        psp->nrhs = 0;
+        psp->lhsalias = 0;
+        psp->state = WAITING_FOR_ARROW;
+      }else if( x[0]=='{' ){
+        if( psp->prevrule==0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "There is no prior rule upon which to attach the code "
+            "fragment which begins on this line.");
+          psp->errorcnt++;
+        }else if( psp->prevrule->code!=0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Code fragment beginning on this line is not the first "
+            "to follow the previous rule.");
+          psp->errorcnt++;
+        }else if( strcmp(x, "{NEVER-REDUCE")==0 ){
+          psp->prevrule->neverReduce = 1;
+        }else{
+          psp->prevrule->line = psp->tokenlineno;
+          psp->prevrule->code = &x[1];
+          psp->prevrule->noCode = 0;
+        }
+      }else if( x[0]=='[' ){
+        psp->state = PRECEDENCE_MARK_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Token \"%s\" should be either \"%%\" or a nonterminal name.",
+          x);
+        psp->errorcnt++;
+      }
+      break;
+    case PRECEDENCE_MARK_1:
+      if( !ISUPPER(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "The precedence symbol must be a terminal.");
+        psp->errorcnt++;
+      }else if( psp->prevrule==0 ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "There is no prior rule to assign precedence \"[%s]\".",x);
+        psp->errorcnt++;
+      }else if( psp->prevrule->precsym!=0 ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Precedence mark on this line is not the first "
+          "to follow the previous rule.");
+        psp->errorcnt++;
+      }else{
+        psp->prevrule->precsym = Symbol_new(x);
+      }
+      psp->state = PRECEDENCE_MARK_2;
+      break;
+    case PRECEDENCE_MARK_2:
+      if( x[0]!=']' ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \"]\" on precedence mark.");
+        psp->errorcnt++;
+      }
+      psp->state = WAITING_FOR_DECL_OR_RULE;
+      break;
+    case WAITING_FOR_ARROW:
+      if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+        psp->state = IN_RHS;
+      }else if( x[0]=='(' ){
+        psp->state = LHS_ALIAS_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Expected to see a \":\" following the LHS symbol \"%s\".",
+          psp->lhs->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_1:
+      if( ISALPHA(x[0]) ){
+        psp->lhsalias = x;
+        psp->state = LHS_ALIAS_2;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "\"%s\" is not a valid alias for the LHS \"%s\"\n",
+          x,psp->lhs->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_2:
+      if( x[0]==')' ){
+        psp->state = LHS_ALIAS_3;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case LHS_ALIAS_3:
+      if( x[0]==':' && x[1]==':' && x[2]=='=' ){
+        psp->state = IN_RHS;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \"->\" following: \"%s(%s)\".",
+           psp->lhs->name,psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case IN_RHS:
+      if( x[0]=='.' ){
+        struct rule *rp;
+        rp = (struct rule *)calloc( sizeof(struct rule) +
+             sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1);
+        if( rp==0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Can't allocate enough memory for this rule.");
+          psp->errorcnt++;
+          psp->prevrule = 0;
+        }else{
+          int i;
+          rp->ruleline = psp->tokenlineno;
+          rp->rhs = (struct symbol**)&rp[1];
+          rp->rhsalias = (const char**)&(rp->rhs[psp->nrhs]);
+          for(i=0; i<psp->nrhs; i++){
+            rp->rhs[i] = psp->rhs[i];
+            rp->rhsalias[i] = psp->alias[i];
+            if( rp->rhsalias[i]!=0 ){ rp->rhs[i]->bContent = 1; }
+          }
+          rp->lhs = psp->lhs;
+          rp->lhsalias = psp->lhsalias;
+          rp->nrhs = psp->nrhs;
+          rp->code = 0;
+          rp->noCode = 1;
+          rp->precsym = 0;
+          rp->index = psp->gp->nrule++;
+          rp->nextlhs = rp->lhs->rule;
+          rp->lhs->rule = rp;
+          rp->next = 0;
+          if( psp->firstrule==0 ){
+            psp->firstrule = psp->lastrule = rp;
+          }else{
+            psp->lastrule->next = rp;
+            psp->lastrule = rp;
+          }
+          psp->prevrule = rp;
+        }
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( ISALPHA(x[0]) ){
+        if( psp->nrhs>=MAXRHS ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Too many symbols on RHS of rule beginning at \"%s\".",
+            x);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_RULE_ERROR;
+        }else{
+          psp->rhs[psp->nrhs] = Symbol_new(x);
+          psp->alias[psp->nrhs] = 0;
+          psp->nrhs++;
+        }
+      }else if( (x[0]=='|' || x[0]=='/') && psp->nrhs>0 && ISUPPER(x[1]) ){
+        struct symbol *msp = psp->rhs[psp->nrhs-1];
+        if( msp->type!=MULTITERMINAL ){
+          struct symbol *origsp = msp;
+          msp = (struct symbol *) calloc(1,sizeof(*msp));
+          memset(msp, 0, sizeof(*msp));
+          msp->type = MULTITERMINAL;
+          msp->nsubsym = 1;
+          msp->subsym = (struct symbol **) calloc(1,sizeof(struct symbol*));
+          msp->subsym[0] = origsp;
+          msp->name = origsp->name;
+          psp->rhs[psp->nrhs-1] = msp;
+        }
+        msp->nsubsym++;
+        msp->subsym = (struct symbol **) realloc(msp->subsym,
+          sizeof(struct symbol*)*msp->nsubsym);
+        msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]);
+        if( ISLOWER(x[1]) || ISLOWER(msp->subsym[0]->name[0]) ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Cannot form a compound containing a non-terminal");
+          psp->errorcnt++;
+        }
+      }else if( x[0]=='(' && psp->nrhs>0 ){
+        psp->state = RHS_ALIAS_1;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal character on RHS of rule: \"%s\".",x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case RHS_ALIAS_1:
+      if( ISALPHA(x[0]) ){
+        psp->alias[psp->nrhs-1] = x;
+        psp->state = RHS_ALIAS_2;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
+          x,psp->rhs[psp->nrhs-1]->name);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case RHS_ALIAS_2:
+      if( x[0]==')' ){
+        psp->state = IN_RHS;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_RULE_ERROR;
+      }
+      break;
+    case WAITING_FOR_DECL_KEYWORD:
+      if( ISALPHA(x[0]) ){
+        psp->declkeyword = x;
+        psp->declargslot = 0;
+        psp->decllinenoslot = 0;
+        psp->insertLineMacro = 1;
+        psp->state = WAITING_FOR_DECL_ARG;
+        if( strcmp(x,"name")==0 ){
+          psp->declargslot = &(psp->gp->name);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"include")==0 ){
+          psp->declargslot = &(psp->gp->include);
+        }else if( strcmp(x,"code")==0 ){
+          psp->declargslot = &(psp->gp->extracode);
+        }else if( strcmp(x,"token_destructor")==0 ){
+          psp->declargslot = &psp->gp->tokendest;
+        }else if( strcmp(x,"default_destructor")==0 ){
+          psp->declargslot = &psp->gp->vardest;
+        }else if( strcmp(x,"token_prefix")==0 ){
+          psp->declargslot = &psp->gp->tokenprefix;
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"syntax_error")==0 ){
+          psp->declargslot = &(psp->gp->error);
+        }else if( strcmp(x,"parse_accept")==0 ){
+          psp->declargslot = &(psp->gp->accept);
+        }else if( strcmp(x,"parse_failure")==0 ){
+          psp->declargslot = &(psp->gp->failure);
+        }else if( strcmp(x,"stack_overflow")==0 ){
+          psp->declargslot = &(psp->gp->overflow);
+        }else if( strcmp(x,"extra_argument")==0 ){
+          psp->declargslot = &(psp->gp->arg);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"extra_context")==0 ){
+          psp->declargslot = &(psp->gp->ctx);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"token_type")==0 ){
+          psp->declargslot = &(psp->gp->tokentype);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"default_type")==0 ){
+          psp->declargslot = &(psp->gp->vartype);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"stack_size")==0 ){
+          psp->declargslot = &(psp->gp->stacksize);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"start_symbol")==0 ){
+          psp->declargslot = &(psp->gp->start);
+          psp->insertLineMacro = 0;
+        }else if( strcmp(x,"left")==0 ){
+          psp->preccounter++;
+          psp->declassoc = LEFT;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+        }else if( strcmp(x,"right")==0 ){
+          psp->preccounter++;
+          psp->declassoc = RIGHT;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+        }else if( strcmp(x,"nonassoc")==0 ){
+          psp->preccounter++;
+          psp->declassoc = NONE;
+          psp->state = WAITING_FOR_PRECEDENCE_SYMBOL;
+        }else if( strcmp(x,"destructor")==0 ){
+          psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL;
+        }else if( strcmp(x,"type")==0 ){
+          psp->state = WAITING_FOR_DATATYPE_SYMBOL;
+        }else if( strcmp(x,"fallback")==0 ){
+          psp->fallback = 0;
+          psp->state = WAITING_FOR_FALLBACK_ID;
+        }else if( strcmp(x,"token")==0 ){
+          psp->state = WAITING_FOR_TOKEN_NAME;
+        }else if( strcmp(x,"wildcard")==0 ){
+          psp->state = WAITING_FOR_WILDCARD_ID;
+        }else if( strcmp(x,"token_class")==0 ){
+          psp->state = WAITING_FOR_CLASS_ID;
+        }else{
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Unknown declaration keyword: \"%%%s\".",x);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_DECL_ERROR;
+        }
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal declaration keyword: \"%s\".",x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }
+      break;
+    case WAITING_FOR_DESTRUCTOR_SYMBOL:
+      if( !ISALPHA(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Symbol name missing after %%destructor keyword");
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        psp->declargslot = &sp->destructor;
+        psp->decllinenoslot = &sp->destLineno;
+        psp->insertLineMacro = 1;
+        psp->state = WAITING_FOR_DECL_ARG;
+      }
+      break;
+    case WAITING_FOR_DATATYPE_SYMBOL:
+      if( !ISALPHA(x[0]) ){
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Symbol name missing after %%type keyword");
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }else{
+        struct symbol *sp = Symbol_find(x);
+        if((sp) && (sp->datatype)){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Symbol %%type \"%s\" already defined", x);
+          psp->errorcnt++;
+          psp->state = RESYNC_AFTER_DECL_ERROR;
+        }else{
+          if (!sp){
+            sp = Symbol_new(x);
+          }
+          psp->declargslot = &sp->datatype;
+          psp->insertLineMacro = 0;
+          psp->state = WAITING_FOR_DECL_ARG;
+        }
+      }
+      break;
+    case WAITING_FOR_PRECEDENCE_SYMBOL:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( ISUPPER(x[0]) ){
+        struct symbol *sp;
+        sp = Symbol_new(x);
+        if( sp->prec>=0 ){
+          ErrorMsg(psp->filename,psp->tokenlineno,
+            "Symbol \"%s\" has already be given a precedence.",x);
+          psp->errorcnt++;
+        }else{
+          sp->prec = psp->preccounter;
+          sp->assoc = psp->declassoc;
+        }
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Can't assign a precedence to \"%s\".",x);
+        psp->errorcnt++;
+      }
+      break;
+    case WAITING_FOR_DECL_ARG:
+      if( x[0]=='{' || x[0]=='\"' || ISALNUM(x[0]) ){
+        const char *zOld, *zNew;
+        char *zBuf, *z;
+        int nOld, n, nLine = 0, nNew, nBack;
+        int addLineMacro;
+        char zLine[50];
+        zNew = x;
+        if( zNew[0]=='"' || zNew[0]=='{' ) zNew++;
+        nNew = lemonStrlen(zNew);
+        if( *psp->declargslot ){
+          zOld = *psp->declargslot;
+        }else{
+          zOld = "";
+        }
+        nOld = lemonStrlen(zOld);
+        n = nOld + nNew + 20;
+        addLineMacro = !psp->gp->nolinenosflag
+                       && psp->insertLineMacro
+                       && psp->tokenlineno>1
+                       && (psp->decllinenoslot==0 || psp->decllinenoslot[0]!=0);
+        if( addLineMacro ){
+          for(z=psp->filename, nBack=0; *z; z++){
+            if( *z=='\\' ) nBack++;
+          }
+          lemon_sprintf(zLine, "#line %d ", psp->tokenlineno);
+          nLine = lemonStrlen(zLine);
+          n += nLine + lemonStrlen(psp->filename) + nBack;
+        }
+        *psp->declargslot = (char *) realloc(*psp->declargslot, n);
+        zBuf = *psp->declargslot + nOld;
+        if( addLineMacro ){
+          if( nOld && zBuf[-1]!='\n' ){
+            *(zBuf++) = '\n';
+          }
+          memcpy(zBuf, zLine, nLine);
+          zBuf += nLine;
+          *(zBuf++) = '"';
+          for(z=psp->filename; *z; z++){
+            if( *z=='\\' ){
+              *(zBuf++) = '\\';
+            }
+            *(zBuf++) = *z;
+          }
+          *(zBuf++) = '"';
+          *(zBuf++) = '\n';
+        }
+        if( psp->decllinenoslot && psp->decllinenoslot[0]==0 ){
+          psp->decllinenoslot[0] = psp->tokenlineno;
+        }
+        memcpy(zBuf, zNew, nNew);
+        zBuf += nNew;
+        *zBuf = 0;
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else{
+        ErrorMsg(psp->filename,psp->tokenlineno,
+          "Illegal argument to %%%s: %s",psp->declkeyword,x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }
+      break;
+    case WAITING_FOR_FALLBACK_ID:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( !ISUPPER(x[0]) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%fallback argument \"%s\" should be a token", x);
+        psp->errorcnt++;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        if( psp->fallback==0 ){
+          psp->fallback = sp;
+        }else if( sp->fallback ){
+          ErrorMsg(psp->filename, psp->tokenlineno,
+            "More than one fallback assigned to token %s", x);
+          psp->errorcnt++;
+        }else{
+          sp->fallback = psp->fallback;
+          psp->gp->has_fallback = 1;
+        }
+      }
+      break;
+    case WAITING_FOR_TOKEN_NAME:
+      /* Tokens do not have to be declared before use.  But they can be
+      ** in order to control their assigned integer number.  The number for
+      ** each token is assigned when it is first seen.  So by including
+      **
+      **     %token ONE TWO THREE.
+      **
+      ** early in the grammar file, that assigns small consecutive values
+      ** to each of the tokens ONE TWO and THREE.
+      */
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( !ISUPPER(x[0]) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%token argument \"%s\" should be a token", x);
+        psp->errorcnt++;
+      }else{
+        (void)Symbol_new(x);
+      }
+      break;
+    case WAITING_FOR_WILDCARD_ID:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( !ISUPPER(x[0]) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%wildcard argument \"%s\" should be a token", x);
+        psp->errorcnt++;
+      }else{
+        struct symbol *sp = Symbol_new(x);
+        if( psp->gp->wildcard==0 ){
+          psp->gp->wildcard = sp;
+        }else{
+          ErrorMsg(psp->filename, psp->tokenlineno,
+            "Extra wildcard to token: %s", x);
+          psp->errorcnt++;
+        }
+      }
+      break;
+    case WAITING_FOR_CLASS_ID:
+      if( !ISLOWER(x[0]) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%token_class must be followed by an identifier: %s", x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+     }else if( Symbol_find(x) ){
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "Symbol \"%s\" already used", x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }else{
+        psp->tkclass = Symbol_new(x);
+        psp->tkclass->type = MULTITERMINAL;
+        psp->state = WAITING_FOR_CLASS_TOKEN;
+      }
+      break;
+    case WAITING_FOR_CLASS_TOKEN:
+      if( x[0]=='.' ){
+        psp->state = WAITING_FOR_DECL_OR_RULE;
+      }else if( ISUPPER(x[0]) || ((x[0]=='|' || x[0]=='/') && ISUPPER(x[1])) ){
+        struct symbol *msp = psp->tkclass;
+        msp->nsubsym++;
+        msp->subsym = (struct symbol **) realloc(msp->subsym,
+          sizeof(struct symbol*)*msp->nsubsym);
+        if( !ISUPPER(x[0]) ) x++;
+        msp->subsym[msp->nsubsym-1] = Symbol_new(x);
+      }else{
+        ErrorMsg(psp->filename, psp->tokenlineno,
+          "%%token_class argument \"%s\" should be a token", x);
+        psp->errorcnt++;
+        psp->state = RESYNC_AFTER_DECL_ERROR;
+      }
+      break;
+    case RESYNC_AFTER_RULE_ERROR:
+/*      if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+**      break; */
+    case RESYNC_AFTER_DECL_ERROR:
+      if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE;
+      if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD;
+      break;
+  }
+}
+
+/* The text in the input is part of the argument to an %ifdef or %ifndef.
+** Evaluate the text as a boolean expression.  Return true or false.
+*/
+static int eval_preprocessor_boolean(char *z, int lineno){
+  int neg = 0;
+  int res = 0;
+  int okTerm = 1;
+  int i;
+  for(i=0; z[i]!=0; i++){
+    if( ISSPACE(z[i]) ) continue;
+    if( z[i]=='!' ){
+      if( !okTerm ) goto pp_syntax_error;
+      neg = !neg;
+      continue;
+    }
+    if( z[i]=='|' && z[i+1]=='|' ){
+      if( okTerm ) goto pp_syntax_error;
+      if( res ) return 1;
+      i++;
+      okTerm = 1;
+      continue;
+    }
+    if( z[i]=='&' && z[i+1]=='&' ){
+      if( okTerm ) goto pp_syntax_error;
+      if( !res ) return 0;
+      i++;
+      okTerm = 1;
+      continue;
+    }
+    if( z[i]=='(' ){
+      int k;
+      int n = 1;
+      if( !okTerm ) goto pp_syntax_error;
+      for(k=i+1; z[k]; k++){
+        if( z[k]==')' ){
+          n--;
+          if( n==0 ){
+            z[k] = 0;
+            res = eval_preprocessor_boolean(&z[i+1], -1);
+            z[k] = ')';
+            if( res<0 ){
+              i = i-res;
+              goto pp_syntax_error;
+            }
+            i = k;
+            break;
+          }
+        }else if( z[k]=='(' ){
+          n++;
+        }else if( z[k]==0 ){
+          i = k;
+          goto pp_syntax_error;
+        }
+      }
+      if( neg ){
+        res = !res;
+        neg = 0;
+      }
+      okTerm = 0;
+      continue;
+    }
+    if( ISALPHA(z[i]) ){
+      int j, k, n;
+      if( !okTerm ) goto pp_syntax_error;
+      for(k=i+1; ISALNUM(z[k]) || z[k]=='_'; k++){}
+      n = k - i;
+      res = 0;
+      for(j=0; j<nDefine; j++){
+        if( strncmp(azDefine[j],&z[i],n)==0 && azDefine[j][n]==0 ){
+          res = 1;
+          break;
+        }
+      }
+      i = k-1;
+      if( neg ){
+        res = !res;
+        neg = 0;
+      }
+      okTerm = 0;
+      continue;
+    }
+    goto pp_syntax_error;
+  }
+  return res;
+
+pp_syntax_error:
+  if( lineno>0 ){
+    fprintf(stderr, "%%if syntax error on line %d.\n", lineno);
+    fprintf(stderr, "  %.*s <-- syntax error here\n", i+1, z);
+    exit(1);
+  }else{
+    return -(i+1);
+  }
+}
+
+/* Run the preprocessor over the input file text.  The global variables
+** azDefine[0] through azDefine[nDefine-1] contains the names of all defined
+** macros.  This routine looks for "%ifdef" and "%ifndef" and "%endif" and
+** comments them out.  Text in between is also commented out as appropriate.
+*/
+static void preprocess_input(char *z){
+  int i, j, k;
+  int exclude = 0;
+  int start = 0;
+  int lineno = 1;
+  int start_lineno = 1;
+  for(i=0; z[i]; i++){
+    if( z[i]=='\n' ) lineno++;
+    if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue;
+    if( strncmp(&z[i],"%endif",6)==0 && ISSPACE(z[i+6]) ){
+      if( exclude ){
+        exclude--;
+        if( exclude==0 ){
+          for(j=start; j<i; j++) if( z[j]!='\n' ) z[j] = ' ';
+        }
+      }
+      for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+    }else if( strncmp(&z[i],"%else",5)==0 && ISSPACE(z[i+5]) ){
+      if( exclude==1){
+        exclude = 0;
+        for(j=start; j<i; j++) if( z[j]!='\n' ) z[j] = ' ';
+      }else if( exclude==0 ){
+        exclude = 1;
+        start = i;
+        start_lineno = lineno;
+      }
+      for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+    }else if( strncmp(&z[i],"%ifdef ",7)==0 
+          || strncmp(&z[i],"%if ",4)==0
+          || strncmp(&z[i],"%ifndef ",8)==0 ){
+      if( exclude ){
+        exclude++;
+      }else{
+        int isNot;
+        int iBool;
+        for(j=i; z[j] && !ISSPACE(z[j]); j++){}
+        iBool = j;
+        isNot = (j==i+7);
+        while( z[j] && z[j]!='\n' ){ j++; }
+        k = z[j];
+        z[j] = 0;
+        exclude = eval_preprocessor_boolean(&z[iBool], lineno);
+        z[j] = k;
+        if( !isNot ) exclude = !exclude;
+        if( exclude ){
+          start = i;
+          start_lineno = lineno;
+        }
+      }
+      for(j=i; z[j] && z[j]!='\n'; j++) z[j] = ' ';
+    }
+  }
+  if( exclude ){
+    fprintf(stderr,"unterminated %%ifdef starting on line %d\n", start_lineno);
+    exit(1);
+  }
+}
+
+/* In spite of its name, this function is really a scanner.  It read
+** in the entire input file (all at once) then tokenizes it.  Each
+** token is passed to the function "parseonetoken" which builds all
+** the appropriate data structures in the global state vector "gp".
+*/
+void Parse(struct lemon *gp)
+{
+  struct pstate ps;
+  FILE *fp;
+  char *filebuf;
+  unsigned int filesize;
+  int lineno;
+  int c;
+  char *cp, *nextcp;
+  int startline = 0;
+
+  memset(&ps, '\0', sizeof(ps));
+  ps.gp = gp;
+  ps.filename = gp->filename;
+  ps.errorcnt = 0;
+  ps.state = INITIALIZE;
+
+  /* Begin by reading the input file */
+  fp = fopen(ps.filename,"rb");
+  if( fp==0 ){
+    ErrorMsg(ps.filename,0,"Can't open this file for reading.");
+    gp->errorcnt++;
+    return;
+  }
+  fseek(fp,0,2);
+  filesize = ftell(fp);
+  rewind(fp);
+  filebuf = (char *)malloc( filesize+1 );
+  if( filesize>100000000 || filebuf==0 ){
+    ErrorMsg(ps.filename,0,"Input file too large.");
+    free(filebuf);
+    gp->errorcnt++;
+    fclose(fp);
+    return;
+  }
+  if( fread(filebuf,1,filesize,fp)!=filesize ){
+    ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.",
+      filesize);
+    free(filebuf);
+    gp->errorcnt++;
+    fclose(fp);
+    return;
+  }
+  fclose(fp);
+  filebuf[filesize] = 0;
+
+  /* Make an initial pass through the file to handle %ifdef and %ifndef */
+  preprocess_input(filebuf);
+  if( gp->printPreprocessed ){
+    printf("%s\n", filebuf);
+    return;
+  }
+
+  /* Now scan the text of the input file */
+  lineno = 1;
+  for(cp=filebuf; (c= *cp)!=0; ){
+    if( c=='\n' ) lineno++;              /* Keep track of the line number */
+    if( ISSPACE(c) ){ cp++; continue; }  /* Skip all white space */
+    if( c=='/' && cp[1]=='/' ){          /* Skip C++ style comments */
+      cp+=2;
+      while( (c= *cp)!=0 && c!='\n' ) cp++;
+      continue;
+    }
+    if( c=='/' && cp[1]=='*' ){          /* Skip C style comments */
+      cp+=2;
+      if( (*cp)=='/' ) cp++;
+      while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){
+        if( c=='\n' ) lineno++;
+        cp++;
+      }
+      if( c ) cp++;
+      continue;
+    }
+    ps.tokenstart = cp;                /* Mark the beginning of the token */
+    ps.tokenlineno = lineno;           /* Linenumber on which token begins */
+    if( c=='\"' ){                     /* String literals */
+      cp++;
+      while( (c= *cp)!=0 && c!='\"' ){
+        if( c=='\n' ) lineno++;
+        cp++;
+      }
+      if( c==0 ){
+        ErrorMsg(ps.filename,startline,
+            "String starting on this line is not terminated before "
+            "the end of the file.");
+        ps.errorcnt++;
+        nextcp = cp;
+      }else{
+        nextcp = cp+1;
+      }
+    }else if( c=='{' ){               /* A block of C code */
+      int level;
+      cp++;
+      for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){
+        if( c=='\n' ) lineno++;
+        else if( c=='{' ) level++;
+        else if( c=='}' ) level--;
+        else if( c=='/' && cp[1]=='*' ){  /* Skip comments */
+          int prevc;
+          cp = &cp[2];
+          prevc = 0;
+          while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){
+            if( c=='\n' ) lineno++;
+            prevc = c;
+            cp++;
+          }
+        }else if( c=='/' && cp[1]=='/' ){  /* Skip C++ style comments too */
+          cp = &cp[2];
+          while( (c= *cp)!=0 && c!='\n' ) cp++;
+          if( c ) lineno++;
+        }else if( c=='\'' || c=='\"' ){    /* String a character literals */
+          int startchar, prevc;
+          startchar = c;
+          prevc = 0;
+          for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){
+            if( c=='\n' ) lineno++;
+            if( prevc=='\\' ) prevc = 0;
+            else              prevc = c;
+          }
+        }
+      }
+      if( c==0 ){
+        ErrorMsg(ps.filename,ps.tokenlineno,
+          "C code starting on this line is not terminated before "
+          "the end of the file.");
+        ps.errorcnt++;
+        nextcp = cp;
+      }else{
+        nextcp = cp+1;
+      }
+    }else if( ISALNUM(c) ){          /* Identifiers */
+      while( (c= *cp)!=0 && (ISALNUM(c) || c=='_') ) cp++;
+      nextcp = cp;
+    }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */
+      cp += 3;
+      nextcp = cp;
+    }else if( (c=='/' || c=='|') && ISALPHA(cp[1]) ){
+      cp += 2;
+      while( (c = *cp)!=0 && (ISALNUM(c) || c=='_') ) cp++;
+      nextcp = cp;
+    }else{                          /* All other (one character) operators */
+      cp++;
+      nextcp = cp;
+    }
+    c = *cp;
+    *cp = 0;                        /* Null terminate the token */
+    parseonetoken(&ps);             /* Parse the token */
+    *cp = (char)c;                  /* Restore the buffer */
+    cp = nextcp;
+  }
+  free(filebuf);                    /* Release the buffer after parsing */
+  gp->rule = ps.firstrule;
+  gp->errorcnt = ps.errorcnt;
+}
+/*************************** From the file "plink.c" *********************/
+/*
+** Routines processing configuration follow-set propagation links
+** in the LEMON parser generator.
+*/
+static struct plink *plink_freelist = 0;
+
+/* Allocate a new plink */
+struct plink *Plink_new(void){
+  struct plink *newlink;
+
+  if( plink_freelist==0 ){
+    int i;
+    int amt = 100;
+    plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) );
+    if( plink_freelist==0 ){
+      fprintf(stderr,
+      "Unable to allocate memory for a new follow-set propagation link.\n");
+      exit(1);
+    }
+    for(i=0; i<amt-1; i++) plink_freelist[i].next = &plink_freelist[i+1];
+    plink_freelist[amt-1].next = 0;
+  }
+  newlink = plink_freelist;
+  plink_freelist = plink_freelist->next;
+  return newlink;
+}
+
+/* Add a plink to a plink list */
+void Plink_add(struct plink **plpp, struct config *cfp)
+{
+  struct plink *newlink;
+  newlink = Plink_new();
+  newlink->next = *plpp;
+  *plpp = newlink;
+  newlink->cfp = cfp;
+}
+
+/* Transfer every plink on the list "from" to the list "to" */
+void Plink_copy(struct plink **to, struct plink *from)
+{
+  struct plink *nextpl;
+  while( from ){
+    nextpl = from->next;
+    from->next = *to;
+    *to = from;
+    from = nextpl;
+  }
+}
+
+/* Delete every plink on the list */
+void Plink_delete(struct plink *plp)
+{
+  struct plink *nextpl;
+
+  while( plp ){
+    nextpl = plp->next;
+    plp->next = plink_freelist;
+    plink_freelist = plp;
+    plp = nextpl;
+  }
+}
+/*********************** From the file "report.c" **************************/
+/*
+** Procedures for generating reports and tables in the LEMON parser generator.
+*/
+
+/* Generate a filename with the given suffix.  Space to hold the
+** name comes from malloc() and must be freed by the calling
+** function.
+*/
+PRIVATE char *file_makename(struct lemon *lemp, const char *suffix)
+{
+  char *name;
+  char *cp;
+  char *filename = lemp->filename;
+  int sz;
+
+  if( outputDir ){
+    cp = strrchr(filename, '/');
+    if( cp ) filename = cp + 1;
+  }
+  sz = lemonStrlen(filename);
+  sz += lemonStrlen(suffix);
+  if( outputDir ) sz += lemonStrlen(outputDir) + 1;
+  sz += 5;
+  name = (char*)malloc( sz );
+  if( name==0 ){
+    fprintf(stderr,"Can't allocate space for a filename.\n");
+    exit(1);
+  }
+  name[0] = 0;
+  if( outputDir ){
+    lemon_strcpy(name, outputDir);
+    lemon_strcat(name, "/");
+  }
+  lemon_strcat(name,filename);
+  cp = strrchr(name,'.');
+  if( cp ) *cp = 0;
+  lemon_strcat(name,suffix);
+  return name;
+}
+
+/* Open a file with a name based on the name of the input file,
+** but with a different (specified) suffix, and return a pointer
+** to the stream */
+PRIVATE FILE *file_open(
+  struct lemon *lemp,
+  const char *suffix,
+  const char *mode
+){
+  FILE *fp;
+
+  if( lemp->outname ) free(lemp->outname);
+  lemp->outname = file_makename(lemp, suffix);
+  fp = fopen(lemp->outname,mode);
+  if( fp==0 && *mode=='w' ){
+    fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname);
+    lemp->errorcnt++;
+    return 0;
+  }
+  return fp;
+}
+
+/* Print the text of a rule
+*/
+void rule_print(FILE *out, struct rule *rp){
+  int i, j;
+  fprintf(out, "%s",rp->lhs->name);
+  /*    if( rp->lhsalias ) fprintf(out,"(%s)",rp->lhsalias); */
+  fprintf(out," ::=");
+  for(i=0; i<rp->nrhs; i++){
+    struct symbol *sp = rp->rhs[i];
+    if( sp->type==MULTITERMINAL ){
+      fprintf(out," %s", sp->subsym[0]->name);
+      for(j=1; j<sp->nsubsym; j++){
+        fprintf(out,"|%s", sp->subsym[j]->name);
+      }
+    }else{
+      fprintf(out," %s", sp->name);
+    }
+    /* if( rp->rhsalias[i] ) fprintf(out,"(%s)",rp->rhsalias[i]); */
+  }
+}
+
+/* Duplicate the input file without comments and without actions
+** on rules */
+void Reprint(struct lemon *lemp)
+{
+  struct rule *rp;
+  struct symbol *sp;
+  int i, j, maxlen, len, ncolumns, skip;
+  printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename);
+  maxlen = 10;
+  for(i=0; i<lemp->nsymbol; i++){
+    sp = lemp->symbols[i];
+    len = lemonStrlen(sp->name);
+    if( len>maxlen ) maxlen = len;
+  }
+  ncolumns = 76/(maxlen+5);
+  if( ncolumns<1 ) ncolumns = 1;
+  skip = (lemp->nsymbol + ncolumns - 1)/ncolumns;
+  for(i=0; i<skip; i++){
+    printf("//");
+    for(j=i; j<lemp->nsymbol; j+=skip){
+      sp = lemp->symbols[j];
+      assert( sp->index==j );
+      printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name);
+    }
+    printf("\n");
+  }
+  for(rp=lemp->rule; rp; rp=rp->next){
+    rule_print(stdout, rp);
+    printf(".");
+    if( rp->precsym ) printf(" [%s]",rp->precsym->name);
+    /* if( rp->code ) printf("\n    %s",rp->code); */
+    printf("\n");
+  }
+}
+
+/* Print a single rule.
+*/
+void RulePrint(FILE *fp, struct rule *rp, int iCursor){
+  struct symbol *sp;
+  int i, j;
+  fprintf(fp,"%s ::=",rp->lhs->name);
+  for(i=0; i<=rp->nrhs; i++){
+    if( i==iCursor ) fprintf(fp," *");
+    if( i==rp->nrhs ) break;
+    sp = rp->rhs[i];
+    if( sp->type==MULTITERMINAL ){
+      fprintf(fp," %s", sp->subsym[0]->name);
+      for(j=1; j<sp->nsubsym; j++){
+        fprintf(fp,"|%s",sp->subsym[j]->name);
+      }
+    }else{
+      fprintf(fp," %s", sp->name);
+    }
+  }
+}
+
+/* Print the rule for a configuration.
+*/
+void ConfigPrint(FILE *fp, struct config *cfp){
+  RulePrint(fp, cfp->rp, cfp->dot);
+}
+
+/* #define TEST */
+#if 0
+/* Print a set */
+PRIVATE void SetPrint(out,set,lemp)
+FILE *out;
+char *set;
+struct lemon *lemp;
+{
+  int i;
+  char *spacer;
+  spacer = "";
+  fprintf(out,"%12s[","");
+  for(i=0; i<lemp->nterminal; i++){
+    if( SetFind(set,i) ){
+      fprintf(out,"%s%s",spacer,lemp->symbols[i]->name);
+      spacer = " ";
+    }
+  }
+  fprintf(out,"]\n");
+}
+
+/* Print a plink chain */
+PRIVATE void PlinkPrint(out,plp,tag)
+FILE *out;
+struct plink *plp;
+char *tag;
+{
+  while( plp ){
+    fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->statenum);
+    ConfigPrint(out,plp->cfp);
+    fprintf(out,"\n");
+    plp = plp->next;
+  }
+}
+#endif
+
+/* Print an action to the given file descriptor.  Return FALSE if
+** nothing was actually printed.
+*/
+int PrintAction(
+  struct action *ap,          /* The action to print */
+  FILE *fp,                   /* Print the action here */
+  int indent                  /* Indent by this amount */
+){
+  int result = 1;
+  switch( ap->type ){
+    case SHIFT: {
+      struct state *stp = ap->x.stp;
+      fprintf(fp,"%*s shift        %-7d",indent,ap->sp->name,stp->statenum);
+      break;
+    }
+    case REDUCE: {
+      struct rule *rp = ap->x.rp;
+      fprintf(fp,"%*s reduce       %-7d",indent,ap->sp->name,rp->iRule);
+      RulePrint(fp, rp, -1);
+      break;
+    }
+    case SHIFTREDUCE: {
+      struct rule *rp = ap->x.rp;
+      fprintf(fp,"%*s shift-reduce %-7d",indent,ap->sp->name,rp->iRule);
+      RulePrint(fp, rp, -1);
+      break;
+    }
+    case ACCEPT:
+      fprintf(fp,"%*s accept",indent,ap->sp->name);
+      break;
+    case ERROR:
+      fprintf(fp,"%*s error",indent,ap->sp->name);
+      break;
+    case SRCONFLICT:
+    case RRCONFLICT:
+      fprintf(fp,"%*s reduce       %-7d ** Parsing conflict **",
+        indent,ap->sp->name,ap->x.rp->iRule);
+      break;
+    case SSCONFLICT:
+      fprintf(fp,"%*s shift        %-7d ** Parsing conflict **",
+        indent,ap->sp->name,ap->x.stp->statenum);
+      break;
+    case SH_RESOLVED:
+      if( showPrecedenceConflict ){
+        fprintf(fp,"%*s shift        %-7d -- dropped by precedence",
+                indent,ap->sp->name,ap->x.stp->statenum);
+      }else{
+        result = 0;
+      }
+      break;
+    case RD_RESOLVED:
+      if( showPrecedenceConflict ){
+        fprintf(fp,"%*s reduce %-7d -- dropped by precedence",
+                indent,ap->sp->name,ap->x.rp->iRule);
+      }else{
+        result = 0;
+      }
+      break;
+    case NOT_USED:
+      result = 0;
+      break;
+  }
+  if( result && ap->spOpt ){
+    fprintf(fp,"  /* because %s==%s */", ap->sp->name, ap->spOpt->name);
+  }
+  return result;
+}
+
+/* Generate the "*.out" log file */
+void ReportOutput(struct lemon *lemp)
+{
+  int i, n;
+  struct state *stp;
+  struct config *cfp;
+  struct action *ap;
+  struct rule *rp;
+  FILE *fp;
+
+  fp = file_open(lemp,".out","wb");
+  if( fp==0 ) return;
+  for(i=0; i<lemp->nxstate; i++){
+    stp = lemp->sorted[i];
+    fprintf(fp,"State %d:\n",stp->statenum);
+    if( lemp->basisflag ) cfp=stp->bp;
+    else                  cfp=stp->cfp;
+    while( cfp ){
+      char buf[20];
+      if( cfp->dot==cfp->rp->nrhs ){
+        lemon_sprintf(buf,"(%d)",cfp->rp->iRule);
+        fprintf(fp,"    %5s ",buf);
+      }else{
+        fprintf(fp,"          ");
+      }
+      ConfigPrint(fp,cfp);
+      fprintf(fp,"\n");
+#if 0
+      SetPrint(fp,cfp->fws,lemp);
+      PlinkPrint(fp,cfp->fplp,"To  ");
+      PlinkPrint(fp,cfp->bplp,"From");
+#endif
+      if( lemp->basisflag ) cfp=cfp->bp;
+      else                  cfp=cfp->next;
+    }
+    fprintf(fp,"\n");
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( PrintAction(ap,fp,30) ) fprintf(fp,"\n");
+    }
+    fprintf(fp,"\n");
+  }
+  fprintf(fp, "----------------------------------------------------\n");
+  fprintf(fp, "Symbols:\n");
+  fprintf(fp, "The first-set of non-terminals is shown after the name.\n\n");
+  for(i=0; i<lemp->nsymbol; i++){
+    int j;
+    struct symbol *sp;
+
+    sp = lemp->symbols[i];
+    fprintf(fp, "  %3d: %s", i, sp->name);
+    if( sp->type==NONTERMINAL ){
+      fprintf(fp, ":");
+      if( sp->lambda ){
+        fprintf(fp, " <lambda>");
+      }
+      for(j=0; j<lemp->nterminal; j++){
+        if( sp->firstset && SetFind(sp->firstset, j) ){
+          fprintf(fp, " %s", lemp->symbols[j]->name);
+        }
+      }
+    }
+    if( sp->prec>=0 ) fprintf(fp," (precedence=%d)", sp->prec);
+    fprintf(fp, "\n");
+  }
+  fprintf(fp, "----------------------------------------------------\n");
+  fprintf(fp, "Syntax-only Symbols:\n");
+  fprintf(fp, "The following symbols never carry semantic content.\n\n");
+  for(i=n=0; i<lemp->nsymbol; i++){
+    int w;
+    struct symbol *sp = lemp->symbols[i];
+    if( sp->bContent ) continue;
+    w = (int)strlen(sp->name);
+    if( n>0 && n+w>75 ){
+      fprintf(fp,"\n");
+      n = 0;
+    }
+    if( n>0 ){
+      fprintf(fp, " ");
+      n++;
+    }
+    fprintf(fp, "%s", sp->name);
+    n += w;
+  }
+  if( n>0 ) fprintf(fp, "\n");
+  fprintf(fp, "----------------------------------------------------\n");
+  fprintf(fp, "Rules:\n");
+  for(rp=lemp->rule; rp; rp=rp->next){
+    fprintf(fp, "%4d: ", rp->iRule);
+    rule_print(fp, rp);
+    fprintf(fp,".");
+    if( rp->precsym ){
+      fprintf(fp," [%s precedence=%d]",
+              rp->precsym->name, rp->precsym->prec);
+    }
+    fprintf(fp,"\n");
+  }
+  fclose(fp);
+  return;
+}
+
+/* Search for the file "name" which is in the same directory as
+** the executable */
+PRIVATE char *pathsearch(char *argv0, char *name, int modemask)
+{
+  const char *pathlist;
+  char *pathbufptr = 0;
+  char *pathbuf = 0;
+  char *path,*cp;
+  char c;
+
+#ifdef __WIN32__
+  cp = strrchr(argv0,'\\');
+#else
+  cp = strrchr(argv0,'/');
+#endif
+  if( cp ){
+    c = *cp;
+    *cp = 0;
+    path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 );
+    if( path ) lemon_sprintf(path,"%s/%s",argv0,name);
+    *cp = c;
+  }else{
+    pathlist = getenv("PATH");
+    if( pathlist==0 ) pathlist = ".:/bin:/usr/bin";
+    pathbuf = (char *) malloc( lemonStrlen(pathlist) + 1 );
+    path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 );
+    if( (pathbuf != 0) && (path!=0) ){
+      pathbufptr = pathbuf;
+      lemon_strcpy(pathbuf, pathlist);
+      while( *pathbuf ){
+        cp = strchr(pathbuf,':');
+        if( cp==0 ) cp = &pathbuf[lemonStrlen(pathbuf)];
+        c = *cp;
+        *cp = 0;
+        lemon_sprintf(path,"%s/%s",pathbuf,name);
+        *cp = c;
+        if( c==0 ) pathbuf[0] = 0;
+        else pathbuf = &cp[1];
+        if( access(path,modemask)==0 ) break;
+      }
+    }
+    free(pathbufptr);
+  }
+  return path;
+}
+
+/* Given an action, compute the integer value for that action
+** which is to be put in the action table of the generated machine.
+** Return negative if no action should be generated.
+*/
+PRIVATE int compute_action(struct lemon *lemp, struct action *ap)
+{
+  int act;
+  switch( ap->type ){
+    case SHIFT:  act = ap->x.stp->statenum;                        break;
+    case SHIFTREDUCE: {
+      /* Since a SHIFT is inherient after a prior REDUCE, convert any
+      ** SHIFTREDUCE action with a nonterminal on the LHS into a simple
+      ** REDUCE action: */
+      if( ap->sp->index>=lemp->nterminal
+       && (lemp->errsym==0 || ap->sp->index!=lemp->errsym->index)
+      ){
+        act = lemp->minReduce + ap->x.rp->iRule;
+      }else{
+        act = lemp->minShiftReduce + ap->x.rp->iRule;
+      }
+      break;
+    }
+    case REDUCE: act = lemp->minReduce + ap->x.rp->iRule;          break;
+    case ERROR:  act = lemp->errAction;                            break;
+    case ACCEPT: act = lemp->accAction;                            break;
+    default:     act = -1; break;
+  }
+  return act;
+}
+
+#define LINESIZE 1000
+/* The next cluster of routines are for reading the template file
+** and writing the results to the generated parser */
+/* The first function transfers data from "in" to "out" until
+** a line is seen which begins with "%%".  The line number is
+** tracked.
+**
+** if name!=0, then any word that begin with "Parse" is changed to
+** begin with *name instead.
+*/
+PRIVATE void tplt_xfer(char *name, FILE *in, FILE *out, int *lineno)
+{
+  int i, iStart;
+  char line[LINESIZE];
+  while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+    (*lineno)++;
+    iStart = 0;
+    if( name ){
+      for(i=0; line[i]; i++){
+        if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0
+          && (i==0 || !ISALPHA(line[i-1]))
+        ){
+          if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]);
+          fprintf(out,"%s",name);
+          i += 4;
+          iStart = i+1;
+        }
+      }
+    }
+    fprintf(out,"%s",&line[iStart]);
+  }
+}
+
+/* Skip forward past the header of the template file to the first "%%"
+*/
+PRIVATE void tplt_skip_header(FILE *in, int *lineno)
+{
+  char line[LINESIZE];
+  while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){
+    (*lineno)++;
+  }
+}
+
+/* The next function finds the template file and opens it, returning
+** a pointer to the opened file. */
+PRIVATE FILE *tplt_open(struct lemon *lemp)
+{
+  static char templatename[] = "lempar.c";
+  char buf[1000];
+  FILE *in;
+  char *tpltname;
+  char *toFree = 0;
+  char *cp;
+
+  /* first, see if user specified a template filename on the command line. */
+  if (user_templatename != 0) {
+    if( access(user_templatename,004)==-1 ){
+      fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+        user_templatename);
+      lemp->errorcnt++;
+      return 0;
+    }
+    in = fopen(user_templatename,"rb");
+    if( in==0 ){
+      fprintf(stderr,"Can't open the template file \"%s\".\n",
+              user_templatename);
+      lemp->errorcnt++;
+      return 0;
+    }
+    return in;
+  }
+
+  cp = strrchr(lemp->filename,'.');
+  if( cp ){
+    lemon_sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename);
+  }else{
+    lemon_sprintf(buf,"%s.lt",lemp->filename);
+  }
+  if( access(buf,004)==0 ){
+    tpltname = buf;
+  }else if( access(templatename,004)==0 ){
+    tpltname = templatename;
+  }else{
+    toFree = tpltname = pathsearch(lemp->argv0,templatename,0);
+  }
+  if( tpltname==0 ){
+    fprintf(stderr,"Can't find the parser driver template file \"%s\".\n",
+    templatename);
+    lemp->errorcnt++;
+    return 0;
+  }
+  in = fopen(tpltname,"rb");
+  if( in==0 ){
+    fprintf(stderr,"Can't open the template file \"%s\".\n",tpltname);
+    lemp->errorcnt++;
+  }
+  free(toFree);
+  return in;
+}
+
+/* Print a #line directive line to the output file. */
+PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename)
+{
+  fprintf(out,"#line %d \"",lineno);
+  while( *filename ){
+    if( *filename == '\\' ) putc('\\',out);
+    putc(*filename,out);
+    filename++;
+  }
+  fprintf(out,"\"\n");
+}
+
+/* Print a string to the file and keep the linenumber up to date */
+PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno)
+{
+  if( str==0 ) return;
+  while( *str ){
+    putc(*str,out);
+    if( *str=='\n' ) (*lineno)++;
+    str++;
+  }
+  if( str[-1]!='\n' ){
+    putc('\n',out);
+    (*lineno)++;
+  }
+  if (!lemp->nolinenosflag) {
+    (*lineno)++; tplt_linedir(out,*lineno,lemp->outname);
+  }
+  return;
+}
+
+/*
+** The following routine emits code for the destructor for the
+** symbol sp
+*/
+void emit_destructor_code(
+  FILE *out,
+  struct symbol *sp,
+  struct lemon *lemp,
+  int *lineno
+){
+ char *cp = 0;
+
+ if( sp->type==TERMINAL ){
+   cp = lemp->tokendest;
+   if( cp==0 ) return;
+   fprintf(out,"{\n"); (*lineno)++;
+ }else if( sp->destructor ){
+   cp = sp->destructor;
+   fprintf(out,"{\n"); (*lineno)++;
+   if( !lemp->nolinenosflag ){
+     (*lineno)++;
+     tplt_linedir(out,sp->destLineno,lemp->filename);
+   }
+ }else if( lemp->vardest ){
+   cp = lemp->vardest;
+   if( cp==0 ) return;
+   fprintf(out,"{\n"); (*lineno)++;
+ }else{
+   assert( 0 );  /* Cannot happen */
+ }
+ for(; *cp; cp++){
+   if( *cp=='$' && cp[1]=='$' ){
+     fprintf(out,"(yypminor->yy%d)",sp->dtnum);
+     cp++;
+     continue;
+   }
+   if( *cp=='\n' ) (*lineno)++;
+   fputc(*cp,out);
+ }
+ fprintf(out,"\n"); (*lineno)++;
+ if (!lemp->nolinenosflag) {
+   (*lineno)++; tplt_linedir(out,*lineno,lemp->outname);
+ }
+ fprintf(out,"}\n"); (*lineno)++;
+ return;
+}
+
+/*
+** Return TRUE (non-zero) if the given symbol has a destructor.
+*/
+int has_destructor(struct symbol *sp, struct lemon *lemp)
+{
+  int ret;
+  if( sp->type==TERMINAL ){
+    ret = lemp->tokendest!=0;
+  }else{
+    ret = lemp->vardest!=0 || sp->destructor!=0;
+  }
+  return ret;
+}
+
+/*
+** Append text to a dynamically allocated string.  If zText is 0 then
+** reset the string to be empty again.  Always return the complete text
+** of the string (which is overwritten with each call).
+**
+** n bytes of zText are stored.  If n==0 then all of zText up to the first
+** \000 terminator is stored.  zText can contain up to two instances of
+** %d.  The values of p1 and p2 are written into the first and second
+** %d.
+**
+** If n==-1, then the previous character is overwritten.
+*/
+PRIVATE char *append_str(const char *zText, int n, int p1, int p2){
+  static char empty[1] = { 0 };
+  static char *z = 0;
+  static int alloced = 0;
+  static int used = 0;
+  int c;
+  char zInt[40];
+  if( zText==0 ){
+    if( used==0 && z!=0 ) z[0] = 0;
+    used = 0;
+    return z;
+  }
+  if( n<=0 ){
+    if( n<0 ){
+      used += n;
+      assert( used>=0 );
+    }
+    n = lemonStrlen(zText);
+  }
+  if( (int) (n+sizeof(zInt)*2+used) >= alloced ){
+    alloced = n + sizeof(zInt)*2 + used + 200;
+    z = (char *) realloc(z,  alloced);
+  }
+  if( z==0 ) return empty;
+  while( n-- > 0 ){
+    c = *(zText++);
+    if( c=='%' && n>0 && zText[0]=='d' ){
+      lemon_sprintf(zInt, "%d", p1);
+      p1 = p2;
+      lemon_strcpy(&z[used], zInt);
+      used += lemonStrlen(&z[used]);
+      zText++;
+      n--;
+    }else{
+      z[used++] = (char)c;
+    }
+  }
+  z[used] = 0;
+  return z;
+}
+
+/*
+** Write and transform the rp->code string so that symbols are expanded.
+** Populate the rp->codePrefix and rp->codeSuffix strings, as appropriate.
+**
+** Return 1 if the expanded code requires that "yylhsminor" local variable
+** to be defined.
+*/
+PRIVATE int translate_code(struct lemon *lemp, struct rule *rp){
+  char *cp, *xp;
+  int i;
+  int rc = 0;            /* True if yylhsminor is used */
+  int dontUseRhs0 = 0;   /* If true, use of left-most RHS label is illegal */
+  const char *zSkip = 0; /* The zOvwrt comment within rp->code, or NULL */
+  char lhsused = 0;      /* True if the LHS element has been used */
+  char lhsdirect;        /* True if LHS writes directly into stack */
+  char used[MAXRHS];     /* True for each RHS element which is used */
+  char zLhs[50];         /* Convert the LHS symbol into this string */
+  char zOvwrt[900];      /* Comment that to allow LHS to overwrite RHS */
+
+  for(i=0; i<rp->nrhs; i++) used[i] = 0;
+  lhsused = 0;
+
+  if( rp->code==0 ){
+    static char newlinestr[2] = { '\n', '\0' };
+    rp->code = newlinestr;
+    rp->line = rp->ruleline;
+    rp->noCode = 1;
+  }else{
+    rp->noCode = 0;
+  }
+
+
+  if( rp->nrhs==0 ){
+    /* If there are no RHS symbols, then writing directly to the LHS is ok */
+    lhsdirect = 1;
+  }else if( rp->rhsalias[0]==0 ){
+    /* The left-most RHS symbol has no value.  LHS direct is ok.  But
+    ** we have to call the destructor on the RHS symbol first. */
+    lhsdirect = 1;
+    if( has_destructor(rp->rhs[0],lemp) ){
+      append_str(0,0,0,0);
+      append_str("  yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0,
+                 rp->rhs[0]->index,1-rp->nrhs);
+      rp->codePrefix = Strsafe(append_str(0,0,0,0));
+      rp->noCode = 0;
+    }
+  }else if( rp->lhsalias==0 ){
+    /* There is no LHS value symbol. */
+    lhsdirect = 1;
+  }else if( strcmp(rp->lhsalias,rp->rhsalias[0])==0 ){
+    /* The LHS symbol and the left-most RHS symbol are the same, so
+    ** direct writing is allowed */
+    lhsdirect = 1;
+    lhsused = 1;
+    used[0] = 1;
+    if( rp->lhs->dtnum!=rp->rhs[0]->dtnum ){
+      ErrorMsg(lemp->filename,rp->ruleline,
+        "%s(%s) and %s(%s) share the same label but have "
+        "different datatypes.",
+        rp->lhs->name, rp->lhsalias, rp->rhs[0]->name, rp->rhsalias[0]);
+      lemp->errorcnt++;
+    }
+  }else{
+    lemon_sprintf(zOvwrt, "/*%s-overwrites-%s*/",
+                  rp->lhsalias, rp->rhsalias[0]);
+    zSkip = strstr(rp->code, zOvwrt);
+    if( zSkip!=0 ){
+      /* The code contains a special comment that indicates that it is safe
+      ** for the LHS label to overwrite left-most RHS label. */
+      lhsdirect = 1;
+    }else{
+      lhsdirect = 0;
+    }
+  }
+  if( lhsdirect ){
+    sprintf(zLhs, "yymsp[%d].minor.yy%d",1-rp->nrhs,rp->lhs->dtnum);
+  }else{
+    rc = 1;
+    sprintf(zLhs, "yylhsminor.yy%d",rp->lhs->dtnum);
+  }
+
+  append_str(0,0,0,0);
+
+  /* This const cast is wrong but harmless, if we're careful. */
+  for(cp=(char *)rp->code; *cp; cp++){
+    if( cp==zSkip ){
+      append_str(zOvwrt,0,0,0);
+      cp += lemonStrlen(zOvwrt)-1;
+      dontUseRhs0 = 1;
+      continue;
+    }
+    if( ISALPHA(*cp) && (cp==rp->code || (!ISALNUM(cp[-1]) && cp[-1]!='_')) ){
+      char saved;
+      for(xp= &cp[1]; ISALNUM(*xp) || *xp=='_'; xp++);
+      saved = *xp;
+      *xp = 0;
+      if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){
+        append_str(zLhs,0,0,0);
+        cp = xp;
+        lhsused = 1;
+      }else{
+        for(i=0; i<rp->nrhs; i++){
+          if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){
+            if( i==0 && dontUseRhs0 ){
+              ErrorMsg(lemp->filename,rp->ruleline,
+                 "Label %s used after '%s'.",
+                 rp->rhsalias[0], zOvwrt);
+              lemp->errorcnt++;
+            }else if( cp!=rp->code && cp[-1]=='@' ){
+              /* If the argument is of the form @X then substituted
+              ** the token number of X, not the value of X */
+              append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0);
+            }else{
+              struct symbol *sp = rp->rhs[i];
+              int dtnum;
+              if( sp->type==MULTITERMINAL ){
+                dtnum = sp->subsym[0]->dtnum;
+              }else{
+                dtnum = sp->dtnum;
+              }
+              append_str("yymsp[%d].minor.yy%d",0,i-rp->nrhs+1, dtnum);
+            }
+            cp = xp;
+            used[i] = 1;
+            break;
+          }
+        }
+      }
+      *xp = saved;
+    }
+    append_str(cp, 1, 0, 0);
+  } /* End loop */
+
+  /* Main code generation completed */
+  cp = append_str(0,0,0,0);
+  if( cp && cp[0] ) rp->code = Strsafe(cp);
+  append_str(0,0,0,0);
+
+  /* Check to make sure the LHS has been used */
+  if( rp->lhsalias && !lhsused ){
+    ErrorMsg(lemp->filename,rp->ruleline,
+      "Label \"%s\" for \"%s(%s)\" is never used.",
+        rp->lhsalias,rp->lhs->name,rp->lhsalias);
+    lemp->errorcnt++;
+  }
+
+  /* Generate destructor code for RHS minor values which are not referenced.
+  ** Generate error messages for unused labels and duplicate labels.
+  */
+  for(i=0; i<rp->nrhs; i++){
+    if( rp->rhsalias[i] ){
+      if( i>0 ){
+        int j;
+        if( rp->lhsalias && strcmp(rp->lhsalias,rp->rhsalias[i])==0 ){
+          ErrorMsg(lemp->filename,rp->ruleline,
+            "%s(%s) has the same label as the LHS but is not the left-most "
+            "symbol on the RHS.",
+            rp->rhs[i]->name, rp->rhsalias[i]);
+          lemp->errorcnt++;
+        }
+        for(j=0; j<i; j++){
+          if( rp->rhsalias[j] && strcmp(rp->rhsalias[j],rp->rhsalias[i])==0 ){
+            ErrorMsg(lemp->filename,rp->ruleline,
+              "Label %s used for multiple symbols on the RHS of a rule.",
+              rp->rhsalias[i]);
+            lemp->errorcnt++;
+            break;
+          }
+        }
+      }
+      if( !used[i] ){
+        ErrorMsg(lemp->filename,rp->ruleline,
+          "Label %s for \"%s(%s)\" is never used.",
+          rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]);
+        lemp->errorcnt++;
+      }
+    }else if( i>0 && has_destructor(rp->rhs[i],lemp) ){
+      append_str("  yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0,
+         rp->rhs[i]->index,i-rp->nrhs+1);
+    }
+  }
+
+  /* If unable to write LHS values directly into the stack, write the
+  ** saved LHS value now. */
+  if( lhsdirect==0 ){
+    append_str("  yymsp[%d].minor.yy%d = ", 0, 1-rp->nrhs, rp->lhs->dtnum);
+    append_str(zLhs, 0, 0, 0);
+    append_str(";\n", 0, 0, 0);
+  }
+
+  /* Suffix code generation complete */
+  cp = append_str(0,0,0,0);
+  if( cp && cp[0] ){
+    rp->codeSuffix = Strsafe(cp);
+    rp->noCode = 0;
+  }
+
+  return rc;
+}
+
+/*
+** Generate code which executes when the rule "rp" is reduced.  Write
+** the code to "out".  Make sure lineno stays up-to-date.
+*/
+PRIVATE void emit_code(
+  FILE *out,
+  struct rule *rp,
+  struct lemon *lemp,
+  int *lineno
+){
+ const char *cp;
+
+ /* Setup code prior to the #line directive */
+ if( rp->codePrefix && rp->codePrefix[0] ){
+   fprintf(out, "{%s", rp->codePrefix);
+   for(cp=rp->codePrefix; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; }
+ }
+
+ /* Generate code to do the reduce action */
+ if( rp->code ){
+   if( !lemp->nolinenosflag ){
+     (*lineno)++;
+     tplt_linedir(out,rp->line,lemp->filename);
+   }
+   fprintf(out,"{%s",rp->code);
+   for(cp=rp->code; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; }
+   fprintf(out,"}\n"); (*lineno)++;
+   if( !lemp->nolinenosflag ){
+     (*lineno)++;
+     tplt_linedir(out,*lineno,lemp->outname);
+   }
+ }
+
+ /* Generate breakdown code that occurs after the #line directive */
+ if( rp->codeSuffix && rp->codeSuffix[0] ){
+   fprintf(out, "%s", rp->codeSuffix);
+   for(cp=rp->codeSuffix; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; }
+ }
+
+ if( rp->codePrefix ){
+   fprintf(out, "}\n"); (*lineno)++;
+ }
+
+ return;
+}
+
+/*
+** Print the definition of the union used for the parser's data stack.
+** This union contains fields for every possible data type for tokens
+** and nonterminals.  In the process of computing and printing this
+** union, also set the ".dtnum" field of every terminal and nonterminal
+** symbol.
+*/
+void print_stack_union(
+  FILE *out,                  /* The output stream */
+  struct lemon *lemp,         /* The main info structure for this parser */
+  int *plineno,               /* Pointer to the line number */
+  int mhflag                  /* True if generating makeheaders output */
+){
+  int lineno;               /* The line number of the output */
+  char **types;             /* A hash table of datatypes */
+  int arraysize;            /* Size of the "types" array */
+  int maxdtlength;          /* Maximum length of any ".datatype" field. */
+  char *stddt;              /* Standardized name for a datatype */
+  int i,j;                  /* Loop counters */
+  unsigned hash;            /* For hashing the name of a type */
+  const char *name;         /* Name of the parser */
+
+  /* Allocate and initialize types[] and allocate stddt[] */
+  arraysize = lemp->nsymbol * 2;
+  types = (char**)calloc( arraysize, sizeof(char*) );
+  if( types==0 ){
+    fprintf(stderr,"Out of memory.\n");
+    exit(1);
+  }
+  for(i=0; i<arraysize; i++) types[i] = 0;
+  maxdtlength = 0;
+  if( lemp->vartype ){
+    maxdtlength = lemonStrlen(lemp->vartype);
+  }
+  for(i=0; i<lemp->nsymbol; i++){
+    int len;
+    struct symbol *sp = lemp->symbols[i];
+    if( sp->datatype==0 ) continue;
+    len = lemonStrlen(sp->datatype);
+    if( len>maxdtlength ) maxdtlength = len;
+  }
+  stddt = (char*)malloc( maxdtlength*2 + 1 );
+  if( stddt==0 ){
+    fprintf(stderr,"Out of memory.\n");
+    exit(1);
+  }
+
+  /* Build a hash table of datatypes. The ".dtnum" field of each symbol
+  ** is filled in with the hash index plus 1.  A ".dtnum" value of 0 is
+  ** used for terminal symbols.  If there is no %default_type defined then
+  ** 0 is also used as the .dtnum value for nonterminals which do not specify
+  ** a datatype using the %type directive.
+  */
+  for(i=0; i<lemp->nsymbol; i++){
+    struct symbol *sp = lemp->symbols[i];
+    char *cp;
+    if( sp==lemp->errsym ){
+      sp->dtnum = arraysize+1;
+      continue;
+    }
+    if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){
+      sp->dtnum = 0;
+      continue;
+    }
+    cp = sp->datatype;
+    if( cp==0 ) cp = lemp->vartype;
+    j = 0;
+    while( ISSPACE(*cp) ) cp++;
+    while( *cp ) stddt[j++] = *cp++;
+    while( j>0 && ISSPACE(stddt[j-1]) ) j--;
+    stddt[j] = 0;
+    if( lemp->tokentype && strcmp(stddt, lemp->tokentype)==0 ){
+      sp->dtnum = 0;
+      continue;
+    }
+    hash = 0;
+    for(j=0; stddt[j]; j++){
+      hash = hash*53 + stddt[j];
+    }
+    hash = (hash & 0x7fffffff)%arraysize;
+    while( types[hash] ){
+      if( strcmp(types[hash],stddt)==0 ){
+        sp->dtnum = hash + 1;
+        break;
+      }
+      hash++;
+      if( hash>=(unsigned)arraysize ) hash = 0;
+    }
+    if( types[hash]==0 ){
+      sp->dtnum = hash + 1;
+      types[hash] = (char*)malloc( lemonStrlen(stddt)+1 );
+      if( types[hash]==0 ){
+        fprintf(stderr,"Out of memory.\n");
+        exit(1);
+      }
+      lemon_strcpy(types[hash],stddt);
+    }
+  }
+
+  /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */
+  name = lemp->name ? lemp->name : "Parse";
+  lineno = *plineno;
+  if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; }
+  fprintf(out,"#define %sTOKENTYPE %s\n",name,
+    lemp->tokentype?lemp->tokentype:"void*");  lineno++;
+  if( mhflag ){ fprintf(out,"#endif\n"); lineno++; }
+  fprintf(out,"typedef union {\n"); lineno++;
+  fprintf(out,"  int yyinit;\n"); lineno++;
+  fprintf(out,"  %sTOKENTYPE yy0;\n",name); lineno++;
+  for(i=0; i<arraysize; i++){
+    if( types[i]==0 ) continue;
+    fprintf(out,"  %s yy%d;\n",types[i],i+1); lineno++;
+    free(types[i]);
+  }
+  if( lemp->errsym && lemp->errsym->useCnt ){
+    fprintf(out,"  int yy%d;\n",lemp->errsym->dtnum); lineno++;
+  }
+  free(stddt);
+  free(types);
+  fprintf(out,"} YYMINORTYPE;\n"); lineno++;
+  *plineno = lineno;
+}
+
+/*
+** Return the name of a C datatype able to represent values between
+** lwr and upr, inclusive.  If pnByte!=NULL then also write the sizeof
+** for that type (1, 2, or 4) into *pnByte.
+*/
+static const char *minimum_size_type(int lwr, int upr, int *pnByte){
+  const char *zType = "int";
+  int nByte = 4;
+  if( lwr>=0 ){
+    if( upr<=255 ){
+      zType = "unsigned char";
+      nByte = 1;
+    }else if( upr<65535 ){
+      zType = "unsigned short int";
+      nByte = 2;
+    }else{
+      zType = "unsigned int";
+      nByte = 4;
+    }
+  }else if( lwr>=-127 && upr<=127 ){
+    zType = "signed char";
+    nByte = 1;
+  }else if( lwr>=-32767 && upr<32767 ){
+    zType = "short";
+    nByte = 2;
+  }
+  if( pnByte ) *pnByte = nByte;
+  return zType;
+}
+
+/*
+** Each state contains a set of token transaction and a set of
+** nonterminal transactions.  Each of these sets makes an instance
+** of the following structure.  An array of these structures is used
+** to order the creation of entries in the yy_action[] table.
+*/
+struct axset {
+  struct state *stp;   /* A pointer to a state */
+  int isTkn;           /* True to use tokens.  False for non-terminals */
+  int nAction;         /* Number of actions */
+  int iOrder;          /* Original order of action sets */
+};
+
+/*
+** Compare to axset structures for sorting purposes
+*/
+static int axset_compare(const void *a, const void *b){
+  struct axset *p1 = (struct axset*)a;
+  struct axset *p2 = (struct axset*)b;
+  int c;
+  c = p2->nAction - p1->nAction;
+  if( c==0 ){
+    c = p1->iOrder - p2->iOrder;
+  }
+  assert( c!=0 || p1==p2 );
+  return c;
+}
+
+/*
+** Write text on "out" that describes the rule "rp".
+*/
+static void writeRuleText(FILE *out, struct rule *rp){
+  int j;
+  fprintf(out,"%s ::=", rp->lhs->name);
+  for(j=0; j<rp->nrhs; j++){
+    struct symbol *sp = rp->rhs[j];
+    if( sp->type!=MULTITERMINAL ){
+      fprintf(out," %s", sp->name);
+    }else{
+      int k;
+      fprintf(out," %s", sp->subsym[0]->name);
+      for(k=1; k<sp->nsubsym; k++){
+        fprintf(out,"|%s",sp->subsym[k]->name);
+      }
+    }
+  }
+}
+
+
+/* Generate C source code for the parser */
+void ReportTable(
+  struct lemon *lemp,
+  int mhflag,     /* Output in makeheaders format if true */
+  int sqlFlag     /* Generate the *.sql file too */
+){
+  FILE *out, *in, *sql;
+  int  lineno;
+  struct state *stp;
+  struct action *ap;
+  struct rule *rp;
+  struct acttab *pActtab;
+  int i, j, n, sz;
+  int nLookAhead;
+  int szActionType;     /* sizeof(YYACTIONTYPE) */
+  int szCodeType;       /* sizeof(YYCODETYPE)   */
+  const char *name;
+  int mnTknOfst, mxTknOfst;
+  int mnNtOfst, mxNtOfst;
+  struct axset *ax;
+  char *prefix;
+
+  lemp->minShiftReduce = lemp->nstate;
+  lemp->errAction = lemp->minShiftReduce + lemp->nrule;
+  lemp->accAction = lemp->errAction + 1;
+  lemp->noAction = lemp->accAction + 1;
+  lemp->minReduce = lemp->noAction + 1;
+  lemp->maxAction = lemp->minReduce + lemp->nrule;
+
+  in = tplt_open(lemp);
+  if( in==0 ) return;
+  out = file_open(lemp,".c","wb");
+  if( out==0 ){
+    fclose(in);
+    return;
+  }
+  if( sqlFlag==0 ){
+    sql = 0;
+  }else{
+    sql = file_open(lemp, ".sql", "wb");
+    if( sql==0 ){
+      fclose(in);
+      fclose(out);
+      return;
+    }
+    fprintf(sql,
+       "BEGIN;\n"
+       "CREATE TABLE symbol(\n"
+       "  id INTEGER PRIMARY KEY,\n"
+       "  name TEXT NOT NULL,\n"
+       "  isTerminal BOOLEAN NOT NULL,\n"
+       "  fallback INTEGER REFERENCES symbol"
+               " DEFERRABLE INITIALLY DEFERRED\n"
+       ");\n"
+    );
+    for(i=0; i<lemp->nsymbol; i++){
+      fprintf(sql,
+         "INSERT INTO symbol(id,name,isTerminal,fallback)"
+         "VALUES(%d,'%s',%s",
+         i, lemp->symbols[i]->name,
+         i<lemp->nterminal ? "TRUE" : "FALSE"
+      );
+      if( lemp->symbols[i]->fallback ){
+        fprintf(sql, ",%d);\n", lemp->symbols[i]->fallback->index);
+      }else{
+        fprintf(sql, ",NULL);\n");
+      }
+    }
+    fprintf(sql,
+      "CREATE TABLE rule(\n"
+      "  ruleid INTEGER PRIMARY KEY,\n"
+      "  lhs INTEGER REFERENCES symbol(id),\n"
+      "  txt TEXT\n"
+      ");\n"
+      "CREATE TABLE rulerhs(\n"
+      "  ruleid INTEGER REFERENCES rule(ruleid),\n"
+      "  pos INTEGER,\n"
+      "  sym INTEGER REFERENCES symbol(id)\n"
+      ");\n"
+    );
+    for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+      assert( i==rp->iRule );
+      fprintf(sql,
+        "INSERT INTO rule(ruleid,lhs,txt)VALUES(%d,%d,'",
+        rp->iRule, rp->lhs->index
+      );
+      writeRuleText(sql, rp);
+      fprintf(sql,"');\n");
+      for(j=0; j<rp->nrhs; j++){
+        struct symbol *sp = rp->rhs[j];
+        if( sp->type!=MULTITERMINAL ){
+          fprintf(sql,
+            "INSERT INTO rulerhs(ruleid,pos,sym)VALUES(%d,%d,%d);\n",
+            i,j,sp->index
+          );
+        }else{
+          int k;
+          for(k=0; k<sp->nsubsym; k++){
+            fprintf(sql,
+              "INSERT INTO rulerhs(ruleid,pos,sym)VALUES(%d,%d,%d);\n",
+              i,j,sp->subsym[k]->index
+            );
+          }
+        }
+      }
+    }
+    fprintf(sql, "COMMIT;\n");
+  }
+  lineno = 1;
+
+  fprintf(out, 
+     "/* This file is automatically generated by Lemon from input grammar\n"
+     "** source file \"%s\". */\n", lemp->filename); lineno += 2;
+  
+  /* The first %include directive begins with a C-language comment,
+  ** then skip over the header comment of the template file
+  */
+  if( lemp->include==0 ) lemp->include = "";
+  for(i=0; ISSPACE(lemp->include[i]); i++){
+    if( lemp->include[i]=='\n' ){
+      lemp->include += i+1;
+      i = -1;
+    }
+  }
+  if( lemp->include[0]=='/' ){
+    tplt_skip_header(in,&lineno);
+  }else{
+    tplt_xfer(lemp->name,in,out,&lineno);
+  }
+
+  /* Generate the include code, if any */
+  tplt_print(out,lemp,lemp->include,&lineno);
+  if( mhflag ){
+    char *incName = file_makename(lemp, ".h");
+    fprintf(out,"#include \"%s\"\n", incName); lineno++;
+    free(incName);
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate #defines for all tokens */
+  if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+  else                    prefix = "";
+  if( mhflag ){
+    fprintf(out,"#if INTERFACE\n"); lineno++;
+  }else{
+    fprintf(out,"#ifndef %s%s\n", prefix, lemp->symbols[1]->name);
+  }
+  for(i=1; i<lemp->nterminal; i++){
+    fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i);
+    lineno++;
+  }
+  fprintf(out,"#endif\n"); lineno++;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the defines */
+  fprintf(out,"#define YYCODETYPE %s\n",
+    minimum_size_type(0, lemp->nsymbol, &szCodeType)); lineno++;
+  fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol);  lineno++;
+  fprintf(out,"#define YYACTIONTYPE %s\n",
+    minimum_size_type(0,lemp->maxAction,&szActionType)); lineno++;
+  if( lemp->wildcard ){
+    fprintf(out,"#define YYWILDCARD %d\n",
+       lemp->wildcard->index); lineno++;
+  }
+  print_stack_union(out,lemp,&lineno,mhflag);
+  fprintf(out, "#ifndef YYSTACKDEPTH\n"); lineno++;
+  if( lemp->stacksize ){
+    fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize);  lineno++;
+  }else{
+    fprintf(out,"#define YYSTACKDEPTH 100\n");  lineno++;
+  }
+  fprintf(out, "#endif\n"); lineno++;
+  if( mhflag ){
+    fprintf(out,"#if INTERFACE\n"); lineno++;
+  }
+  name = lemp->name ? lemp->name : "Parse";
+  if( lemp->arg && lemp->arg[0] ){
+    i = lemonStrlen(lemp->arg);
+    while( i>=1 && ISSPACE(lemp->arg[i-1]) ) i--;
+    while( i>=1 && (ISALNUM(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--;
+    fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg);  lineno++;
+    fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg);  lineno++;
+    fprintf(out,"#define %sARG_PARAM ,%s\n",name,&lemp->arg[i]);  lineno++;
+    fprintf(out,"#define %sARG_FETCH %s=yypParser->%s;\n",
+                 name,lemp->arg,&lemp->arg[i]);  lineno++;
+    fprintf(out,"#define %sARG_STORE yypParser->%s=%s;\n",
+                 name,&lemp->arg[i],&lemp->arg[i]);  lineno++;
+  }else{
+    fprintf(out,"#define %sARG_SDECL\n",name); lineno++;
+    fprintf(out,"#define %sARG_PDECL\n",name); lineno++;
+    fprintf(out,"#define %sARG_PARAM\n",name); lineno++;
+    fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
+    fprintf(out,"#define %sARG_STORE\n",name); lineno++;
+  }
+  if( lemp->ctx && lemp->ctx[0] ){
+    i = lemonStrlen(lemp->ctx);
+    while( i>=1 && ISSPACE(lemp->ctx[i-1]) ) i--;
+    while( i>=1 && (ISALNUM(lemp->ctx[i-1]) || lemp->ctx[i-1]=='_') ) i--;
+    fprintf(out,"#define %sCTX_SDECL %s;\n",name,lemp->ctx);  lineno++;
+    fprintf(out,"#define %sCTX_PDECL ,%s\n",name,lemp->ctx);  lineno++;
+    fprintf(out,"#define %sCTX_PARAM ,%s\n",name,&lemp->ctx[i]);  lineno++;
+    fprintf(out,"#define %sCTX_FETCH %s=yypParser->%s;\n",
+                 name,lemp->ctx,&lemp->ctx[i]);  lineno++;
+    fprintf(out,"#define %sCTX_STORE yypParser->%s=%s;\n",
+                 name,&lemp->ctx[i],&lemp->ctx[i]);  lineno++;
+  }else{
+    fprintf(out,"#define %sCTX_SDECL\n",name); lineno++;
+    fprintf(out,"#define %sCTX_PDECL\n",name); lineno++;
+    fprintf(out,"#define %sCTX_PARAM\n",name); lineno++;
+    fprintf(out,"#define %sCTX_FETCH\n",name); lineno++;
+    fprintf(out,"#define %sCTX_STORE\n",name); lineno++;
+  }
+  if( mhflag ){
+    fprintf(out,"#endif\n"); lineno++;
+  }
+  if( lemp->errsym && lemp->errsym->useCnt ){
+    fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++;
+    fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++;
+  }
+  if( lemp->has_fallback ){
+    fprintf(out,"#define YYFALLBACK 1\n");  lineno++;
+  }
+
+  /* Compute the action table, but do not output it yet.  The action
+  ** table must be computed before generating the YYNSTATE macro because
+  ** we need to know how many states can be eliminated.
+  */
+  ax = (struct axset *) calloc(lemp->nxstate*2, sizeof(ax[0]));
+  if( ax==0 ){
+    fprintf(stderr,"malloc failed\n");
+    exit(1);
+  }
+  for(i=0; i<lemp->nxstate; i++){
+    stp = lemp->sorted[i];
+    ax[i*2].stp = stp;
+    ax[i*2].isTkn = 1;
+    ax[i*2].nAction = stp->nTknAct;
+    ax[i*2+1].stp = stp;
+    ax[i*2+1].isTkn = 0;
+    ax[i*2+1].nAction = stp->nNtAct;
+  }
+  mxTknOfst = mnTknOfst = 0;
+  mxNtOfst = mnNtOfst = 0;
+  /* In an effort to minimize the action table size, use the heuristic
+  ** of placing the largest action sets first */
+  for(i=0; i<lemp->nxstate*2; i++) ax[i].iOrder = i;
+  qsort(ax, lemp->nxstate*2, sizeof(ax[0]), axset_compare);
+  pActtab = acttab_alloc(lemp->nsymbol, lemp->nterminal);
+  for(i=0; i<lemp->nxstate*2 && ax[i].nAction>0; i++){
+    stp = ax[i].stp;
+    if( ax[i].isTkn ){
+      for(ap=stp->ap; ap; ap=ap->next){
+        int action;
+        if( ap->sp->index>=lemp->nterminal ) continue;
+        action = compute_action(lemp, ap);
+        if( action<0 ) continue;
+        acttab_action(pActtab, ap->sp->index, action);
+      }
+      stp->iTknOfst = acttab_insert(pActtab, 1);
+      if( stp->iTknOfst<mnTknOfst ) mnTknOfst = stp->iTknOfst;
+      if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst;
+    }else{
+      for(ap=stp->ap; ap; ap=ap->next){
+        int action;
+        if( ap->sp->index<lemp->nterminal ) continue;
+        if( ap->sp->index==lemp->nsymbol ) continue;
+        action = compute_action(lemp, ap);
+        if( action<0 ) continue;
+        acttab_action(pActtab, ap->sp->index, action);
+      }
+      stp->iNtOfst = acttab_insert(pActtab, 0);
+      if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst;
+      if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst;
+    }
+#if 0  /* Uncomment for a trace of how the yy_action[] table fills out */
+    { int jj, nn;
+      for(jj=nn=0; jj<pActtab->nAction; jj++){
+        if( pActtab->aAction[jj].action<0 ) nn++;
+      }
+      printf("%4d: State %3d %s n: %2d size: %5d freespace: %d\n",
+             i, stp->statenum, ax[i].isTkn ? "Token" : "Var  ",
+             ax[i].nAction, pActtab->nAction, nn);
+    }
+#endif
+  }
+  free(ax);
+
+  /* Mark rules that are actually used for reduce actions after all
+  ** optimizations have been applied
+  */
+  for(rp=lemp->rule; rp; rp=rp->next) rp->doesReduce = LEMON_FALSE;
+  for(i=0; i<lemp->nxstate; i++){
+    for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){
+      if( ap->type==REDUCE || ap->type==SHIFTREDUCE ){
+        ap->x.rp->doesReduce = 1;
+      }
+    }
+  }
+
+  /* Finish rendering the constants now that the action table has
+  ** been computed */
+  fprintf(out,"#define YYNSTATE             %d\n",lemp->nxstate);  lineno++;
+  fprintf(out,"#define YYNRULE              %d\n",lemp->nrule);  lineno++;
+  fprintf(out,"#define YYNRULE_WITH_ACTION  %d\n",lemp->nruleWithAction);
+         lineno++;
+  fprintf(out,"#define YYNTOKEN             %d\n",lemp->nterminal); lineno++;
+  fprintf(out,"#define YY_MAX_SHIFT         %d\n",lemp->nxstate-1); lineno++;
+  i = lemp->minShiftReduce;
+  fprintf(out,"#define YY_MIN_SHIFTREDUCE   %d\n",i); lineno++;
+  i += lemp->nrule;
+  fprintf(out,"#define YY_MAX_SHIFTREDUCE   %d\n", i-1); lineno++;
+  fprintf(out,"#define YY_ERROR_ACTION      %d\n", lemp->errAction); lineno++;
+  fprintf(out,"#define YY_ACCEPT_ACTION     %d\n", lemp->accAction); lineno++;
+  fprintf(out,"#define YY_NO_ACTION         %d\n", lemp->noAction); lineno++;
+  fprintf(out,"#define YY_MIN_REDUCE        %d\n", lemp->minReduce); lineno++;
+  i = lemp->minReduce + lemp->nrule;
+  fprintf(out,"#define YY_MAX_REDUCE        %d\n", i-1); lineno++;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Now output the action table and its associates:
+  **
+  **  yy_action[]        A single table containing all actions.
+  **  yy_lookahead[]     A table containing the lookahead for each entry in
+  **                     yy_action.  Used to detect hash collisions.
+  **  yy_shift_ofst[]    For each state, the offset into yy_action for
+  **                     shifting terminals.
+  **  yy_reduce_ofst[]   For each state, the offset into yy_action for
+  **                     shifting non-terminals after a reduce.
+  **  yy_default[]       Default action for each state.
+  */
+
+  /* Output the yy_action table */
+  lemp->nactiontab = n = acttab_action_size(pActtab);
+  lemp->tablesize += n*szActionType;
+  fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++;
+  fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++;
+  for(i=j=0; i<n; i++){
+    int action = acttab_yyaction(pActtab, i);
+    if( action<0 ) action = lemp->noAction;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", action);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_lookahead table */
+  lemp->nlookaheadtab = n = acttab_lookahead_size(pActtab);
+  lemp->tablesize += n*szCodeType;
+  fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++;
+  for(i=j=0; i<n; i++){
+    int la = acttab_yylookahead(pActtab, i);
+    if( la<0 ) la = lemp->nsymbol;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", la);
+    if( j==9 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  /* Add extra entries to the end of the yy_lookahead[] table so that
+  ** yy_shift_ofst[]+iToken will always be a valid index into the array,
+  ** even for the largest possible value of yy_shift_ofst[] and iToken. */
+  nLookAhead = lemp->nterminal + lemp->nactiontab;
+  while( i<nLookAhead ){
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", lemp->nterminal);
+    if( j==9 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+    i++;
+  }
+  if( j>0 ){ fprintf(out, "\n"); lineno++; }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_shift_ofst[] table */
+  n = lemp->nxstate;
+  while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--;
+  fprintf(out, "#define YY_SHIFT_COUNT    (%d)\n", n-1); lineno++;
+  fprintf(out, "#define YY_SHIFT_MIN      (%d)\n", mnTknOfst); lineno++;
+  fprintf(out, "#define YY_SHIFT_MAX      (%d)\n", mxTknOfst); lineno++;
+  fprintf(out, "static const %s yy_shift_ofst[] = {\n",
+       minimum_size_type(mnTknOfst, lemp->nterminal+lemp->nactiontab, &sz));
+       lineno++;
+  lemp->tablesize += n*sz;
+  for(i=j=0; i<n; i++){
+    int ofst;
+    stp = lemp->sorted[i];
+    ofst = stp->iTknOfst;
+    if( ofst==NO_OFFSET ) ofst = lemp->nactiontab;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", ofst);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the yy_reduce_ofst[] table */
+  n = lemp->nxstate;
+  while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--;
+  fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++;
+  fprintf(out, "#define YY_REDUCE_MIN   (%d)\n", mnNtOfst); lineno++;
+  fprintf(out, "#define YY_REDUCE_MAX   (%d)\n", mxNtOfst); lineno++;
+  fprintf(out, "static const %s yy_reduce_ofst[] = {\n",
+          minimum_size_type(mnNtOfst-1, mxNtOfst, &sz)); lineno++;
+  lemp->tablesize += n*sz;
+  for(i=j=0; i<n; i++){
+    int ofst;
+    stp = lemp->sorted[i];
+    ofst = stp->iNtOfst;
+    if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1;
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    fprintf(out, " %4d,", ofst);
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+
+  /* Output the default action table */
+  fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++;
+  n = lemp->nxstate;
+  lemp->tablesize += n*szActionType;
+  for(i=j=0; i<n; i++){
+    stp = lemp->sorted[i];
+    if( j==0 ) fprintf(out," /* %5d */ ", i);
+    if( stp->iDfltReduce<0 ){
+      fprintf(out, " %4d,", lemp->errAction);
+    }else{
+      fprintf(out, " %4d,", stp->iDfltReduce + lemp->minReduce);
+    }
+    if( j==9 || i==n-1 ){
+      fprintf(out, "\n"); lineno++;
+      j = 0;
+    }else{
+      j++;
+    }
+  }
+  fprintf(out, "};\n"); lineno++;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the table of fallback tokens.
+  */
+  if( lemp->has_fallback ){
+    int mx = lemp->nterminal - 1;
+    /* 2019-08-28:  Generate fallback entries for every token to avoid
+    ** having to do a range check on the index */
+    /* while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } */
+    lemp->tablesize += (mx+1)*szCodeType;
+    for(i=0; i<=mx; i++){
+      struct symbol *p = lemp->symbols[i];
+      if( p->fallback==0 ){
+        fprintf(out, "    0,  /* %10s => nothing */\n", p->name);
+      }else{
+        fprintf(out, "  %3d,  /* %10s => %s */\n", p->fallback->index,
+          p->name, p->fallback->name);
+      }
+      lineno++;
+    }
+  }
+  tplt_xfer(lemp->name, in, out, &lineno);
+
+  /* Generate a table containing the symbolic name of every symbol
+  */
+  for(i=0; i<lemp->nsymbol; i++){
+    fprintf(out,"  /* %4d */ \"%s\",\n",i, lemp->symbols[i]->name); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate a table containing a text string that describes every
+  ** rule in the rule set of the grammar.  This information is used
+  ** when tracing REDUCE actions.
+  */
+  for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+    assert( rp->iRule==i );
+    fprintf(out," /* %3d */ \"", i);
+    writeRuleText(out, rp);
+    fprintf(out,"\",\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes every time a symbol is popped from
+  ** the stack while processing errors or while destroying the parser.
+  ** (In other words, generate the %destructor actions)
+  */
+  if( lemp->tokendest ){
+    int once = 1;
+    for(i=0; i<lemp->nsymbol; i++){
+      struct symbol *sp = lemp->symbols[i];
+      if( sp==0 || sp->type!=TERMINAL ) continue;
+      if( once ){
+        fprintf(out, "      /* TERMINAL Destructor */\n"); lineno++;
+        once = 0;
+      }
+      fprintf(out,"    case %d: /* %s */\n", sp->index, sp->name); lineno++;
+    }
+    for(i=0; i<lemp->nsymbol && lemp->symbols[i]->type!=TERMINAL; i++);
+    if( i<lemp->nsymbol ){
+      emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+      fprintf(out,"      break;\n"); lineno++;
+    }
+  }
+  if( lemp->vardest ){
+    struct symbol *dflt_sp = 0;
+    int once = 1;
+    for(i=0; i<lemp->nsymbol; i++){
+      struct symbol *sp = lemp->symbols[i];
+      if( sp==0 || sp->type==TERMINAL ||
+          sp->index<=0 || sp->destructor!=0 ) continue;
+      if( once ){
+        fprintf(out, "      /* Default NON-TERMINAL Destructor */\n");lineno++;
+        once = 0;
+      }
+      fprintf(out,"    case %d: /* %s */\n", sp->index, sp->name); lineno++;
+      dflt_sp = sp;
+    }
+    if( dflt_sp!=0 ){
+      emit_destructor_code(out,dflt_sp,lemp,&lineno);
+    }
+    fprintf(out,"      break;\n"); lineno++;
+  }
+  for(i=0; i<lemp->nsymbol; i++){
+    struct symbol *sp = lemp->symbols[i];
+    if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue;
+    if( sp->destLineno<0 ) continue;  /* Already emitted */
+    fprintf(out,"    case %d: /* %s */\n", sp->index, sp->name); lineno++;
+
+    /* Combine duplicate destructors into a single case */
+    for(j=i+1; j<lemp->nsymbol; j++){
+      struct symbol *sp2 = lemp->symbols[j];
+      if( sp2 && sp2->type!=TERMINAL && sp2->destructor
+          && sp2->dtnum==sp->dtnum
+          && strcmp(sp->destructor,sp2->destructor)==0 ){
+         fprintf(out,"    case %d: /* %s */\n",
+                 sp2->index, sp2->name); lineno++;
+         sp2->destLineno = -1;  /* Avoid emitting this destructor again */
+      }
+    }
+
+    emit_destructor_code(out,lemp->symbols[i],lemp,&lineno);
+    fprintf(out,"      break;\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes whenever the parser stack overflows */
+  tplt_print(out,lemp,lemp->overflow,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate the tables of rule information.  yyRuleInfoLhs[] and
+  ** yyRuleInfoNRhs[].
+  **
+  ** Note: This code depends on the fact that rules are number
+  ** sequentially beginning with 0.
+  */
+  for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+    fprintf(out,"  %4d,  /* (%d) ", rp->lhs->index, i);
+     rule_print(out, rp);
+    fprintf(out," */\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+  for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){
+    fprintf(out,"  %3d,  /* (%d) ", -rp->nrhs, i);
+    rule_print(out, rp);
+    fprintf(out," */\n"); lineno++;
+  }
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which execution during each REDUCE action */
+  i = 0;
+  for(rp=lemp->rule; rp; rp=rp->next){
+    i += translate_code(lemp, rp);
+  }
+  if( i ){
+    fprintf(out,"        YYMINORTYPE yylhsminor;\n"); lineno++;
+  }
+  /* First output rules other than the default: rule */
+  for(rp=lemp->rule; rp; rp=rp->next){
+    struct rule *rp2;               /* Other rules with the same action */
+    if( rp->codeEmitted ) continue;
+    if( rp->noCode ){
+      /* No C code actions, so this will be part of the "default:" rule */
+      continue;
+    }
+    fprintf(out,"      case %d: /* ", rp->iRule);
+    writeRuleText(out, rp);
+    fprintf(out, " */\n"); lineno++;
+    for(rp2=rp->next; rp2; rp2=rp2->next){
+      if( rp2->code==rp->code && rp2->codePrefix==rp->codePrefix
+             && rp2->codeSuffix==rp->codeSuffix ){
+        fprintf(out,"      case %d: /* ", rp2->iRule);
+        writeRuleText(out, rp2);
+        fprintf(out," */ yytestcase(yyruleno==%d);\n", rp2->iRule); lineno++;
+        rp2->codeEmitted = 1;
+      }
+    }
+    emit_code(out,rp,lemp,&lineno);
+    fprintf(out,"        break;\n"); lineno++;
+    rp->codeEmitted = 1;
+  }
+  /* Finally, output the default: rule.  We choose as the default: all
+  ** empty actions. */
+  fprintf(out,"      default:\n"); lineno++;
+  for(rp=lemp->rule; rp; rp=rp->next){
+    if( rp->codeEmitted ) continue;
+    assert( rp->noCode );
+    fprintf(out,"      /* (%d) ", rp->iRule);
+    writeRuleText(out, rp);
+    if( rp->neverReduce ){
+      fprintf(out, " (NEVER REDUCES) */ assert(yyruleno!=%d);\n",
+              rp->iRule); lineno++;
+    }else if( rp->doesReduce ){
+      fprintf(out, " */ yytestcase(yyruleno==%d);\n", rp->iRule); lineno++;
+    }else{
+      fprintf(out, " (OPTIMIZED OUT) */ assert(yyruleno!=%d);\n",
+              rp->iRule); lineno++;
+    }
+  }
+  fprintf(out,"        break;\n"); lineno++;
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes if a parse fails */
+  tplt_print(out,lemp,lemp->failure,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes when a syntax error occurs */
+  tplt_print(out,lemp,lemp->error,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Generate code which executes when the parser accepts its input */
+  tplt_print(out,lemp,lemp->accept,&lineno);
+  tplt_xfer(lemp->name,in,out,&lineno);
+
+  /* Append any addition code the user desires */
+  tplt_print(out,lemp,lemp->extracode,&lineno);
+
+  acttab_free(pActtab);
+  fclose(in);
+  fclose(out);
+  if( sql ) fclose(sql);
+  return;
+}
+
+/* Generate a header file for the parser */
+void ReportHeader(struct lemon *lemp)
+{
+  FILE *out, *in;
+  const char *prefix;
+  char line[LINESIZE];
+  char pattern[LINESIZE];
+  int i;
+
+  if( lemp->tokenprefix ) prefix = lemp->tokenprefix;
+  else                    prefix = "";
+  in = file_open(lemp,".h","rb");
+  if( in ){
+    int nextChar;
+    for(i=1; i<lemp->nterminal && fgets(line,LINESIZE,in); i++){
+      lemon_sprintf(pattern,"#define %s%-30s %3d\n",
+                    prefix,lemp->symbols[i]->name,i);
+      if( strcmp(line,pattern) ) break;
+    }
+    nextChar = fgetc(in);
+    fclose(in);
+    if( i==lemp->nterminal && nextChar==EOF ){
+      /* No change in the file.  Don't rewrite it. */
+      return;
+    }
+  }
+  out = file_open(lemp,".h","wb");
+  if( out ){
+    for(i=1; i<lemp->nterminal; i++){
+      fprintf(out,"#define %s%-30s %3d\n",prefix,lemp->symbols[i]->name,i);
+    }
+    fclose(out);
+  }
+  return;
+}
+
+/* Reduce the size of the action tables, if possible, by making use
+** of defaults.
+**
+** In this version, we take the most frequent REDUCE action and make
+** it the default.  Except, there is no default if the wildcard token
+** is a possible look-ahead.
+*/
+void CompressTables(struct lemon *lemp)
+{
+  struct state *stp;
+  struct action *ap, *ap2, *nextap;
+  struct rule *rp, *rp2, *rbest;
+  int nbest, n;
+  int i;
+  int usesWildcard;
+
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    nbest = 0;
+    rbest = 0;
+    usesWildcard = 0;
+
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( ap->type==SHIFT && ap->sp==lemp->wildcard ){
+        usesWildcard = 1;
+      }
+      if( ap->type!=REDUCE ) continue;
+      rp = ap->x.rp;
+      if( rp->lhsStart ) continue;
+      if( rp==rbest ) continue;
+      n = 1;
+      for(ap2=ap->next; ap2; ap2=ap2->next){
+        if( ap2->type!=REDUCE ) continue;
+        rp2 = ap2->x.rp;
+        if( rp2==rbest ) continue;
+        if( rp2==rp ) n++;
+      }
+      if( n>nbest ){
+        nbest = n;
+        rbest = rp;
+      }
+    }
+
+    /* Do not make a default if the number of rules to default
+    ** is not at least 1 or if the wildcard token is a possible
+    ** lookahead.
+    */
+    if( nbest<1 || usesWildcard ) continue;
+
+
+    /* Combine matching REDUCE actions into a single default */
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( ap->type==REDUCE && ap->x.rp==rbest ) break;
+    }
+    assert( ap );
+    ap->sp = Symbol_new("{default}");
+    for(ap=ap->next; ap; ap=ap->next){
+      if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED;
+    }
+    stp->ap = Action_sort(stp->ap);
+
+    for(ap=stp->ap; ap; ap=ap->next){
+      if( ap->type==SHIFT ) break;
+      if( ap->type==REDUCE && ap->x.rp!=rbest ) break;
+    }
+    if( ap==0 ){
+      stp->autoReduce = 1;
+      stp->pDfltReduce = rbest;
+    }
+  }
+
+  /* Make a second pass over all states and actions.  Convert
+  ** every action that is a SHIFT to an autoReduce state into
+  ** a SHIFTREDUCE action.
+  */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(ap=stp->ap; ap; ap=ap->next){
+      struct state *pNextState;
+      if( ap->type!=SHIFT ) continue;
+      pNextState = ap->x.stp;
+      if( pNextState->autoReduce && pNextState->pDfltReduce!=0 ){
+        ap->type = SHIFTREDUCE;
+        ap->x.rp = pNextState->pDfltReduce;
+      }
+    }
+  }
+
+  /* If a SHIFTREDUCE action specifies a rule that has a single RHS term
+  ** (meaning that the SHIFTREDUCE will land back in the state where it
+  ** started) and if there is no C-code associated with the reduce action,
+  ** then we can go ahead and convert the action to be the same as the
+  ** action for the RHS of the rule.
+  */
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    for(ap=stp->ap; ap; ap=nextap){
+      nextap = ap->next;
+      if( ap->type!=SHIFTREDUCE ) continue;
+      rp = ap->x.rp;
+      if( rp->noCode==0 ) continue;
+      if( rp->nrhs!=1 ) continue;
+#if 1
+      /* Only apply this optimization to non-terminals.  It would be OK to
+      ** apply it to terminal symbols too, but that makes the parser tables
+      ** larger. */
+      if( ap->sp->index<lemp->nterminal ) continue;
+#endif
+      /* If we reach this point, it means the optimization can be applied */
+      nextap = ap;
+      for(ap2=stp->ap; ap2 && (ap2==ap || ap2->sp!=rp->lhs); ap2=ap2->next){}
+      assert( ap2!=0 );
+      ap->spOpt = ap2->sp;
+      ap->type = ap2->type;
+      ap->x = ap2->x;
+    }
+  }
+}
+
+
+/*
+** Compare two states for sorting purposes.  The smaller state is the
+** one with the most non-terminal actions.  If they have the same number
+** of non-terminal actions, then the smaller is the one with the most
+** token actions.
+*/
+static int stateResortCompare(const void *a, const void *b){
+  const struct state *pA = *(const struct state**)a;
+  const struct state *pB = *(const struct state**)b;
+  int n;
+
+  n = pB->nNtAct - pA->nNtAct;
+  if( n==0 ){
+    n = pB->nTknAct - pA->nTknAct;
+    if( n==0 ){
+      n = pB->statenum - pA->statenum;
+    }
+  }
+  assert( n!=0 );
+  return n;
+}
+
+
+/*
+** Renumber and resort states so that states with fewer choices
+** occur at the end.  Except, keep state 0 as the first state.
+*/
+void ResortStates(struct lemon *lemp)
+{
+  int i;
+  struct state *stp;
+  struct action *ap;
+
+  for(i=0; i<lemp->nstate; i++){
+    stp = lemp->sorted[i];
+    stp->nTknAct = stp->nNtAct = 0;
+    stp->iDfltReduce = -1; /* Init dflt action to "syntax error" */
+    stp->iTknOfst = NO_OFFSET;
+    stp->iNtOfst = NO_OFFSET;
+    for(ap=stp->ap; ap; ap=ap->next){
+      int iAction = compute_action(lemp,ap);
+      if( iAction>=0 ){
+        if( ap->sp->index<lemp->nterminal ){
+          stp->nTknAct++;
+        }else if( ap->sp->index<lemp->nsymbol ){
+          stp->nNtAct++;
+        }else{
+          assert( stp->autoReduce==0 || stp->pDfltReduce==ap->x.rp );
+          stp->iDfltReduce = iAction;
+        }
+      }
+    }
+  }
+  qsort(&lemp->sorted[1], lemp->nstate-1, sizeof(lemp->sorted[0]),
+        stateResortCompare);
+  for(i=0; i<lemp->nstate; i++){
+    lemp->sorted[i]->statenum = i;
+  }
+  lemp->nxstate = lemp->nstate;
+  while( lemp->nxstate>1 && lemp->sorted[lemp->nxstate-1]->autoReduce ){
+    lemp->nxstate--;
+  }
+}
+
+
+/***************** From the file "set.c" ************************************/
+/*
+** Set manipulation routines for the LEMON parser generator.
+*/
+
+static int size = 0;
+
+/* Set the set size */
+void SetSize(int n)
+{
+  size = n+1;
+}
+
+/* Allocate a new set */
+char *SetNew(void){
+  char *s;
+  s = (char*)calloc( size, 1);
+  if( s==0 ){
+    memory_error();
+  }
+  return s;
+}
+
+/* Deallocate a set */
+void SetFree(char *s)
+{
+  free(s);
+}
+
+/* Add a new element to the set.  Return TRUE if the element was added
+** and FALSE if it was already there. */
+int SetAdd(char *s, int e)
+{
+  int rv;
+  assert( e>=0 && e<size );
+  rv = s[e];
+  s[e] = 1;
+  return !rv;
+}
+
+/* Add every element of s2 to s1.  Return TRUE if s1 changes. */
+int SetUnion(char *s1, char *s2)
+{
+  int i, progress;
+  progress = 0;
+  for(i=0; i<size; i++){
+    if( s2[i]==0 ) continue;
+    if( s1[i]==0 ){
+      progress = 1;
+      s1[i] = 1;
+    }
+  }
+  return progress;
+}
+/********************** From the file "table.c" ****************************/
+/*
+** All code in this file has been automatically generated
+** from a specification in the file
+**              "table.q"
+** by the associative array code building program "aagen".
+** Do not edit this file!  Instead, edit the specification
+** file, then rerun aagen.
+*/
+/*
+** Code for processing tables in the LEMON parser generator.
+*/
+
+PRIVATE unsigned strhash(const char *x)
+{
+  unsigned h = 0;
+  while( *x ) h = h*13 + *(x++);
+  return h;
+}
+
+/* Works like strdup, sort of.  Save a string in malloced memory, but
+** keep strings in a table so that the same string is not in more
+** than one place.
+*/
+const char *Strsafe(const char *y)
+{
+  const char *z;
+  char *cpy;
+
+  if( y==0 ) return 0;
+  z = Strsafe_find(y);
+  if( z==0 && (cpy=(char *)malloc( lemonStrlen(y)+1 ))!=0 ){
+    lemon_strcpy(cpy,y);
+    z = cpy;
+    Strsafe_insert(z);
+  }
+  MemoryCheck(z);
+  return z;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x1".
+*/
+struct s_x1 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x1node *tbl;  /* The data stored here */
+  struct s_x1node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x1".
+*/
+typedef struct s_x1node {
+  const char *data;        /* The data */
+  struct s_x1node *next;   /* Next entry with the same hash */
+  struct s_x1node **from;  /* Previous link */
+} x1node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x1 *x1a;
+
+/* Allocate a new associative array */
+void Strsafe_init(void){
+  if( x1a ) return;
+  x1a = (struct s_x1*)malloc( sizeof(struct s_x1) );
+  if( x1a ){
+    x1a->size = 1024;
+    x1a->count = 0;
+    x1a->tbl = (x1node*)calloc(1024, sizeof(x1node) + sizeof(x1node*));
+    if( x1a->tbl==0 ){
+      free(x1a);
+      x1a = 0;
+    }else{
+      int i;
+      x1a->ht = (x1node**)&(x1a->tbl[1024]);
+      for(i=0; i<1024; i++) x1a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Strsafe_insert(const char *data)
+{
+  x1node *np;
+  unsigned h;
+  unsigned ph;
+
+  if( x1a==0 ) return 0;
+  ph = strhash(data);
+  h = ph & (x1a->size-1);
+  np = x1a->ht[h];
+  while( np ){
+    if( strcmp(np->data,data)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x1a->count>=x1a->size ){
+    /* Need to make the hash table bigger */
+    int i,arrSize;
+    struct s_x1 array;
+    array.size = arrSize = x1a->size*2;
+    array.count = x1a->count;
+    array.tbl = (x1node*)calloc(arrSize, sizeof(x1node) + sizeof(x1node*));
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x1node**)&(array.tbl[arrSize]);
+    for(i=0; i<arrSize; i++) array.ht[i] = 0;
+    for(i=0; i<x1a->count; i++){
+      x1node *oldnp, *newnp;
+      oldnp = &(x1a->tbl[i]);
+      h = strhash(oldnp->data) & (arrSize-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    /* free(x1a->tbl); // This program was originally for 16-bit machines.
+    ** Don't worry about freeing memory on modern platforms. */
+    *x1a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x1a->size-1);
+  np = &(x1a->tbl[x1a->count++]);
+  np->data = data;
+  if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next);
+  np->next = x1a->ht[h];
+  x1a->ht[h] = np;
+  np->from = &(x1a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+const char *Strsafe_find(const char *key)
+{
+  unsigned h;
+  x1node *np;
+
+  if( x1a==0 ) return 0;
+  h = strhash(key) & (x1a->size-1);
+  np = x1a->ht[h];
+  while( np ){
+    if( strcmp(np->data,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return a pointer to the (terminal or nonterminal) symbol "x".
+** Create a new symbol if this is the first time "x" has been seen.
+*/
+struct symbol *Symbol_new(const char *x)
+{
+  struct symbol *sp;
+
+  sp = Symbol_find(x);
+  if( sp==0 ){
+    sp = (struct symbol *)calloc(1, sizeof(struct symbol) );
+    MemoryCheck(sp);
+    sp->name = Strsafe(x);
+    sp->type = ISUPPER(*x) ? TERMINAL : NONTERMINAL;
+    sp->rule = 0;
+    sp->fallback = 0;
+    sp->prec = -1;
+    sp->assoc = UNK;
+    sp->firstset = 0;
+    sp->lambda = LEMON_FALSE;
+    sp->destructor = 0;
+    sp->destLineno = 0;
+    sp->datatype = 0;
+    sp->useCnt = 0;
+    Symbol_insert(sp,sp->name);
+  }
+  sp->useCnt++;
+  return sp;
+}
+
+/* Compare two symbols for sorting purposes.  Return negative,
+** zero, or positive if a is less then, equal to, or greater
+** than b.
+**
+** Symbols that begin with upper case letters (terminals or tokens)
+** must sort before symbols that begin with lower case letters
+** (non-terminals).  And MULTITERMINAL symbols (created using the
+** %token_class directive) must sort at the very end. Other than
+** that, the order does not matter.
+**
+** We find experimentally that leaving the symbols in their original
+** order (the order they appeared in the grammar file) gives the
+** smallest parser tables in SQLite.
+*/
+int Symbolcmpp(const void *_a, const void *_b)
+{
+  const struct symbol *a = *(const struct symbol **) _a;
+  const struct symbol *b = *(const struct symbol **) _b;
+  int i1 = a->type==MULTITERMINAL ? 3 : a->name[0]>'Z' ? 2 : 1;
+  int i2 = b->type==MULTITERMINAL ? 3 : b->name[0]>'Z' ? 2 : 1;
+  return i1==i2 ? a->index - b->index : i1 - i2;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x2".
+*/
+struct s_x2 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x2node *tbl;  /* The data stored here */
+  struct s_x2node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x2".
+*/
+typedef struct s_x2node {
+  struct symbol *data;     /* The data */
+  const char *key;         /* The key */
+  struct s_x2node *next;   /* Next entry with the same hash */
+  struct s_x2node **from;  /* Previous link */
+} x2node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x2 *x2a;
+
+/* Allocate a new associative array */
+void Symbol_init(void){
+  if( x2a ) return;
+  x2a = (struct s_x2*)malloc( sizeof(struct s_x2) );
+  if( x2a ){
+    x2a->size = 128;
+    x2a->count = 0;
+    x2a->tbl = (x2node*)calloc(128, sizeof(x2node) + sizeof(x2node*));
+    if( x2a->tbl==0 ){
+      free(x2a);
+      x2a = 0;
+    }else{
+      int i;
+      x2a->ht = (x2node**)&(x2a->tbl[128]);
+      for(i=0; i<128; i++) x2a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Symbol_insert(struct symbol *data, const char *key)
+{
+  x2node *np;
+  unsigned h;
+  unsigned ph;
+
+  if( x2a==0 ) return 0;
+  ph = strhash(key);
+  h = ph & (x2a->size-1);
+  np = x2a->ht[h];
+  while( np ){
+    if( strcmp(np->key,key)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x2a->count>=x2a->size ){
+    /* Need to make the hash table bigger */
+    int i,arrSize;
+    struct s_x2 array;
+    array.size = arrSize = x2a->size*2;
+    array.count = x2a->count;
+    array.tbl = (x2node*)calloc(arrSize, sizeof(x2node) + sizeof(x2node*));
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x2node**)&(array.tbl[arrSize]);
+    for(i=0; i<arrSize; i++) array.ht[i] = 0;
+    for(i=0; i<x2a->count; i++){
+      x2node *oldnp, *newnp;
+      oldnp = &(x2a->tbl[i]);
+      h = strhash(oldnp->key) & (arrSize-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->key = oldnp->key;
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    /* free(x2a->tbl); // This program was originally written for 16-bit
+    ** machines.  Don't worry about freeing this trivial amount of memory
+    ** on modern platforms.  Just leak it. */
+    *x2a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x2a->size-1);
+  np = &(x2a->tbl[x2a->count++]);
+  np->key = key;
+  np->data = data;
+  if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next);
+  np->next = x2a->ht[h];
+  x2a->ht[h] = np;
+  np->from = &(x2a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct symbol *Symbol_find(const char *key)
+{
+  unsigned h;
+  x2node *np;
+
+  if( x2a==0 ) return 0;
+  h = strhash(key) & (x2a->size-1);
+  np = x2a->ht[h];
+  while( np ){
+    if( strcmp(np->key,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return the n-th data.  Return NULL if n is out of range. */
+struct symbol *Symbol_Nth(int n)
+{
+  struct symbol *data;
+  if( x2a && n>0 && n<=x2a->count ){
+    data = x2a->tbl[n-1].data;
+  }else{
+    data = 0;
+  }
+  return data;
+}
+
+/* Return the size of the array */
+int Symbol_count()
+{
+  return x2a ? x2a->count : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc.  Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct symbol **Symbol_arrayof()
+{
+  struct symbol **array;
+  int i,arrSize;
+  if( x2a==0 ) return 0;
+  arrSize = x2a->count;
+  array = (struct symbol **)calloc(arrSize, sizeof(struct symbol *));
+  if( array ){
+    for(i=0; i<arrSize; i++) array[i] = x2a->tbl[i].data;
+  }
+  return array;
+}
+
+/* Compare two configurations */
+int Configcmp(const char *_a,const char *_b)
+{
+  const struct config *a = (struct config *) _a;
+  const struct config *b = (struct config *) _b;
+  int x;
+  x = a->rp->index - b->rp->index;
+  if( x==0 ) x = a->dot - b->dot;
+  return x;
+}
+
+/* Compare two states */
+PRIVATE int statecmp(struct config *a, struct config *b)
+{
+  int rc;
+  for(rc=0; rc==0 && a && b;  a=a->bp, b=b->bp){
+    rc = a->rp->index - b->rp->index;
+    if( rc==0 ) rc = a->dot - b->dot;
+  }
+  if( rc==0 ){
+    if( a ) rc = 1;
+    if( b ) rc = -1;
+  }
+  return rc;
+}
+
+/* Hash a state */
+PRIVATE unsigned statehash(struct config *a)
+{
+  unsigned h=0;
+  while( a ){
+    h = h*571 + a->rp->index*37 + a->dot;
+    a = a->bp;
+  }
+  return h;
+}
+
+/* Allocate a new state structure */
+struct state *State_new()
+{
+  struct state *newstate;
+  newstate = (struct state *)calloc(1, sizeof(struct state) );
+  MemoryCheck(newstate);
+  return newstate;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x3".
+*/
+struct s_x3 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x3node *tbl;  /* The data stored here */
+  struct s_x3node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x3".
+*/
+typedef struct s_x3node {
+  struct state *data;                  /* The data */
+  struct config *key;                   /* The key */
+  struct s_x3node *next;   /* Next entry with the same hash */
+  struct s_x3node **from;  /* Previous link */
+} x3node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x3 *x3a;
+
+/* Allocate a new associative array */
+void State_init(void){
+  if( x3a ) return;
+  x3a = (struct s_x3*)malloc( sizeof(struct s_x3) );
+  if( x3a ){
+    x3a->size = 128;
+    x3a->count = 0;
+    x3a->tbl = (x3node*)calloc(128, sizeof(x3node) + sizeof(x3node*));
+    if( x3a->tbl==0 ){
+      free(x3a);
+      x3a = 0;
+    }else{
+      int i;
+      x3a->ht = (x3node**)&(x3a->tbl[128]);
+      for(i=0; i<128; i++) x3a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int State_insert(struct state *data, struct config *key)
+{
+  x3node *np;
+  unsigned h;
+  unsigned ph;
+
+  if( x3a==0 ) return 0;
+  ph = statehash(key);
+  h = ph & (x3a->size-1);
+  np = x3a->ht[h];
+  while( np ){
+    if( statecmp(np->key,key)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x3a->count>=x3a->size ){
+    /* Need to make the hash table bigger */
+    int i,arrSize;
+    struct s_x3 array;
+    array.size = arrSize = x3a->size*2;
+    array.count = x3a->count;
+    array.tbl = (x3node*)calloc(arrSize, sizeof(x3node) + sizeof(x3node*));
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x3node**)&(array.tbl[arrSize]);
+    for(i=0; i<arrSize; i++) array.ht[i] = 0;
+    for(i=0; i<x3a->count; i++){
+      x3node *oldnp, *newnp;
+      oldnp = &(x3a->tbl[i]);
+      h = statehash(oldnp->key) & (arrSize-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->key = oldnp->key;
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    free(x3a->tbl);
+    *x3a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x3a->size-1);
+  np = &(x3a->tbl[x3a->count++]);
+  np->key = key;
+  np->data = data;
+  if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next);
+  np->next = x3a->ht[h];
+  x3a->ht[h] = np;
+  np->from = &(x3a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct state *State_find(struct config *key)
+{
+  unsigned h;
+  x3node *np;
+
+  if( x3a==0 ) return 0;
+  h = statehash(key) & (x3a->size-1);
+  np = x3a->ht[h];
+  while( np ){
+    if( statecmp(np->key,key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Return an array of pointers to all data in the table.
+** The array is obtained from malloc.  Return NULL if memory allocation
+** problems, or if the array is empty. */
+struct state **State_arrayof(void)
+{
+  struct state **array;
+  int i,arrSize;
+  if( x3a==0 ) return 0;
+  arrSize = x3a->count;
+  array = (struct state **)calloc(arrSize, sizeof(struct state *));
+  if( array ){
+    for(i=0; i<arrSize; i++) array[i] = x3a->tbl[i].data;
+  }
+  return array;
+}
+
+/* Hash a configuration */
+PRIVATE unsigned confighash(struct config *a)
+{
+  unsigned h=0;
+  h = h*571 + a->rp->index*37 + a->dot;
+  return h;
+}
+
+/* There is one instance of the following structure for each
+** associative array of type "x4".
+*/
+struct s_x4 {
+  int size;               /* The number of available slots. */
+                          /*   Must be a power of 2 greater than or */
+                          /*   equal to 1 */
+  int count;              /* Number of currently slots filled */
+  struct s_x4node *tbl;  /* The data stored here */
+  struct s_x4node **ht;  /* Hash table for lookups */
+};
+
+/* There is one instance of this structure for every data element
+** in an associative array of type "x4".
+*/
+typedef struct s_x4node {
+  struct config *data;                  /* The data */
+  struct s_x4node *next;   /* Next entry with the same hash */
+  struct s_x4node **from;  /* Previous link */
+} x4node;
+
+/* There is only one instance of the array, which is the following */
+static struct s_x4 *x4a;
+
+/* Allocate a new associative array */
+void Configtable_init(void){
+  if( x4a ) return;
+  x4a = (struct s_x4*)malloc( sizeof(struct s_x4) );
+  if( x4a ){
+    x4a->size = 64;
+    x4a->count = 0;
+    x4a->tbl = (x4node*)calloc(64, sizeof(x4node) + sizeof(x4node*));
+    if( x4a->tbl==0 ){
+      free(x4a);
+      x4a = 0;
+    }else{
+      int i;
+      x4a->ht = (x4node**)&(x4a->tbl[64]);
+      for(i=0; i<64; i++) x4a->ht[i] = 0;
+    }
+  }
+}
+/* Insert a new record into the array.  Return TRUE if successful.
+** Prior data with the same key is NOT overwritten */
+int Configtable_insert(struct config *data)
+{
+  x4node *np;
+  unsigned h;
+  unsigned ph;
+
+  if( x4a==0 ) return 0;
+  ph = confighash(data);
+  h = ph & (x4a->size-1);
+  np = x4a->ht[h];
+  while( np ){
+    if( Configcmp((const char *) np->data,(const char *) data)==0 ){
+      /* An existing entry with the same key is found. */
+      /* Fail because overwrite is not allows. */
+      return 0;
+    }
+    np = np->next;
+  }
+  if( x4a->count>=x4a->size ){
+    /* Need to make the hash table bigger */
+    int i,arrSize;
+    struct s_x4 array;
+    array.size = arrSize = x4a->size*2;
+    array.count = x4a->count;
+    array.tbl = (x4node*)calloc(arrSize, sizeof(x4node) + sizeof(x4node*));
+    if( array.tbl==0 ) return 0;  /* Fail due to malloc failure */
+    array.ht = (x4node**)&(array.tbl[arrSize]);
+    for(i=0; i<arrSize; i++) array.ht[i] = 0;
+    for(i=0; i<x4a->count; i++){
+      x4node *oldnp, *newnp;
+      oldnp = &(x4a->tbl[i]);
+      h = confighash(oldnp->data) & (arrSize-1);
+      newnp = &(array.tbl[i]);
+      if( array.ht[h] ) array.ht[h]->from = &(newnp->next);
+      newnp->next = array.ht[h];
+      newnp->data = oldnp->data;
+      newnp->from = &(array.ht[h]);
+      array.ht[h] = newnp;
+    }
+    /* free(x4a->tbl); // This code was originall written for 16-bit machines.
+    ** on modern machines, don't worry about freeing this trival amount of
+    ** memory. */
+    *x4a = array;
+  }
+  /* Insert the new data */
+  h = ph & (x4a->size-1);
+  np = &(x4a->tbl[x4a->count++]);
+  np->data = data;
+  if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next);
+  np->next = x4a->ht[h];
+  x4a->ht[h] = np;
+  np->from = &(x4a->ht[h]);
+  return 1;
+}
+
+/* Return a pointer to data assigned to the given key.  Return NULL
+** if no such key. */
+struct config *Configtable_find(struct config *key)
+{
+  int h;
+  x4node *np;
+
+  if( x4a==0 ) return 0;
+  h = confighash(key) & (x4a->size-1);
+  np = x4a->ht[h];
+  while( np ){
+    if( Configcmp((const char *) np->data,(const char *) key)==0 ) break;
+    np = np->next;
+  }
+  return np ? np->data : 0;
+}
+
+/* Remove all data from the table.  Pass each data to the function "f"
+** as it is removed.  ("f" may be null to avoid this step.) */
+void Configtable_clear(int(*f)(struct config *))
+{
+  int i;
+  if( x4a==0 || x4a->count==0 ) return;
+  if( f ) for(i=0; i<x4a->count; i++) (*f)(x4a->tbl[i].data);
+  for(i=0; i<x4a->size; i++) x4a->ht[i] = 0;
+  x4a->count = 0;
+  return;
+}

+ 1068 - 0
pikchr.mod/pikchr/lempar.c

@@ -0,0 +1,1068 @@
+/*
+** 2000-05-29
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+** Driver template for the LEMON parser generator.
+**
+** The "lemon" program processes an LALR(1) input grammar file, then uses
+** this template to construct a parser.  The "lemon" program inserts text
+** at each "%%" line.  Also, any "P-a-r-s-e" identifer prefix (without the
+** interstitial "-" characters) contained in this template is changed into
+** the value of the %name directive from the grammar.  Otherwise, the content
+** of this template is copied straight through into the generate parser
+** source file.
+**
+** The following is the concatenation of all %include directives from the
+** input grammar file:
+*/
+/************ Begin %include sections from the grammar ************************/
+%%
+/**************** End of %include directives **********************************/
+/* These constants specify the various numeric values for terminal symbols.
+***************** Begin token definitions *************************************/
+%%
+/**************** End token definitions ***************************************/
+
+/* The next sections is a series of control #defines.
+** various aspects of the generated parser.
+**    YYCODETYPE         is the data type used to store the integer codes
+**                       that represent terminal and non-terminal symbols.
+**                       "unsigned char" is used if there are fewer than
+**                       256 symbols.  Larger types otherwise.
+**    YYNOCODE           is a number of type YYCODETYPE that is not used for
+**                       any terminal or nonterminal symbol.
+**    YYFALLBACK         If defined, this indicates that one or more tokens
+**                       (also known as: "terminal symbols") have fall-back
+**                       values which should be used if the original symbol
+**                       would not parse.  This permits keywords to sometimes
+**                       be used as identifiers, for example.
+**    YYACTIONTYPE       is the data type used for "action codes" - numbers
+**                       that indicate what to do in response to the next
+**                       token.
+**    ParseTOKENTYPE     is the data type used for minor type for terminal
+**                       symbols.  Background: A "minor type" is a semantic
+**                       value associated with a terminal or non-terminal
+**                       symbols.  For example, for an "ID" terminal symbol,
+**                       the minor type might be the name of the identifier.
+**                       Each non-terminal can have a different minor type.
+**                       Terminal symbols all have the same minor type, though.
+**                       This macros defines the minor type for terminal 
+**                       symbols.
+**    YYMINORTYPE        is the data type used for all minor types.
+**                       This is typically a union of many types, one of
+**                       which is ParseTOKENTYPE.  The entry in the union
+**                       for terminal symbols is called "yy0".
+**    YYSTACKDEPTH       is the maximum depth of the parser's stack.  If
+**                       zero the stack is dynamically sized using realloc()
+**    ParseARG_SDECL     A static variable declaration for the %extra_argument
+**    ParseARG_PDECL     A parameter declaration for the %extra_argument
+**    ParseARG_PARAM     Code to pass %extra_argument as a subroutine parameter
+**    ParseARG_STORE     Code to store %extra_argument into yypParser
+**    ParseARG_FETCH     Code to extract %extra_argument from yypParser
+**    ParseCTX_*         As ParseARG_ except for %extra_context
+**    YYERRORSYMBOL      is the code number of the error symbol.  If not
+**                       defined, then do no error processing.
+**    YYNSTATE           the combined number of states.
+**    YYNRULE            the number of rules in the grammar
+**    YYNTOKEN           Number of terminal symbols
+**    YY_MAX_SHIFT       Maximum value for shift actions
+**    YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
+**    YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
+**    YY_ERROR_ACTION    The yy_action[] code for syntax error
+**    YY_ACCEPT_ACTION   The yy_action[] code for accept
+**    YY_NO_ACTION       The yy_action[] code for no-op
+**    YY_MIN_REDUCE      Minimum value for reduce actions
+**    YY_MAX_REDUCE      Maximum value for reduce actions
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/************* Begin control #defines *****************************************/
+%%
+/************* End control #defines *******************************************/
+#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])))
+
+/* Define the yytestcase() macro to be a no-op if is not already defined
+** otherwise.
+**
+** Applications can choose to define yytestcase() in the %include section
+** to a macro that can assist in verifying code coverage.  For production
+** code the yytestcase() macro should be turned off.  But it is useful
+** for testing.
+*/
+#ifndef yytestcase
+# define yytestcase(X)
+#endif
+
+
+/* Next are the tables used to determine what action to take based on the
+** current state and lookahead token.  These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.  
+**
+** Suppose the action integer is N.  Then the action is determined as
+** follows
+**
+**   0 <= N <= YY_MAX_SHIFT             Shift N.  That is, push the lookahead
+**                                      token onto the stack and goto state N.
+**
+**   N between YY_MIN_SHIFTREDUCE       Shift to an arbitrary state then
+**     and YY_MAX_SHIFTREDUCE           reduce by rule N-YY_MIN_SHIFTREDUCE.
+**
+**   N == YY_ERROR_ACTION               A syntax error has occurred.
+**
+**   N == YY_ACCEPT_ACTION              The parser accepts its input.
+**
+**   N == YY_NO_ACTION                  No such action.  Denotes unused
+**                                      slots in the yy_action[] table.
+**
+**   N between YY_MIN_REDUCE            Reduce by rule N-YY_MIN_REDUCE
+**     and YY_MAX_REDUCE
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as either:
+**
+**    (A)   N = yy_action[ yy_shift_ofst[S] + X ]
+**    (B)   N = yy_default[S]
+**
+** The (A) formula is preferred.  The B formula is used instead if
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X.
+**
+** The formulas above are for computing the action when the lookahead is
+** a terminal symbol.  If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array.
+**
+** The following are the tables generated in this section:
+**
+**  yy_action[]        A single table containing all actions.
+**  yy_lookahead[]     A table containing the lookahead for each entry in
+**                     yy_action.  Used to detect hash collisions.
+**  yy_shift_ofst[]    For each state, the offset into yy_action for
+**                     shifting terminals.
+**  yy_reduce_ofst[]   For each state, the offset into yy_action for
+**                     shifting non-terminals after a reduce.
+**  yy_default[]       Default action for each state.
+**
+*********** Begin parsing tables **********************************************/
+%%
+/********** End of lemon-generated parsing tables *****************************/
+
+/* The next table maps tokens (terminal symbols) into fallback tokens.  
+** If a construct like the following:
+** 
+**      %fallback ID X Y Z.
+**
+** appears in the grammar, then ID becomes a fallback token for X, Y,
+** and Z.  Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+**
+** This feature can be used, for example, to cause some keywords in a language
+** to revert to identifiers if they keyword does not apply in the context where
+** it appears.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+%%
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack.  Information stored includes:
+**
+**   +  The state number for the parser at this level of the stack.
+**
+**   +  The value of the token stored at this level of the stack.
+**      (In other words, the "major" token.)
+**
+**   +  The semantic value stored at this level of the stack.  This is
+**      the information used by the action routines in the grammar.
+**      It is sometimes called the "minor" token.
+**
+** After the "shift" half of a SHIFTREDUCE action, the stateno field
+** actually contains the reduce action for the second half of the
+** SHIFTREDUCE.
+*/
+struct yyStackEntry {
+  YYACTIONTYPE stateno;  /* The state-number, or reduce action in SHIFTREDUCE */
+  YYCODETYPE major;      /* The major token value.  This is the code
+                         ** number for the token at this stack level */
+  YYMINORTYPE minor;     /* The user-supplied minor token value.  This
+                         ** is the value of the token  */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+  yyStackEntry *yytos;          /* Pointer to top element of the stack */
+#ifdef YYTRACKMAXSTACKDEPTH
+  int yyhwm;                    /* High-water mark of the stack */
+#endif
+#ifndef YYNOERRORRECOVERY
+  int yyerrcnt;                 /* Shifts left before out of the error */
+#endif
+  ParseARG_SDECL                /* A place to hold %extra_argument */
+  ParseCTX_SDECL                /* A place to hold %extra_context */
+#if YYSTACKDEPTH<=0
+  int yystksz;                  /* Current side of the stack */
+  yyStackEntry *yystack;        /* The parser's stack */
+  yyStackEntry yystk0;          /* First stack entry */
+#else
+  yyStackEntry yystack[YYSTACKDEPTH];  /* The parser's stack */
+  yyStackEntry *yystackEnd;            /* Last entry in the stack */
+#endif
+};
+typedef struct yyParser yyParser;
+
+#include <assert.h>
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* 
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message.  Tracing is turned off
+** by making either argument NULL 
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+**      If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+**      line of trace output.  If NULL, then tracing is
+**      turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
+  yyTraceFILE = TraceFILE;
+  yyTracePrompt = zTracePrompt;
+  if( yyTraceFILE==0 ) yyTracePrompt = 0;
+  else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#if defined(YYCOVERAGE) || !defined(NDEBUG)
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required.  The following table supplies these names */
+static const char *const yyTokenName[] = { 
+%%
+};
+#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *const yyRuleName[] = {
+%%
+};
+#endif /* NDEBUG */
+
+
+#if YYSTACKDEPTH<=0
+/*
+** Try to increase the size of the parser stack.  Return the number
+** of errors.  Return 0 on success.
+*/
+static int yyGrowStack(yyParser *p){
+  int newSize;
+  int idx;
+  yyStackEntry *pNew;
+
+  newSize = p->yystksz*2 + 100;
+  idx = p->yytos ? (int)(p->yytos - p->yystack) : 0;
+  if( p->yystack==&p->yystk0 ){
+    pNew = malloc(newSize*sizeof(pNew[0]));
+    if( pNew ) pNew[0] = p->yystk0;
+  }else{
+    pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
+  }
+  if( pNew ){
+    p->yystack = pNew;
+    p->yytos = &p->yystack[idx];
+#ifndef NDEBUG
+    if( yyTraceFILE ){
+      fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n",
+              yyTracePrompt, p->yystksz, newSize);
+    }
+#endif
+    p->yystksz = newSize;
+  }
+  return pNew==0; 
+}
+#endif
+
+/* Datatype of the argument to the memory allocated passed as the
+** second argument to ParseAlloc() below.  This can be changed by
+** putting an appropriate #define in the %include section of the input
+** grammar.
+*/
+#ifndef YYMALLOCARGTYPE
+# define YYMALLOCARGTYPE size_t
+#endif
+
+/* Initialize a new parser that has already been allocated.
+*/
+void ParseInit(void *yypRawParser ParseCTX_PDECL){
+  yyParser *yypParser = (yyParser*)yypRawParser;
+  ParseCTX_STORE
+#ifdef YYTRACKMAXSTACKDEPTH
+  yypParser->yyhwm = 0;
+#endif
+#if YYSTACKDEPTH<=0
+  yypParser->yytos = NULL;
+  yypParser->yystack = NULL;
+  yypParser->yystksz = 0;
+  if( yyGrowStack(yypParser) ){
+    yypParser->yystack = &yypParser->yystk0;
+    yypParser->yystksz = 1;
+  }
+#endif
+#ifndef YYNOERRORRECOVERY
+  yypParser->yyerrcnt = -1;
+#endif
+  yypParser->yytos = yypParser->yystack;
+  yypParser->yystack[0].stateno = 0;
+  yypParser->yystack[0].major = 0;
+#if YYSTACKDEPTH>0
+  yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1];
+#endif
+}
+
+#ifndef Parse_ENGINEALWAYSONSTACK
+/* 
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser.  This pointer is used in subsequent calls
+** to Parse and ParseFree.
+*/
+void *ParseAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) ParseCTX_PDECL){
+  yyParser *yypParser;
+  yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) );
+  if( yypParser ){
+    ParseCTX_STORE
+    ParseInit(yypParser ParseCTX_PARAM);
+  }
+  return (void*)yypParser;
+}
+#endif /* Parse_ENGINEALWAYSONSTACK */
+
+
+/* The following function deletes the "minor type" or semantic value
+** associated with a symbol.  The symbol can be either a terminal
+** or nonterminal. "yymajor" is the symbol code, and "yypminor" is
+** a pointer to the value to be deleted.  The code used to do the 
+** deletions is derived from the %destructor and/or %token_destructor
+** directives of the input grammar.
+*/
+static void yy_destructor(
+  yyParser *yypParser,    /* The parser */
+  YYCODETYPE yymajor,     /* Type code for object to destroy */
+  YYMINORTYPE *yypminor   /* The object to be destroyed */
+){
+  ParseARG_FETCH
+  ParseCTX_FETCH
+  switch( yymajor ){
+    /* Here is inserted the actions which take place when a
+    ** terminal or non-terminal is destroyed.  This can happen
+    ** when the symbol is popped from the stack during a
+    ** reduce or during error processing or when a parser is 
+    ** being destroyed before it is finished parsing.
+    **
+    ** Note: during a reduce, the only symbols destroyed are those
+    ** which appear on the RHS of the rule, but which are *not* used
+    ** inside the C code.
+    */
+/********* Begin destructor definitions ***************************************/
+%%
+/********* End destructor definitions *****************************************/
+    default:  break;   /* If no destructor action specified: do nothing */
+  }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+*/
+static void yy_pop_parser_stack(yyParser *pParser){
+  yyStackEntry *yytos;
+  assert( pParser->yytos!=0 );
+  assert( pParser->yytos > pParser->yystack );
+  yytos = pParser->yytos--;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sPopping %s\n",
+      yyTracePrompt,
+      yyTokenName[yytos->major]);
+  }
+#endif
+  yy_destructor(pParser, yytos->major, &yytos->minor);
+}
+
+/*
+** Clear all secondary memory allocations from the parser
+*/
+void ParseFinalize(void *p){
+  yyParser *pParser = (yyParser*)p;
+  while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+  if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack);
+#endif
+}
+
+#ifndef Parse_ENGINEALWAYSONSTACK
+/* 
+** Deallocate and destroy a parser.  Destructors are called for
+** all stack elements before shutting the parser down.
+**
+** If the YYPARSEFREENEVERNULL macro exists (for example because it
+** is defined in a %include section of the input grammar) then it is
+** assumed that the input pointer is never NULL.
+*/
+void ParseFree(
+  void *p,                    /* The parser to be deleted */
+  void (*freeProc)(void*)     /* Function used to reclaim memory */
+){
+#ifndef YYPARSEFREENEVERNULL
+  if( p==0 ) return;
+#endif
+  ParseFinalize(p);
+  (*freeProc)(p);
+}
+#endif /* Parse_ENGINEALWAYSONSTACK */
+
+/*
+** Return the peak depth of the stack for a parser.
+*/
+#ifdef YYTRACKMAXSTACKDEPTH
+int ParseStackPeak(void *p){
+  yyParser *pParser = (yyParser*)p;
+  return pParser->yyhwm;
+}
+#endif
+
+/* This array of booleans keeps track of the parser statement
+** coverage.  The element yycoverage[X][Y] is set when the parser
+** is in state X and has a lookahead token Y.  In a well-tested
+** systems, every element of this matrix should end up being set.
+*/
+#if defined(YYCOVERAGE)
+static unsigned char yycoverage[YYNSTATE][YYNTOKEN];
+#endif
+
+/*
+** Write into out a description of every state/lookahead combination that
+**
+**   (1)  has not been used by the parser, and
+**   (2)  is not a syntax error.
+**
+** Return the number of missed state/lookahead combinations.
+*/
+#if defined(YYCOVERAGE)
+int ParseCoverage(FILE *out){
+  int stateno, iLookAhead, i;
+  int nMissed = 0;
+  for(stateno=0; stateno<YYNSTATE; stateno++){
+    i = yy_shift_ofst[stateno];
+    for(iLookAhead=0; iLookAhead<YYNTOKEN; iLookAhead++){
+      if( yy_lookahead[i+iLookAhead]!=iLookAhead ) continue;
+      if( yycoverage[stateno][iLookAhead]==0 ) nMissed++;
+      if( out ){
+        fprintf(out,"State %d lookahead %s %s\n", stateno,
+                yyTokenName[iLookAhead],
+                yycoverage[stateno][iLookAhead] ? "ok" : "missed");
+      }
+    }
+  }
+  return nMissed;
+}
+#endif
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+*/
+static YYACTIONTYPE yy_find_shift_action(
+  YYCODETYPE iLookAhead,    /* The look-ahead token */
+  YYACTIONTYPE stateno      /* Current state number */
+){
+  int i;
+
+  if( stateno>YY_MAX_SHIFT ) return stateno;
+  assert( stateno <= YY_SHIFT_COUNT );
+#if defined(YYCOVERAGE)
+  yycoverage[stateno][iLookAhead] = 1;
+#endif
+  do{
+    i = yy_shift_ofst[stateno];
+    assert( i>=0 );
+    assert( i<=YY_ACTTAB_COUNT );
+    assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD );
+    assert( iLookAhead!=YYNOCODE );
+    assert( iLookAhead < YYNTOKEN );
+    i += iLookAhead;
+    assert( i<(int)YY_NLOOKAHEAD );
+    if( yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+      YYCODETYPE iFallback;            /* Fallback token */
+      assert( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) );
+      iFallback = yyFallback[iLookAhead];
+      if( iFallback!=0 ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+             yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+        }
+#endif
+        assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */
+        iLookAhead = iFallback;
+        continue;
+      }
+#endif
+#ifdef YYWILDCARD
+      {
+        int j = i - iLookAhead + YYWILDCARD;
+        assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) );
+        if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){
+#ifndef NDEBUG
+          if( yyTraceFILE ){
+            fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
+               yyTracePrompt, yyTokenName[iLookAhead],
+               yyTokenName[YYWILDCARD]);
+          }
+#endif /* NDEBUG */
+          return yy_action[j];
+        }
+      }
+#endif /* YYWILDCARD */
+      return yy_default[stateno];
+    }else{
+      assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) );
+      return yy_action[i];
+    }
+  }while(1);
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+*/
+static YYACTIONTYPE yy_find_reduce_action(
+  YYACTIONTYPE stateno,     /* Current state number */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+#ifdef YYERRORSYMBOL
+  if( stateno>YY_REDUCE_COUNT ){
+    return yy_default[stateno];
+  }
+#else
+  assert( stateno<=YY_REDUCE_COUNT );
+#endif
+  i = yy_reduce_ofst[stateno];
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+#ifdef YYERRORSYMBOL
+  if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){
+    return yy_default[stateno];
+  }
+#else
+  assert( i>=0 && i<YY_ACTTAB_COUNT );
+  assert( yy_lookahead[i]==iLookAhead );
+#endif
+  return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser){
+   ParseARG_FETCH
+   ParseCTX_FETCH
+#ifndef NDEBUG
+   if( yyTraceFILE ){
+     fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+   }
+#endif
+   while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
+   /* Here code is inserted which will execute if the parser
+   ** stack every overflows */
+/******** Begin %stack_overflow code ******************************************/
+%%
+/******** End %stack_overflow code ********************************************/
+   ParseARG_STORE /* Suppress warning about unused %extra_argument var */
+   ParseCTX_STORE
+}
+
+/*
+** Print tracing information for a SHIFT action
+*/
+#ifndef NDEBUG
+static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){
+  if( yyTraceFILE ){
+    if( yyNewState<YYNSTATE ){
+      fprintf(yyTraceFILE,"%s%s '%s', go to state %d\n",
+         yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+         yyNewState);
+    }else{
+      fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n",
+         yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+         yyNewState - YY_MIN_REDUCE);
+    }
+  }
+}
+#else
+# define yyTraceShift(X,Y,Z)
+#endif
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+  yyParser *yypParser,          /* The parser to be shifted */
+  YYACTIONTYPE yyNewState,      /* The new state to shift in */
+  YYCODETYPE yyMajor,           /* The major token to shift in */
+  ParseTOKENTYPE yyMinor        /* The minor token to shift in */
+){
+  yyStackEntry *yytos;
+  yypParser->yytos++;
+#ifdef YYTRACKMAXSTACKDEPTH
+  if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
+    yypParser->yyhwm++;
+    assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) );
+  }
+#endif
+#if YYSTACKDEPTH>0 
+  if( yypParser->yytos>yypParser->yystackEnd ){
+    yypParser->yytos--;
+    yyStackOverflow(yypParser);
+    return;
+  }
+#else
+  if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){
+    if( yyGrowStack(yypParser) ){
+      yypParser->yytos--;
+      yyStackOverflow(yypParser);
+      return;
+    }
+  }
+#endif
+  if( yyNewState > YY_MAX_SHIFT ){
+    yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
+  }
+  yytos = yypParser->yytos;
+  yytos->stateno = yyNewState;
+  yytos->major = yyMajor;
+  yytos->minor.yy0 = yyMinor;
+  yyTraceShift(yypParser, yyNewState, "Shift");
+}
+
+/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side
+** of that rule */
+static const YYCODETYPE yyRuleInfoLhs[] = {
+%%
+};
+
+/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number
+** of symbols on the right-hand side of that rule. */
+static const signed char yyRuleInfoNRhs[] = {
+%%
+};
+
+static void yy_accept(yyParser*);  /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+**
+** The yyLookahead and yyLookaheadToken parameters provide reduce actions
+** access to the lookahead token (if any).  The yyLookahead will be YYNOCODE
+** if the lookahead token has already been consumed.  As this procedure is
+** only called from one place, optimizing compilers will in-line it, which
+** means that the extra parameters have no performance impact.
+*/
+static YYACTIONTYPE yy_reduce(
+  yyParser *yypParser,         /* The parser */
+  unsigned int yyruleno,       /* Number of the rule by which to reduce */
+  int yyLookahead,             /* Lookahead token, or YYNOCODE if none */
+  ParseTOKENTYPE yyLookaheadToken  /* Value of the lookahead token */
+  ParseCTX_PDECL                   /* %extra_context */
+){
+  int yygoto;                     /* The next state */
+  YYACTIONTYPE yyact;             /* The next action */
+  yyStackEntry *yymsp;            /* The top of the parser's stack */
+  int yysize;                     /* Amount to pop the stack */
+  ParseARG_FETCH
+  (void)yyLookahead;
+  (void)yyLookaheadToken;
+  yymsp = yypParser->yytos;
+
+  switch( yyruleno ){
+  /* Beginning here are the reduction cases.  A typical example
+  ** follows:
+  **   case 0:
+  **  #line <lineno> <grammarfile>
+  **     { ... }           // User supplied code
+  **  #line <lineno> <thisfile>
+  **     break;
+  */
+/********** Begin reduce actions **********************************************/
+%%
+/********** End reduce actions ************************************************/
+  };
+  assert( yyruleno<sizeof(yyRuleInfoLhs)/sizeof(yyRuleInfoLhs[0]) );
+  yygoto = yyRuleInfoLhs[yyruleno];
+  yysize = yyRuleInfoNRhs[yyruleno];
+  yyact = yy_find_reduce_action(yymsp[yysize].stateno,(YYCODETYPE)yygoto);
+
+  /* There are no SHIFTREDUCE actions on nonterminals because the table
+  ** generator has simplified them to pure REDUCE actions. */
+  assert( !(yyact>YY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) );
+
+  /* It is not possible for a REDUCE to be followed by an error */
+  assert( yyact!=YY_ERROR_ACTION );
+
+  yymsp += yysize+1;
+  yypParser->yytos = yymsp;
+  yymsp->stateno = (YYACTIONTYPE)yyact;
+  yymsp->major = (YYCODETYPE)yygoto;
+  yyTraceShift(yypParser, yyact, "... then shift");
+  return yyact;
+}
+
+/*
+** The following code executes when the parse fails
+*/
+#ifndef YYNOERRORRECOVERY
+static void yy_parse_failed(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH
+  ParseCTX_FETCH
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser fails */
+/************ Begin %parse_failure code ***************************************/
+%%
+/************ End %parse_failure code *****************************************/
+  ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+  ParseCTX_STORE
+}
+#endif /* YYNOERRORRECOVERY */
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+  yyParser *yypParser,           /* The parser */
+  int yymajor,                   /* The major type of the error token */
+  ParseTOKENTYPE yyminor         /* The minor type of the error token */
+){
+  ParseARG_FETCH
+  ParseCTX_FETCH
+#define TOKEN yyminor
+/************ Begin %syntax_error code ****************************************/
+%%
+/************ End %syntax_error code ******************************************/
+  ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+  ParseCTX_STORE
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+  yyParser *yypParser           /* The parser */
+){
+  ParseARG_FETCH
+  ParseCTX_FETCH
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+  }
+#endif
+#ifndef YYNOERRORRECOVERY
+  yypParser->yyerrcnt = -1;
+#endif
+  assert( yypParser->yytos==yypParser->yystack );
+  /* Here code is inserted which will be executed whenever the
+  ** parser accepts */
+/*********** Begin %parse_accept code *****************************************/
+%%
+/*********** End %parse_accept code *******************************************/
+  ParseARG_STORE /* Suppress warning about unused %extra_argument variable */
+  ParseCTX_STORE
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "ParseAlloc" which describes the current state of the parser.
+** The second argument is the major token number.  The third is
+** the minor token.  The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void Parse(
+  void *yyp,                   /* The parser */
+  int yymajor,                 /* The major token code number */
+  ParseTOKENTYPE yyminor       /* The value for the token */
+  ParseARG_PDECL               /* Optional %extra_argument parameter */
+){
+  YYMINORTYPE yyminorunion;
+  YYACTIONTYPE yyact;   /* The parser action. */
+#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
+  int yyendofinput;     /* True if we are at the end of input */
+#endif
+#ifdef YYERRORSYMBOL
+  int yyerrorhit = 0;   /* True if yymajor has invoked an error */
+#endif
+  yyParser *yypParser = (yyParser*)yyp;  /* The parser */
+  ParseCTX_FETCH
+  ParseARG_STORE
+
+  assert( yypParser->yytos!=0 );
+#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
+  yyendofinput = (yymajor==0);
+#endif
+
+  yyact = yypParser->yytos->stateno;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    if( yyact < YY_MIN_REDUCE ){
+      fprintf(yyTraceFILE,"%sInput '%s' in state %d\n",
+              yyTracePrompt,yyTokenName[yymajor],yyact);
+    }else{
+      fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n",
+              yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE);
+    }
+  }
+#endif
+
+  while(1){ /* Exit by "break" */
+    assert( yypParser->yytos>=yypParser->yystack );
+    assert( yyact==yypParser->yytos->stateno );
+    yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact);
+    if( yyact >= YY_MIN_REDUCE ){
+      unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */
+#ifndef NDEBUG
+      assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) );
+      if( yyTraceFILE ){
+        int yysize = yyRuleInfoNRhs[yyruleno];
+        if( yysize ){
+          fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
+            yyTracePrompt,
+            yyruleno, yyRuleName[yyruleno],
+            yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action",
+            yypParser->yytos[yysize].stateno);
+        }else{
+          fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n",
+            yyTracePrompt, yyruleno, yyRuleName[yyruleno],
+            yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action");
+        }
+      }
+#endif /* NDEBUG */
+
+      /* Check that the stack is large enough to grow by a single entry
+      ** if the RHS of the rule is empty.  This ensures that there is room
+      ** enough on the stack to push the LHS value */
+      if( yyRuleInfoNRhs[yyruleno]==0 ){
+#ifdef YYTRACKMAXSTACKDEPTH
+        if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
+          yypParser->yyhwm++;
+          assert( yypParser->yyhwm ==
+                  (int)(yypParser->yytos - yypParser->yystack));
+        }
+#endif
+#if YYSTACKDEPTH>0 
+        if( yypParser->yytos>=yypParser->yystackEnd ){
+          yyStackOverflow(yypParser);
+          break;
+        }
+#else
+        if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
+          if( yyGrowStack(yypParser) ){
+            yyStackOverflow(yypParser);
+            break;
+          }
+        }
+#endif
+      }
+      yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor ParseCTX_PARAM);
+    }else if( yyact <= YY_MAX_SHIFTREDUCE ){
+      yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor);
+#ifndef YYNOERRORRECOVERY
+      yypParser->yyerrcnt--;
+#endif
+      break;
+    }else if( yyact==YY_ACCEPT_ACTION ){
+      yypParser->yytos--;
+      yy_accept(yypParser);
+      return;
+    }else{
+      assert( yyact == YY_ERROR_ACTION );
+      yyminorunion.yy0 = yyminor;
+#ifdef YYERRORSYMBOL
+      int yymx;
+#endif
+#ifndef NDEBUG
+      if( yyTraceFILE ){
+        fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+      }
+#endif
+#ifdef YYERRORSYMBOL
+      /* A syntax error has occurred.
+      ** The response to an error depends upon whether or not the
+      ** grammar defines an error token "ERROR".  
+      **
+      ** This is what we do if the grammar does define ERROR:
+      **
+      **  * Call the %syntax_error function.
+      **
+      **  * Begin popping the stack until we enter a state where
+      **    it is legal to shift the error symbol, then shift
+      **    the error symbol.
+      **
+      **  * Set the error count to three.
+      **
+      **  * Begin accepting and shifting new tokens.  No new error
+      **    processing will occur until three tokens have been
+      **    shifted successfully.
+      **
+      */
+      if( yypParser->yyerrcnt<0 ){
+        yy_syntax_error(yypParser,yymajor,yyminor);
+      }
+      yymx = yypParser->yytos->major;
+      if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+             yyTracePrompt,yyTokenName[yymajor]);
+        }
+#endif
+        yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion);
+        yymajor = YYNOCODE;
+      }else{
+        while( yypParser->yytos > yypParser->yystack ){
+          yyact = yy_find_reduce_action(yypParser->yytos->stateno,
+                                        YYERRORSYMBOL);
+          if( yyact<=YY_MAX_SHIFTREDUCE ) break;
+          yy_pop_parser_stack(yypParser);
+        }
+        if( yypParser->yytos <= yypParser->yystack || yymajor==0 ){
+          yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+          yy_parse_failed(yypParser);
+#ifndef YYNOERRORRECOVERY
+          yypParser->yyerrcnt = -1;
+#endif
+          yymajor = YYNOCODE;
+        }else if( yymx!=YYERRORSYMBOL ){
+          yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor);
+        }
+      }
+      yypParser->yyerrcnt = 3;
+      yyerrorhit = 1;
+      if( yymajor==YYNOCODE ) break;
+      yyact = yypParser->yytos->stateno;
+#elif defined(YYNOERRORRECOVERY)
+      /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to
+      ** do any kind of error recovery.  Instead, simply invoke the syntax
+      ** error routine and continue going as if nothing had happened.
+      **
+      ** Applications can set this macro (for example inside %include) if
+      ** they intend to abandon the parse upon the first syntax error seen.
+      */
+      yy_syntax_error(yypParser,yymajor, yyminor);
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      break;
+#else  /* YYERRORSYMBOL is not defined */
+      /* This is what we do if the grammar does not define ERROR:
+      **
+      **  * Report an error message, and throw away the input token.
+      **
+      **  * If the input token is $, then fail the parse.
+      **
+      ** As before, subsequent error messages are suppressed until
+      ** three input tokens have been successfully shifted.
+      */
+      if( yypParser->yyerrcnt<=0 ){
+        yy_syntax_error(yypParser,yymajor, yyminor);
+      }
+      yypParser->yyerrcnt = 3;
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      if( yyendofinput ){
+        yy_parse_failed(yypParser);
+#ifndef YYNOERRORRECOVERY
+        yypParser->yyerrcnt = -1;
+#endif
+      }
+      break;
+#endif
+    }
+  }
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    yyStackEntry *i;
+    char cDiv = '[';
+    fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt);
+    for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){
+      fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]);
+      cDiv = ' ';
+    }
+    fprintf(yyTraceFILE,"]\n");
+  }
+#endif
+  return;
+}
+
+/*
+** Return the fallback token corresponding to canonical token iToken, or
+** 0 if iToken has no fallback.
+*/
+int ParseFallback(int iToken){
+#ifdef YYFALLBACK
+  assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) );
+  return yyFallback[iToken];
+#else
+  (void)iToken;
+  return 0;
+#endif
+}

+ 8127 - 0
pikchr.mod/pikchr/pikchr.c

@@ -0,0 +1,8127 @@
+/* This file is automatically generated by Lemon from input grammar
+** source file "pikchr.y". */
+/*
+** Zero-Clause BSD license:
+**
+** Copyright (C) 2020-09-01 by D. Richard Hipp <[email protected]>
+**
+** Permission to use, copy, modify, and/or distribute this software for
+** any purpose with or without fee is hereby granted.
+**
+****************************************************************************
+**
+** This software translates a PIC-inspired diagram language into SVG.
+**
+** PIKCHR (pronounced like "picture") is *mostly* backwards compatible
+** with legacy PIC, though some features of legacy PIC are removed 
+** (for example, the "sh" command is removed for security) and
+** many enhancements are added.
+**
+** PIKCHR is designed for use in an internet facing web environment.
+** In particular, PIKCHR is designed to safely generate benign SVG from
+** source text that provided by a hostile agent. 
+**
+** This code was originally written by D. Richard Hipp using documentation
+** from prior PIC implementations but without reference to prior code.
+** All of the code in this project is original.
+**
+** This file implements a C-language subroutine that accepts a string
+** of PIKCHR language text and generates a second string of SVG output that
+** renders the drawing defined by the input.  Space to hold the returned
+** string is obtained from malloc() and should be freed by the caller.
+** NULL might be returned if there is a memory allocation error.
+**
+** If there are errors in the PIKCHR input, the output will consist of an
+** error message and the original PIKCHR input text (inside of <pre>...</pre>).
+**
+** The subroutine implemented by this file is intended to be stand-alone.
+** It uses no external routines other than routines commonly found in
+** the standard C library.
+**
+****************************************************************************
+** COMPILING:
+**
+** The original source text is a mixture of C99 and "Lemon"
+** (See https://sqlite.org/src/file/doc/lemon.html).  Lemon is an LALR(1)
+** parser generator program, similar to Yacc.  The grammar of the
+** input language is specified in Lemon.  C-code is attached.  Lemon
+** runs to generate a single output file ("pikchr.c") which is then
+** compiled to generate the Pikchr library.  This header comment is
+** preserved in the Lemon output, so you might be reading this in either
+** the generated "pikchr.c" file that is output by Lemon, or in the
+** "pikchr.y" source file that is input into Lemon.  If you make changes,
+** you should change the input source file "pikchr.y", not the
+** Lemon-generated output file.
+**
+** Basic compilation steps:
+**
+**      lemon pikchr.y
+**      cc pikchr.c -o pikchr.o
+**
+** Add -DPIKCHR_SHELL to add a main() routine that reads input files
+** and sends them through Pikchr, for testing.  Add -DPIKCHR_FUZZ for
+** -fsanitizer=fuzzer testing.
+** 
+****************************************************************************
+** IMPLEMENTATION NOTES (for people who want to understand the internal
+** operation of this software, perhaps to extend the code or to fix bugs):
+**
+** Each call to pikchr() uses a single instance of the Pik structure to
+** track its internal state.  The Pik structure lives for the duration
+** of the pikchr() call.
+**
+** The input is a sequence of objects or "statements".  Each statement is
+** parsed into a PObj object.  These are stored on an extensible array
+** called PList.  All parameters to each PObj are computed as the
+** object is parsed.  (Hence, the parameters to a PObj may only refer
+** to prior statements.) Once the PObj is completely assembled, it is
+** added to the end of a PList and never changes thereafter - except,
+** PObj objects that are part of a "[...]" block might have their
+** absolute position shifted when the outer [...] block is positioned.
+** But apart from this repositioning, PObj objects are unchanged once
+** they are added to the list. The order of statements on a PList does
+** not change.
+**
+** After all input has been parsed, the top-level PList is walked to
+** generate output.  Sub-lists resulting from [...] blocks are scanned
+** as they are encountered.  All input must be collected and parsed ahead
+** of output generation because the size and position of statements must be
+** known in order to compute a bounding box on the output.
+**
+** Each PObj is on a "layer".  (The common case is that all PObj's are
+** on a single layer, but multiple layers are possible.)  A separate pass
+** is made through the list for each layer.
+**
+** After all output is generated, the Pik object and all the PList
+** and PObj objects are deallocated and the generated output string is
+** returned.  Upon any error, the Pik.nErr flag is set, processing quickly
+** stops, and the stack unwinds.  No attempt is made to continue reading
+** input after an error.
+**
+** Most statements begin with a class name like "box" or "arrow" or "move".
+** There is a class named "text" which is used for statements that begin
+** with a string literal.  You can also specify the "text" class.
+** A Sublist ("[...]") is a single object that contains a pointer to
+** its substatements, all gathered onto a separate PList object.
+**
+** Variables go into PVar objects that form a linked list.
+**
+** Each PObj has zero or one names.  Input constructs that attempt
+** to assign a new name from an older name, for example:
+**
+**      Abc:  Abc + (0.5cm, 0)
+**
+** Statements like these generate a new "noop" object at the specified
+** place and with the given name. As place-names are searched by scanning
+** the list in reverse order, this has the effect of overriding the "Abc"
+** name when referenced by subsequent objects.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <assert.h>
+#define count(X) (sizeof(X)/sizeof(X[0]))
+#ifndef M_PI
+# define M_PI 3.1415926535897932385
+#endif
+
+/* Limit the number of tokens in a single script to avoid run-away
+** macro expansion attacks.  See forum post
+**    https://pikchr.org/home/forumpost/ef8684c6955a411a
+*/
+#ifndef PIKCHR_TOKEN_LIMIT
+# define PIKCHR_TOKEN_LIMIT 100000
+#endif
+
+
+/* Tag intentionally unused parameters with this macro to prevent
+** compiler warnings with -Wextra */
+#define UNUSED_PARAMETER(X)  (void)(X)
+
+typedef struct Pik Pik;          /* Complete parsing context */
+typedef struct PToken PToken;    /* A single token */
+typedef struct PObj PObj;        /* A single diagram object */
+typedef struct PList PList;      /* A list of diagram objects */
+typedef struct PClass PClass;    /* Description of statements types */
+typedef double PNum;             /* Numeric value */
+typedef struct PRel PRel;        /* Absolute or percentage value */
+typedef struct PPoint PPoint;    /* A position in 2-D space */
+typedef struct PVar PVar;        /* script-defined variable */
+typedef struct PBox PBox;        /* A bounding box */
+typedef struct PMacro PMacro;    /* A "define" macro */
+
+/* Compass points */
+#define CP_N      1
+#define CP_NE     2
+#define CP_E      3
+#define CP_SE     4
+#define CP_S      5
+#define CP_SW     6
+#define CP_W      7
+#define CP_NW     8
+#define CP_C      9   /* .center or .c */
+#define CP_END   10   /* .end */
+#define CP_START 11   /* .start */
+
+/* Heading angles corresponding to compass points */
+static const PNum pik_hdg_angle[] = {
+/* none  */   0.0,
+  /* N  */    0.0,
+  /* NE */   45.0,
+  /* E  */   90.0,
+  /* SE */  135.0,
+  /* S  */  180.0,
+  /* SW */  225.0,
+  /* W  */  270.0,
+  /* NW */  315.0,
+  /* C  */    0.0,
+};
+
+/* Built-in functions */
+#define FN_ABS    0
+#define FN_COS    1
+#define FN_INT    2
+#define FN_MAX    3
+#define FN_MIN    4
+#define FN_SIN    5
+#define FN_SQRT   6
+
+/* Text position and style flags.  Stored in PToken.eCode so limited
+** to 15 bits. */
+#define TP_LJUST   0x0001  /* left justify......          */
+#define TP_RJUST   0x0002  /*            ...Right justify */
+#define TP_JMASK   0x0003  /* Mask for justification bits */
+#define TP_ABOVE2  0x0004  /* Position text way above PObj.ptAt */
+#define TP_ABOVE   0x0008  /* Position text above PObj.ptAt */
+#define TP_CENTER  0x0010  /* On the line */
+#define TP_BELOW   0x0020  /* Position text below PObj.ptAt */
+#define TP_BELOW2  0x0040  /* Position text way below PObj.ptAt */
+#define TP_VMASK   0x007c  /* Mask for text positioning flags */
+#define TP_BIG     0x0100  /* Larger font */
+#define TP_SMALL   0x0200  /* Smaller font */
+#define TP_XTRA    0x0400  /* Amplify TP_BIG or TP_SMALL */
+#define TP_SZMASK  0x0700  /* Font size mask */
+#define TP_ITALIC  0x1000  /* Italic font */
+#define TP_BOLD    0x2000  /* Bold font */
+#define TP_FMASK   0x3000  /* Mask for font style */
+#define TP_ALIGN   0x4000  /* Rotate to align with the line */
+
+/* An object to hold a position in 2-D space */
+struct PPoint {
+  PNum x, y;             /* X and Y coordinates */
+};
+static const PPoint cZeroPoint = {0.0,0.0};
+
+/* A bounding box */
+struct PBox {
+  PPoint sw, ne;         /* Lower-left and top-right corners */
+};
+
+/* An Absolute or a relative distance.  The absolute distance
+** is stored in rAbs and the relative distance is stored in rRel.
+** Usually, one or the other will be 0.0.  When using a PRel to
+** update an existing value, the computation is usually something
+** like this:
+**
+**          value = PRel.rAbs + value*PRel.rRel
+**
+*/
+struct PRel {
+  PNum rAbs;            /* Absolute value */
+  PNum rRel;            /* Value relative to current value */
+};
+
+/* A variable created by the ID = EXPR construct of the PIKCHR script 
+**
+** PIKCHR (and PIC) scripts do not use many varaibles, so it is reasonable
+** to store them all on a linked list.
+*/
+struct PVar {
+  const char *zName;       /* Name of the variable */
+  PNum val;                /* Value of the variable */
+  PVar *pNext;             /* Next variable in a list of them all */
+};
+
+/* A single token in the parser input stream
+*/
+struct PToken {
+  const char *z;             /* Pointer to the token text */
+  unsigned int n;            /* Length of the token in bytes */
+  short int eCode;           /* Auxiliary code */
+  unsigned char eType;       /* The numeric parser code */
+  unsigned char eEdge;       /* Corner value for corner keywords */
+};
+
+/* Return negative, zero, or positive if pToken is less than, equal to
+** or greater than the zero-terminated string z[]
+*/
+static int pik_token_eq(PToken *pToken, const char *z){
+  int c = strncmp(pToken->z,z,pToken->n);
+  if( c==0 && z[pToken->n]!=0 ) c = -1;
+  return c;
+}
+
+/* Extra token types not generated by LEMON but needed by the
+** tokenizer
+*/
+#define T_PARAMETER  253     /* $1, $2, ..., $9 */
+#define T_WHITESPACE 254     /* Whitespace of comments */
+#define T_ERROR      255     /* Any text that is not a valid token */
+
+/* Directions of movement */
+#define DIR_RIGHT     0
+#define DIR_DOWN      1
+#define DIR_LEFT      2
+#define DIR_UP        3
+#define ValidDir(X)     ((X)>=0 && (X)<=3)
+#define IsUpDown(X)     (((X)&1)==1)
+#define IsLeftRight(X)  (((X)&1)==0)
+
+/* Bitmask for the various attributes for PObj.  These bits are
+** collected in PObj.mProp and PObj.mCalc to check for constraint
+** errors. */
+#define A_WIDTH         0x0001
+#define A_HEIGHT        0x0002
+#define A_RADIUS        0x0004
+#define A_THICKNESS     0x0008
+#define A_DASHED        0x0010 /* Includes "dotted" */
+#define A_FILL          0x0020
+#define A_COLOR         0x0040
+#define A_ARROW         0x0080
+#define A_FROM          0x0100
+#define A_CW            0x0200
+#define A_AT            0x0400
+#define A_TO            0x0800 /* one or more movement attributes */
+#define A_FIT           0x1000
+
+
+/* A single graphics object */
+struct PObj {
+  const PClass *type;      /* Object type or class */
+  PToken errTok;           /* Reference token for error messages */
+  PPoint ptAt;             /* Reference point for the object */
+  PPoint ptEnter, ptExit;  /* Entry and exit points */
+  PList *pSublist;         /* Substructure for [...] objects */
+  char *zName;             /* Name assigned to this statement */
+  PNum w;                  /* "width" property */
+  PNum h;                  /* "height" property */
+  PNum rad;                /* "radius" property */
+  PNum sw;                 /* "thickness" property. (Mnemonic: "stroke width")*/
+  PNum dotted;             /* "dotted" property.   <=0.0 for off */
+  PNum dashed;             /* "dashed" property.   <=0.0 for off */
+  PNum fill;               /* "fill" property.  Negative for off */
+  PNum color;              /* "color" property */
+  PPoint with;             /* Position constraint from WITH clause */
+  char eWith;              /* Type of heading point on WITH clause */
+  char cw;                 /* True for clockwise arc */
+  char larrow;             /* Arrow at beginning (<- or <->) */
+  char rarrow;             /* Arrow at end  (-> or <->) */
+  char bClose;             /* True if "close" is seen */
+  char bChop;              /* True if "chop" is seen */
+  unsigned char nTxt;      /* Number of text values */
+  unsigned mProp;          /* Masks of properties set so far */
+  unsigned mCalc;          /* Values computed from other constraints */
+  PToken aTxt[5];          /* Text with .eCode holding TP flags */
+  int iLayer;              /* Rendering order */
+  int inDir, outDir;       /* Entry and exit directions */
+  int nPath;               /* Number of path points */
+  PPoint *aPath;           /* Array of path points */
+  PObj *pFrom, *pTo;       /* End-point objects of a path */
+  PBox bbox;               /* Bounding box */
+};
+
+/* A list of graphics objects */
+struct PList {
+  int n;          /* Number of statements in the list */
+  int nAlloc;     /* Allocated slots in a[] */
+  PObj **a;       /* Pointers to individual objects */
+};
+
+/* A macro definition */
+struct PMacro {
+  PMacro *pNext;       /* Next in the list */
+  PToken macroName;    /* Name of the macro */
+  PToken macroBody;    /* Body of the macro */
+  int inUse;           /* Do not allow recursion */
+};
+
+/* Each call to the pikchr() subroutine uses an instance of the following
+** object to pass around context to all of its subroutines.
+*/
+struct Pik {
+  unsigned nErr;           /* Number of errors seen */
+  unsigned nToken;         /* Number of tokens parsed */
+  PToken sIn;              /* Input Pikchr-language text */
+  char *zOut;              /* Result accumulates here */
+  unsigned int nOut;       /* Bytes written to zOut[] so far */
+  unsigned int nOutAlloc;  /* Space allocated to zOut[] */
+  unsigned char eDir;      /* Current direction */
+  unsigned int mFlags;     /* Flags passed to pikchr() */
+  PObj *cur;               /* Object under construction */
+  PObj *lastRef;           /* Last object references by name */
+  PList *list;             /* Object list under construction */
+  PMacro *pMacros;         /* List of all defined macros */
+  PVar *pVar;              /* Application-defined variables */
+  PBox bbox;               /* Bounding box around all statements */
+                           /* Cache of layout values.  <=0.0 for unknown... */
+  PNum rScale;                 /* Multiply to convert inches to pixels */
+  PNum fontScale;              /* Scale fonts by this percent */
+  PNum charWidth;              /* Character width */
+  PNum charHeight;             /* Character height */
+  PNum wArrow;                 /* Width of arrowhead at the fat end */
+  PNum hArrow;                 /* Ht of arrowhead - dist from tip to fat end */
+  char bLayoutVars;            /* True if cache is valid */
+  char thenFlag;           /* True if "then" seen */
+  char samePath;           /* aTPath copied by "same" */
+  const char *zClass;      /* Class name for the <svg> */
+  int wSVG, hSVG;          /* Width and height of the <svg> */
+  int fgcolor;             /* foreground color value, or -1 for none */
+  int bgcolor;             /* background color value, or -1 for none */
+  /* Paths for lines are constructed here first, then transferred into
+  ** the PObj object at the end: */
+  int nTPath;              /* Number of entries on aTPath[] */
+  int mTPath;              /* For last entry, 1: x set,  2: y set */
+  PPoint aTPath[1000];     /* Path under construction */
+  /* Error contexts */
+  unsigned int nCtx;       /* Number of error contexts */
+  PToken aCtx[10];         /* Nested error contexts */
+};
+
+/* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
+** argument to pikchr() in order to cause error message text to come out
+** as text/plain instead of as text/html
+*/
+#define PIKCHR_PLAINTEXT_ERRORS 0x0001
+
+/* Include PIKCHR_DARK_MODE among the mFlag bits to invert colors.
+*/
+#define PIKCHR_DARK_MODE        0x0002
+
+/*
+** The behavior of an object class is defined by an instance of
+** this structure. This is the "virtual method" table.
+*/
+struct PClass {
+  const char *zName;                     /* Name of class */
+  char isLine;                           /* True if a line class */
+  char eJust;                            /* Use box-style text justification */
+  void (*xInit)(Pik*,PObj*);              /* Initializer */
+  void (*xNumProp)(Pik*,PObj*,PToken*);   /* Value change notification */
+  void (*xCheck)(Pik*,PObj*);             /* Checks to do after parsing */
+  PPoint (*xChop)(Pik*,PObj*,PPoint*);    /* Chopper */
+  PPoint (*xOffset)(Pik*,PObj*,int);      /* Offset from .c to edge point */
+  void (*xFit)(Pik*,PObj*,PNum w,PNum h); /* Size to fit text */
+  void (*xRender)(Pik*,PObj*);            /* Render */
+};
+
+
+/* Forward declarations */
+static void pik_append(Pik*, const char*,int);
+static void pik_append_text(Pik*,const char*,int,int);
+static void pik_append_num(Pik*,const char*,PNum);
+static void pik_append_point(Pik*,const char*,PPoint*);
+static void pik_append_x(Pik*,const char*,PNum,const char*);
+static void pik_append_y(Pik*,const char*,PNum,const char*);
+static void pik_append_xy(Pik*,const char*,PNum,PNum);
+static void pik_append_dis(Pik*,const char*,PNum,const char*);
+static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum);
+static void pik_append_clr(Pik*,const char*,PNum,const char*,int);
+static void pik_append_style(Pik*,PObj*,int);
+static void pik_append_txt(Pik*,PObj*, PBox*);
+static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*);
+static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum);
+static void pik_error(Pik*,PToken*,const char*);
+static void pik_elist_free(Pik*,PList*);
+static void pik_elem_free(Pik*,PObj*);
+static void pik_render(Pik*,PList*);
+static PList *pik_elist_append(Pik*,PList*,PObj*);
+static PObj *pik_elem_new(Pik*,PToken*,PToken*,PList*);
+static void pik_set_direction(Pik*,int);
+static void pik_elem_setname(Pik*,PObj*,PToken*);
+static int pik_round(PNum);
+static void pik_set_var(Pik*,PToken*,PNum,PToken*);
+static PNum pik_value(Pik*,const char*,int,int*);
+static int pik_value_int(Pik*,const char*,int,int*);
+static PNum pik_lookup_color(Pik*,PToken*);
+static PNum pik_get_var(Pik*,PToken*);
+static PNum pik_atof(PToken*);
+static void pik_after_adding_attributes(Pik*,PObj*);
+static void pik_elem_move(PObj*,PNum dx, PNum dy);
+static void pik_elist_move(PList*,PNum dx, PNum dy);
+static void pik_set_numprop(Pik*,PToken*,PRel*);
+static void pik_set_clrprop(Pik*,PToken*,PNum);
+static void pik_set_dashed(Pik*,PToken*,PNum*);
+static void pik_then(Pik*,PToken*,PObj*);
+static void pik_add_direction(Pik*,PToken*,PRel*);
+static void pik_move_hdg(Pik*,PRel*,PToken*,PNum,PToken*,PToken*);
+static void pik_evenwith(Pik*,PToken*,PPoint*);
+static void pik_set_from(Pik*,PObj*,PToken*,PPoint*);
+static void pik_add_to(Pik*,PObj*,PToken*,PPoint*);
+static void pik_close_path(Pik*,PToken*);
+static void pik_set_at(Pik*,PToken*,PPoint*,PToken*);
+static short int pik_nth_value(Pik*,PToken*);
+static PObj *pik_find_nth(Pik*,PObj*,PToken*);
+static PObj *pik_find_byname(Pik*,PObj*,PToken*);
+static PPoint pik_place_of_elem(Pik*,PObj*,PToken*);
+static int pik_bbox_isempty(PBox*);
+static int pik_bbox_contains_point(PBox*,PPoint*);
+static void pik_bbox_init(PBox*);
+static void pik_bbox_addbox(PBox*,PBox*);
+static void pik_bbox_add_xy(PBox*,PNum,PNum);
+static void pik_bbox_addellipse(PBox*,PNum x,PNum y,PNum rx,PNum ry);
+static void pik_add_txt(Pik*,PToken*,int);
+static int pik_text_length(const PToken *pToken);
+static void pik_size_to_fit(Pik*,PToken*,int);
+static int pik_text_position(int,PToken*);
+static PNum pik_property_of(PObj*,PToken*);
+static PNum pik_func(Pik*,PToken*,PNum,PNum);
+static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2);
+static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt);
+static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt);
+static void pik_same(Pik *p, PObj*, PToken*);
+static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj);
+static PToken pik_next_semantic_token(PToken *pThis);
+static void pik_compute_layout_settings(Pik*);
+static void pik_behind(Pik*,PObj*);
+static PObj *pik_assert(Pik*,PNum,PToken*,PNum);
+static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*);
+static PNum pik_dist(PPoint*,PPoint*);
+static void pik_add_macro(Pik*,PToken *pId,PToken *pCode);
+
+
+#line 520 "pikchr.c"
+/**************** End of %include directives **********************************/
+/* These constants specify the various numeric values for terminal symbols.
+***************** Begin token definitions *************************************/
+#ifndef T_ID
+#define T_ID                              1
+#define T_EDGEPT                          2
+#define T_OF                              3
+#define T_PLUS                            4
+#define T_MINUS                           5
+#define T_STAR                            6
+#define T_SLASH                           7
+#define T_PERCENT                         8
+#define T_UMINUS                          9
+#define T_EOL                            10
+#define T_ASSIGN                         11
+#define T_PLACENAME                      12
+#define T_COLON                          13
+#define T_ASSERT                         14
+#define T_LP                             15
+#define T_EQ                             16
+#define T_RP                             17
+#define T_DEFINE                         18
+#define T_CODEBLOCK                      19
+#define T_FILL                           20
+#define T_COLOR                          21
+#define T_THICKNESS                      22
+#define T_PRINT                          23
+#define T_STRING                         24
+#define T_COMMA                          25
+#define T_CLASSNAME                      26
+#define T_LB                             27
+#define T_RB                             28
+#define T_UP                             29
+#define T_DOWN                           30
+#define T_LEFT                           31
+#define T_RIGHT                          32
+#define T_CLOSE                          33
+#define T_CHOP                           34
+#define T_FROM                           35
+#define T_TO                             36
+#define T_THEN                           37
+#define T_HEADING                        38
+#define T_GO                             39
+#define T_AT                             40
+#define T_WITH                           41
+#define T_SAME                           42
+#define T_AS                             43
+#define T_FIT                            44
+#define T_BEHIND                         45
+#define T_UNTIL                          46
+#define T_EVEN                           47
+#define T_DOT_E                          48
+#define T_HEIGHT                         49
+#define T_WIDTH                          50
+#define T_RADIUS                         51
+#define T_DIAMETER                       52
+#define T_DOTTED                         53
+#define T_DASHED                         54
+#define T_CW                             55
+#define T_CCW                            56
+#define T_LARROW                         57
+#define T_RARROW                         58
+#define T_LRARROW                        59
+#define T_INVIS                          60
+#define T_THICK                          61
+#define T_THIN                           62
+#define T_SOLID                          63
+#define T_CENTER                         64
+#define T_LJUST                          65
+#define T_RJUST                          66
+#define T_ABOVE                          67
+#define T_BELOW                          68
+#define T_ITALIC                         69
+#define T_BOLD                           70
+#define T_ALIGNED                        71
+#define T_BIG                            72
+#define T_SMALL                          73
+#define T_AND                            74
+#define T_LT                             75
+#define T_GT                             76
+#define T_ON                             77
+#define T_WAY                            78
+#define T_BETWEEN                        79
+#define T_THE                            80
+#define T_NTH                            81
+#define T_VERTEX                         82
+#define T_TOP                            83
+#define T_BOTTOM                         84
+#define T_START                          85
+#define T_END                            86
+#define T_IN                             87
+#define T_THIS                           88
+#define T_DOT_U                          89
+#define T_LAST                           90
+#define T_NUMBER                         91
+#define T_FUNC1                          92
+#define T_FUNC2                          93
+#define T_DIST                           94
+#define T_DOT_XY                         95
+#define T_X                              96
+#define T_Y                              97
+#define T_DOT_L                          98
+#endif
+/**************** End token definitions ***************************************/
+
+/* The next sections is a series of control #defines.
+** various aspects of the generated parser.
+**    YYCODETYPE         is the data type used to store the integer codes
+**                       that represent terminal and non-terminal symbols.
+**                       "unsigned char" is used if there are fewer than
+**                       256 symbols.  Larger types otherwise.
+**    YYNOCODE           is a number of type YYCODETYPE that is not used for
+**                       any terminal or nonterminal symbol.
+**    YYFALLBACK         If defined, this indicates that one or more tokens
+**                       (also known as: "terminal symbols") have fall-back
+**                       values which should be used if the original symbol
+**                       would not parse.  This permits keywords to sometimes
+**                       be used as identifiers, for example.
+**    YYACTIONTYPE       is the data type used for "action codes" - numbers
+**                       that indicate what to do in response to the next
+**                       token.
+**    pik_parserTOKENTYPE     is the data type used for minor type for terminal
+**                       symbols.  Background: A "minor type" is a semantic
+**                       value associated with a terminal or non-terminal
+**                       symbols.  For example, for an "ID" terminal symbol,
+**                       the minor type might be the name of the identifier.
+**                       Each non-terminal can have a different minor type.
+**                       Terminal symbols all have the same minor type, though.
+**                       This macros defines the minor type for terminal 
+**                       symbols.
+**    YYMINORTYPE        is the data type used for all minor types.
+**                       This is typically a union of many types, one of
+**                       which is pik_parserTOKENTYPE.  The entry in the union
+**                       for terminal symbols is called "yy0".
+**    YYSTACKDEPTH       is the maximum depth of the parser's stack.  If
+**                       zero the stack is dynamically sized using realloc()
+**    pik_parserARG_SDECL     A static variable declaration for the %extra_argument
+**    pik_parserARG_PDECL     A parameter declaration for the %extra_argument
+**    pik_parserARG_PARAM     Code to pass %extra_argument as a subroutine parameter
+**    pik_parserARG_STORE     Code to store %extra_argument into yypParser
+**    pik_parserARG_FETCH     Code to extract %extra_argument from yypParser
+**    pik_parserCTX_*         As pik_parserARG_ except for %extra_context
+**    YYERRORSYMBOL      is the code number of the error symbol.  If not
+**                       defined, then do no error processing.
+**    YYNSTATE           the combined number of states.
+**    YYNRULE            the number of rules in the grammar
+**    YYNTOKEN           Number of terminal symbols
+**    YY_MAX_SHIFT       Maximum value for shift actions
+**    YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
+**    YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
+**    YY_ERROR_ACTION    The yy_action[] code for syntax error
+**    YY_ACCEPT_ACTION   The yy_action[] code for accept
+**    YY_NO_ACTION       The yy_action[] code for no-op
+**    YY_MIN_REDUCE      Minimum value for reduce actions
+**    YY_MAX_REDUCE      Maximum value for reduce actions
+*/
+#ifndef INTERFACE
+# define INTERFACE 1
+#endif
+/************* Begin control #defines *****************************************/
+#define YYCODETYPE unsigned char
+#define YYNOCODE 135
+#define YYACTIONTYPE unsigned short int
+#define pik_parserTOKENTYPE PToken
+typedef union {
+  int yyinit;
+  pik_parserTOKENTYPE yy0;
+  PRel yy10;
+  PObj* yy36;
+  PPoint yy79;
+  PNum yy153;
+  short int yy164;
+  PList* yy227;
+} YYMINORTYPE;
+#ifndef YYSTACKDEPTH
+#define YYSTACKDEPTH 100
+#endif
+#define pik_parserARG_SDECL
+#define pik_parserARG_PDECL
+#define pik_parserARG_PARAM
+#define pik_parserARG_FETCH
+#define pik_parserARG_STORE
+#define pik_parserCTX_SDECL Pik *p;
+#define pik_parserCTX_PDECL ,Pik *p
+#define pik_parserCTX_PARAM ,p
+#define pik_parserCTX_FETCH Pik *p=yypParser->p;
+#define pik_parserCTX_STORE yypParser->p=p;
+#define YYFALLBACK 1
+#define YYNSTATE             164
+#define YYNRULE              156
+#define YYNRULE_WITH_ACTION  116
+#define YYNTOKEN             99
+#define YY_MAX_SHIFT         163
+#define YY_MIN_SHIFTREDUCE   287
+#define YY_MAX_SHIFTREDUCE   442
+#define YY_ERROR_ACTION      443
+#define YY_ACCEPT_ACTION     444
+#define YY_NO_ACTION         445
+#define YY_MIN_REDUCE        446
+#define YY_MAX_REDUCE        601
+/************* End control #defines *******************************************/
+#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])))
+
+/* Define the yytestcase() macro to be a no-op if is not already defined
+** otherwise.
+**
+** Applications can choose to define yytestcase() in the %include section
+** to a macro that can assist in verifying code coverage.  For production
+** code the yytestcase() macro should be turned off.  But it is useful
+** for testing.
+*/
+#ifndef yytestcase
+# define yytestcase(X)
+#endif
+
+
+/* Next are the tables used to determine what action to take based on the
+** current state and lookahead token.  These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.  
+**
+** Suppose the action integer is N.  Then the action is determined as
+** follows
+**
+**   0 <= N <= YY_MAX_SHIFT             Shift N.  That is, push the lookahead
+**                                      token onto the stack and goto state N.
+**
+**   N between YY_MIN_SHIFTREDUCE       Shift to an arbitrary state then
+**     and YY_MAX_SHIFTREDUCE           reduce by rule N-YY_MIN_SHIFTREDUCE.
+**
+**   N == YY_ERROR_ACTION               A syntax error has occurred.
+**
+**   N == YY_ACCEPT_ACTION              The parser accepts its input.
+**
+**   N == YY_NO_ACTION                  No such action.  Denotes unused
+**                                      slots in the yy_action[] table.
+**
+**   N between YY_MIN_REDUCE            Reduce by rule N-YY_MIN_REDUCE
+**     and YY_MAX_REDUCE
+**
+** The action table is constructed as a single large table named yy_action[].
+** Given state S and lookahead X, the action is computed as either:
+**
+**    (A)   N = yy_action[ yy_shift_ofst[S] + X ]
+**    (B)   N = yy_default[S]
+**
+** The (A) formula is preferred.  The B formula is used instead if
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X.
+**
+** The formulas above are for computing the action when the lookahead is
+** a terminal symbol.  If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the yy_reduce_ofst[] array is used in place of
+** the yy_shift_ofst[] array.
+**
+** The following are the tables generated in this section:
+**
+**  yy_action[]        A single table containing all actions.
+**  yy_lookahead[]     A table containing the lookahead for each entry in
+**                     yy_action.  Used to detect hash collisions.
+**  yy_shift_ofst[]    For each state, the offset into yy_action for
+**                     shifting terminals.
+**  yy_reduce_ofst[]   For each state, the offset into yy_action for
+**                     shifting non-terminals after a reduce.
+**  yy_default[]       Default action for each state.
+**
+*********** Begin parsing tables **********************************************/
+#define YY_ACTTAB_COUNT (1303)
+static const YYACTIONTYPE yy_action[] = {
+ /*     0 */   575,  495,  161,  119,   25,  452,   29,   74,  129,  148,
+ /*    10 */   575,  492,  161,  119,  453,  113,  120,  161,  119,  530,
+ /*    20 */   427,  428,  339,  559,   81,   30,  560,  561,  575,   64,
+ /*    30 */    63,   62,   61,  322,  323,    9,    8,   33,  149,   32,
+ /*    40 */     7,   71,  127,   38,  335,   66,   48,   37,   28,  339,
+ /*    50 */   339,  339,  339,  425,  426,  340,  341,  342,  343,  344,
+ /*    60 */   345,  346,  347,  348,  474,  528,  161,  119,  577,   77,
+ /*    70 */   577,   73,  376,  148,  474,  533,  161,  119,  112,  113,
+ /*    80 */   120,  161,  119,  128,  427,  428,  339,  357,   81,  531,
+ /*    90 */   161,  119,  474,   36,  330,   13,  306,  322,  323,    9,
+ /*   100 */     8,   33,  149,   32,    7,   71,  127,  328,  335,   66,
+ /*   110 */   579,  310,   31,  339,  339,  339,  339,  425,  426,  340,
+ /*   120 */   341,  342,  343,  344,  345,  346,  347,  348,  394,  435,
+ /*   130 */    46,   59,   60,   64,   63,   62,   61,   54,   51,  376,
+ /*   140 */    69,  108,    2,   47,  403,   83,  297,  435,  375,   84,
+ /*   150 */   117,   80,   35,  308,   79,  133,  122,  126,  441,  440,
+ /*   160 */   299,  123,    3,  404,  405,  406,  408,   80,  298,  308,
+ /*   170 */    79,    4,  411,  412,  413,  414,  441,  440,  350,  350,
+ /*   180 */   350,  350,  350,  350,  350,  350,  350,  350,   62,   61,
+ /*   190 */    67,  434,    1,   75,  378,  158,   74,   76,  148,  411,
+ /*   200 */   412,  413,  414,  124,  113,  120,  161,  119,  106,  434,
+ /*   210 */   436,  437,  438,  439,    5,  375,    6,  117,  393,  155,
+ /*   220 */   154,  153,  394,  435,   69,   59,   60,  149,  436,  437,
+ /*   230 */   438,  439,  535,  376,  398,  399,    2,  424,  427,  428,
+ /*   240 */   339,  156,  156,  156,  423,  394,  435,   65,   59,   60,
+ /*   250 */   162,  131,  441,  440,  397,   72,  376,  148,  118,    2,
+ /*   260 */   380,  157,  125,  113,  120,  161,  119,  339,  339,  339,
+ /*   270 */   339,  425,  426,  535,   11,  441,  440,  394,  356,  535,
+ /*   280 */    59,   60,  535,  379,  159,  434,  149,   12,  102,  446,
+ /*   290 */   432,   42,  138,   14,  435,  139,  301,  302,  303,   36,
+ /*   300 */   305,  430,  106,   16,  436,  437,  438,  439,  434,  375,
+ /*   310 */    18,  117,  393,  155,  154,  153,   44,  142,  140,   64,
+ /*   320 */    63,   62,   61,  441,  440,  106,   19,  436,  437,  438,
+ /*   330 */   439,   45,  375,   20,  117,  393,  155,  154,  153,   68,
+ /*   340 */    55,  114,   64,   63,   62,   61,  147,  146,  394,  473,
+ /*   350 */   359,   59,   60,   43,   23,  391,  434,  106,   26,  376,
+ /*   360 */    57,   58,   42,   49,  375,  392,  117,  393,  155,  154,
+ /*   370 */   153,   64,   63,   62,   61,  436,  437,  438,  439,  384,
+ /*   380 */   382,  383,   22,   21,  377,  473,  160,   70,   39,  445,
+ /*   390 */    24,  445,  145,  141,  431,  142,  140,   64,   63,   62,
+ /*   400 */    61,  394,   15,  445,   59,   60,   64,   63,   62,   61,
+ /*   410 */   391,  445,  376,  445,  445,   42,  445,  445,   55,  391,
+ /*   420 */   156,  156,  156,  445,  147,  146,  445,   52,  106,  445,
+ /*   430 */   445,   43,  445,  445,  445,  375,  445,  117,  393,  155,
+ /*   440 */   154,  153,  445,  394,  143,  445,   59,   60,   64,   63,
+ /*   450 */    62,   61,  313,  445,  376,  378,  158,   42,  445,  445,
+ /*   460 */    22,   21,  121,  447,  454,   29,  445,  445,   24,  450,
+ /*   470 */   145,  141,  431,  142,  140,   64,   63,   62,   61,  445,
+ /*   480 */   163,  106,  445,  445,  444,   27,  445,  445,  375,  445,
+ /*   490 */   117,  393,  155,  154,  153,  445,   55,   74,  445,  148,
+ /*   500 */   445,  445,  147,  146,  497,  113,  120,  161,  119,   43,
+ /*   510 */   445,  394,  445,  445,   59,   60,  445,  445,  445,  118,
+ /*   520 */   445,  445,  376,  106,  445,   42,  445,  445,  149,  445,
+ /*   530 */   375,  445,  117,  393,  155,  154,  153,  445,   22,   21,
+ /*   540 */   394,  144,  445,   59,   60,  445,   24,  445,  145,  141,
+ /*   550 */   431,  376,  445,  445,   42,  445,  132,  130,  394,  445,
+ /*   560 */   445,   59,   60,  109,  447,  454,   29,  445,  445,  376,
+ /*   570 */   450,  445,   42,  445,  394,  445,  445,   59,   60,  445,
+ /*   580 */   445,  163,  445,  445,  445,  102,   27,  445,   42,  445,
+ /*   590 */   445,  106,  445,   64,   63,   62,   61,  445,  375,  445,
+ /*   600 */   117,  393,  155,  154,  153,  394,  355,  445,   59,   60,
+ /*   610 */   445,  445,  445,  445,  445,   74,  376,  148,  445,   40,
+ /*   620 */   106,  445,  496,  113,  120,  161,  119,  375,  445,  117,
+ /*   630 */   393,  155,  154,  153,  445,  448,  454,   29,  106,  445,
+ /*   640 */   445,  450,  445,  445,  445,  375,  149,  117,  393,  155,
+ /*   650 */   154,  153,  163,  445,  106,  445,  445,   27,  445,  445,
+ /*   660 */   445,  375,  445,  117,  393,  155,  154,  153,  394,  445,
+ /*   670 */   445,   59,   60,   64,   63,   62,   61,  445,  445,  376,
+ /*   680 */   445,  445,   41,  445,  445,  106,  354,   64,   63,   62,
+ /*   690 */    61,  445,  375,  445,  117,  393,  155,  154,  153,  445,
+ /*   700 */   445,  445,   74,  445,  148,  445,   88,  445,  445,  490,
+ /*   710 */   113,  120,  161,  119,  445,  120,  161,  119,   17,   74,
+ /*   720 */   445,  148,  110,  110,  445,  445,  484,  113,  120,  161,
+ /*   730 */   119,  445,  445,  149,   74,  445,  148,  152,  445,  445,
+ /*   740 */   445,  483,  113,  120,  161,  119,  445,  445,  106,  445,
+ /*   750 */   149,  445,  445,  107,  445,  375,  445,  117,  393,  155,
+ /*   760 */   154,  153,  120,  161,  119,  149,  478,   74,  445,  148,
+ /*   770 */   445,   88,  445,  445,  480,  113,  120,  161,  119,  445,
+ /*   780 */   120,  161,  119,   74,  152,  148,   10,  479,  479,  445,
+ /*   790 */   134,  113,  120,  161,  119,  445,  445,  445,  149,   74,
+ /*   800 */   445,  148,  152,  445,  445,  445,  517,  113,  120,  161,
+ /*   810 */   119,  445,  445,   74,  149,  148,  445,  445,  445,  445,
+ /*   820 */   137,  113,  120,  161,  119,   74,  445,  148,  445,  445,
+ /*   830 */   149,  445,  525,  113,  120,  161,  119,  445,   74,  445,
+ /*   840 */   148,  445,  445,  445,  149,  527,  113,  120,  161,  119,
+ /*   850 */   445,  445,   74,  445,  148,  445,  149,  445,  445,  524,
+ /*   860 */   113,  120,  161,  119,   74,  445,  148,  445,  445,  149,
+ /*   870 */   445,  526,  113,  120,  161,  119,  445,  445,   74,  445,
+ /*   880 */   148,  445,   88,  149,  445,  523,  113,  120,  161,  119,
+ /*   890 */   445,  120,  161,  119,   74,  149,  148,   85,  111,  111,
+ /*   900 */   445,  522,  113,  120,  161,  119,  120,  161,  119,  149,
+ /*   910 */    74,  445,  148,  152,  445,  445,  445,  521,  113,  120,
+ /*   920 */   161,  119,  445,  445,   74,  149,  148,  445,  152,  445,
+ /*   930 */   445,  520,  113,  120,  161,  119,   74,  445,  148,  445,
+ /*   940 */   445,  149,  445,  519,  113,  120,  161,  119,  445,   74,
+ /*   950 */   445,  148,  445,  445,  445,  149,  150,  113,  120,  161,
+ /*   960 */   119,  445,  445,   74,  445,  148,  445,  149,  445,  445,
+ /*   970 */   151,  113,  120,  161,  119,   74,  445,  148,  445,  445,
+ /*   980 */   149,  445,  136,  113,  120,  161,  119,  445,  445,   74,
+ /*   990 */   445,  148,  107,  445,  149,  445,  135,  113,  120,  161,
+ /*  1000 */   119,  120,  161,  119,  445,  463,  149,  445,   88,  445,
+ /*  1010 */   445,  445,   78,   78,  445,  445,  107,  120,  161,  119,
+ /*  1020 */   149,  445,  445,  152,   82,  120,  161,  119,  445,  463,
+ /*  1030 */   445,  466,   86,   34,  445,   88,  445,  569,  445,  152,
+ /*  1040 */   445,  120,  161,  119,  120,  161,  119,  152,  107,  445,
+ /*  1050 */   445,  475,   64,   63,   62,   61,  445,  120,  161,  119,
+ /*  1060 */    98,  451,  445,  152,   89,  396,  152,   90,  445,  120,
+ /*  1070 */   161,  119,  445,  120,  161,  119,  120,  161,  119,  152,
+ /*  1080 */   445,   64,   63,   62,   61,  445,  445,  445,  445,  445,
+ /*  1090 */    87,  152,  445,   99,  395,  152,  100,  445,  152,  120,
+ /*  1100 */   161,  119,  120,  161,  119,  120,  161,  119,  445,  101,
+ /*  1110 */    64,   63,   62,   61,  445,  445,  445,  445,  120,  161,
+ /*  1120 */   119,  152,   91,  391,  152,  445,  445,  152,  103,  445,
+ /*  1130 */   445,  120,  161,  119,  445,   92,  445,  120,  161,  119,
+ /*  1140 */   152,   93,  445,  445,  120,  161,  119,  104,  445,  445,
+ /*  1150 */   120,  161,  119,  152,  445,  445,  120,  161,  119,  152,
+ /*  1160 */   445,  445,  445,  445,   94,  445,  152,  445,  445,  445,
+ /*  1170 */   105,  445,  152,  120,  161,  119,  445,   95,  152,  120,
+ /*  1180 */   161,  119,   96,  445,  445,  445,  120,  161,  119,  445,
+ /*  1190 */   445,  120,  161,  119,   97,  152,  445,  445,  445,  445,
+ /*  1200 */   549,  152,  445,  120,  161,  119,  548,  445,  152,  120,
+ /*  1210 */   161,  119,  445,  152,  445,  120,  161,  119,  445,  445,
+ /*  1220 */   445,  445,  445,  547,  445,  152,  445,  445,  445,  445,
+ /*  1230 */   445,  152,  120,  161,  119,  546,  445,  152,  445,  115,
+ /*  1240 */   445,  445,  116,  445,  120,  161,  119,  445,  120,  161,
+ /*  1250 */   119,  120,  161,  119,  152,   64,   63,   62,   61,   64,
+ /*  1260 */    63,   62,   61,  445,  445,  445,  152,  445,  445,  445,
+ /*  1270 */   152,  445,  445,  152,  445,  445,   50,  445,  445,  445,
+ /*  1280 */    53,   64,   63,   62,   61,  445,  445,  445,  445,  445,
+ /*  1290 */   445,  445,  445,  445,  445,  445,  445,  445,  445,  445,
+ /*  1300 */   445,  445,   56,
+};
+static const YYCODETYPE yy_lookahead[] = {
+ /*     0 */     0,  112,  113,  114,  133,  101,  102,  103,  105,  105,
+ /*    10 */    10,  112,  113,  114,  110,  111,  112,  113,  114,  105,
+ /*    20 */    20,   21,   22,  104,   24,  125,  107,  108,   28,    4,
+ /*    30 */     5,    6,    7,   33,   34,   35,   36,   37,  134,   39,
+ /*    40 */    40,   41,   42,  104,   44,   45,  107,  108,  106,   49,
+ /*    50 */    50,   51,   52,   53,   54,   55,   56,   57,   58,   59,
+ /*    60 */    60,   61,   62,   63,    0,  112,  113,  114,  129,  130,
+ /*    70 */   131,  103,   12,  105,   10,  112,  113,  114,  110,  111,
+ /*    80 */   112,  113,  114,  105,   20,   21,   22,   17,   24,  112,
+ /*    90 */   113,  114,   28,   10,    2,   25,   25,   33,   34,   35,
+ /*   100 */    36,   37,  134,   39,   40,   41,   42,    2,   44,   45,
+ /*   110 */   132,   28,  127,   49,   50,   51,   52,   53,   54,   55,
+ /*   120 */    56,   57,   58,   59,   60,   61,   62,   63,    1,    2,
+ /*   130 */    38,    4,    5,    4,    5,    6,    7,    4,    5,   12,
+ /*   140 */     3,   81,   15,   38,    1,  115,   17,    2,   88,  115,
+ /*   150 */    90,   24,  128,   26,   27,   12,    1,   14,   31,   32,
+ /*   160 */    19,   18,   16,   20,   21,   22,   23,   24,   17,   26,
+ /*   170 */    27,   15,   29,   30,   31,   32,   31,   32,   64,   65,
+ /*   180 */    66,   67,   68,   69,   70,   71,   72,   73,    6,    7,
+ /*   190 */    43,   64,   13,   48,   26,   27,  103,   48,  105,   29,
+ /*   200 */    30,   31,   32,  110,  111,  112,  113,  114,   81,   64,
+ /*   210 */    83,   84,   85,   86,   40,   88,   40,   90,   91,   92,
+ /*   220 */    93,   94,    1,    2,   87,    4,    5,  134,   83,   84,
+ /*   230 */    85,   86,   48,   12,   96,   97,   15,   41,   20,   21,
+ /*   240 */    22,   20,   21,   22,   41,    1,    2,   98,    4,    5,
+ /*   250 */    82,   47,   31,   32,   17,  103,   12,  105,   90,   15,
+ /*   260 */    26,   27,  110,  111,  112,  113,  114,   49,   50,   51,
+ /*   270 */    52,   53,   54,   89,   25,   31,   32,    1,   17,   95,
+ /*   280 */     4,    5,   98,   26,   27,   64,  134,   74,   12,    0,
+ /*   290 */    79,   15,   78,    3,    2,   80,   20,   21,   22,   10,
+ /*   300 */    24,   79,   81,    3,   83,   84,   85,   86,   64,   88,
+ /*   310 */     3,   90,   91,   92,   93,   94,   38,    2,    3,    4,
+ /*   320 */     5,    6,    7,   31,   32,   81,    3,   83,   84,   85,
+ /*   330 */    86,   16,   88,    3,   90,   91,   92,   93,   94,    3,
+ /*   340 */    25,   95,    4,    5,    6,    7,   31,   32,    1,    2,
+ /*   350 */    76,    4,    5,   38,   25,   17,   64,   81,   15,   12,
+ /*   360 */    15,   15,   15,   25,   88,   17,   90,   91,   92,   93,
+ /*   370 */    94,    4,    5,    6,    7,   83,   84,   85,   86,   28,
+ /*   380 */    28,   28,   67,   68,   12,   38,   89,    3,   11,  135,
+ /*   390 */    75,  135,   77,   78,   79,    2,    3,    4,    5,    6,
+ /*   400 */     7,    1,   35,  135,    4,    5,    4,    5,    6,    7,
+ /*   410 */    17,  135,   12,  135,  135,   15,  135,  135,   25,   17,
+ /*   420 */    20,   21,   22,  135,   31,   32,  135,   25,   81,  135,
+ /*   430 */   135,   38,  135,  135,  135,   88,  135,   90,   91,   92,
+ /*   440 */    93,   94,  135,    1,    2,  135,    4,    5,    4,    5,
+ /*   450 */     6,    7,    8,  135,   12,   26,   27,   15,  135,  135,
+ /*   460 */    67,   68,   99,  100,  101,  102,  135,  135,   75,  106,
+ /*   470 */    77,   78,   79,    2,    3,    4,    5,    6,    7,  135,
+ /*   480 */   117,   81,  135,  135,  121,  122,  135,  135,   88,  135,
+ /*   490 */    90,   91,   92,   93,   94,  135,   25,  103,  135,  105,
+ /*   500 */   135,  135,   31,   32,  110,  111,  112,  113,  114,   38,
+ /*   510 */   135,    1,  135,  135,    4,    5,  135,  135,  135,   90,
+ /*   520 */   135,  135,   12,   81,  135,   15,  135,  135,  134,  135,
+ /*   530 */    88,  135,   90,   91,   92,   93,   94,  135,   67,   68,
+ /*   540 */     1,    2,  135,    4,    5,  135,   75,  135,   77,   78,
+ /*   550 */    79,   12,  135,  135,   15,  135,   46,   47,    1,  135,
+ /*   560 */   135,    4,    5,   99,  100,  101,  102,  135,  135,   12,
+ /*   570 */   106,  135,   15,  135,    1,  135,  135,    4,    5,  135,
+ /*   580 */   135,  117,  135,  135,  135,   12,  122,  135,   15,  135,
+ /*   590 */   135,   81,  135,    4,    5,    6,    7,  135,   88,  135,
+ /*   600 */    90,   91,   92,   93,   94,    1,   17,  135,    4,    5,
+ /*   610 */   135,  135,  135,  135,  135,  103,   12,  105,  135,   15,
+ /*   620 */    81,  135,  110,  111,  112,  113,  114,   88,  135,   90,
+ /*   630 */    91,   92,   93,   94,  135,  100,  101,  102,   81,  135,
+ /*   640 */   135,  106,  135,  135,  135,   88,  134,   90,   91,   92,
+ /*   650 */    93,   94,  117,  135,   81,  135,  135,  122,  135,  135,
+ /*   660 */   135,   88,  135,   90,   91,   92,   93,   94,    1,  135,
+ /*   670 */   135,    4,    5,    4,    5,    6,    7,  135,  135,   12,
+ /*   680 */   135,  135,   15,  135,  135,   81,   17,    4,    5,    6,
+ /*   690 */     7,  135,   88,  135,   90,   91,   92,   93,   94,  135,
+ /*   700 */   135,  135,  103,  135,  105,  135,  103,  135,  135,  110,
+ /*   710 */   111,  112,  113,  114,  135,  112,  113,  114,   35,  103,
+ /*   720 */   135,  105,  119,  120,  135,  135,  110,  111,  112,  113,
+ /*   730 */   114,  135,  135,  134,  103,  135,  105,  134,  135,  135,
+ /*   740 */   135,  110,  111,  112,  113,  114,  135,  135,   81,  135,
+ /*   750 */   134,  135,  135,  103,  135,   88,  135,   90,   91,   92,
+ /*   760 */    93,   94,  112,  113,  114,  134,  116,  103,  135,  105,
+ /*   770 */   135,  103,  135,  135,  110,  111,  112,  113,  114,  135,
+ /*   780 */   112,  113,  114,  103,  134,  105,  118,  119,  120,  135,
+ /*   790 */   110,  111,  112,  113,  114,  135,  135,  135,  134,  103,
+ /*   800 */   135,  105,  134,  135,  135,  135,  110,  111,  112,  113,
+ /*   810 */   114,  135,  135,  103,  134,  105,  135,  135,  135,  135,
+ /*   820 */   110,  111,  112,  113,  114,  103,  135,  105,  135,  135,
+ /*   830 */   134,  135,  110,  111,  112,  113,  114,  135,  103,  135,
+ /*   840 */   105,  135,  135,  135,  134,  110,  111,  112,  113,  114,
+ /*   850 */   135,  135,  103,  135,  105,  135,  134,  135,  135,  110,
+ /*   860 */   111,  112,  113,  114,  103,  135,  105,  135,  135,  134,
+ /*   870 */   135,  110,  111,  112,  113,  114,  135,  135,  103,  135,
+ /*   880 */   105,  135,  103,  134,  135,  110,  111,  112,  113,  114,
+ /*   890 */   135,  112,  113,  114,  103,  134,  105,  103,  119,  120,
+ /*   900 */   135,  110,  111,  112,  113,  114,  112,  113,  114,  134,
+ /*   910 */   103,  135,  105,  134,  135,  135,  135,  110,  111,  112,
+ /*   920 */   113,  114,  135,  135,  103,  134,  105,  135,  134,  135,
+ /*   930 */   135,  110,  111,  112,  113,  114,  103,  135,  105,  135,
+ /*   940 */   135,  134,  135,  110,  111,  112,  113,  114,  135,  103,
+ /*   950 */   135,  105,  135,  135,  135,  134,  110,  111,  112,  113,
+ /*   960 */   114,  135,  135,  103,  135,  105,  135,  134,  135,  135,
+ /*   970 */   110,  111,  112,  113,  114,  103,  135,  105,  135,  135,
+ /*   980 */   134,  135,  110,  111,  112,  113,  114,  135,  135,  103,
+ /*   990 */   135,  105,  103,  135,  134,  135,  110,  111,  112,  113,
+ /*  1000 */   114,  112,  113,  114,  135,  116,  134,  135,  103,  135,
+ /*  1010 */   135,  135,  123,  124,  135,  135,  103,  112,  113,  114,
+ /*  1020 */   134,  135,  135,  134,  119,  112,  113,  114,  135,  116,
+ /*  1030 */   135,  126,  103,  128,  135,  103,  135,  124,  135,  134,
+ /*  1040 */   135,  112,  113,  114,  112,  113,  114,  134,  103,  135,
+ /*  1050 */   135,  119,    4,    5,    6,    7,  135,  112,  113,  114,
+ /*  1060 */   103,  116,  135,  134,  103,   17,  134,  103,  135,  112,
+ /*  1070 */   113,  114,  135,  112,  113,  114,  112,  113,  114,  134,
+ /*  1080 */   135,    4,    5,    6,    7,  135,  135,  135,  135,  135,
+ /*  1090 */   103,  134,  135,  103,   17,  134,  103,  135,  134,  112,
+ /*  1100 */   113,  114,  112,  113,  114,  112,  113,  114,  135,  103,
+ /*  1110 */     4,    5,    6,    7,  135,  135,  135,  135,  112,  113,
+ /*  1120 */   114,  134,  103,   17,  134,  135,  135,  134,  103,  135,
+ /*  1130 */   135,  112,  113,  114,  135,  103,  135,  112,  113,  114,
+ /*  1140 */   134,  103,  135,  135,  112,  113,  114,  103,  135,  135,
+ /*  1150 */   112,  113,  114,  134,  135,  135,  112,  113,  114,  134,
+ /*  1160 */   135,  135,  135,  135,  103,  135,  134,  135,  135,  135,
+ /*  1170 */   103,  135,  134,  112,  113,  114,  135,  103,  134,  112,
+ /*  1180 */   113,  114,  103,  135,  135,  135,  112,  113,  114,  135,
+ /*  1190 */   135,  112,  113,  114,  103,  134,  135,  135,  135,  135,
+ /*  1200 */   103,  134,  135,  112,  113,  114,  103,  135,  134,  112,
+ /*  1210 */   113,  114,  135,  134,  135,  112,  113,  114,  135,  135,
+ /*  1220 */   135,  135,  135,  103,  135,  134,  135,  135,  135,  135,
+ /*  1230 */   135,  134,  112,  113,  114,  103,  135,  134,  135,  103,
+ /*  1240 */   135,  135,  103,  135,  112,  113,  114,  135,  112,  113,
+ /*  1250 */   114,  112,  113,  114,  134,    4,    5,    6,    7,    4,
+ /*  1260 */     5,    6,    7,  135,  135,  135,  134,  135,  135,  135,
+ /*  1270 */   134,  135,  135,  134,  135,  135,   25,  135,  135,  135,
+ /*  1280 */    25,    4,    5,    6,    7,  135,  135,  135,  135,  135,
+ /*  1290 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1300 */   135,  135,   25,  135,  135,  135,  135,  135,  135,  135,
+ /*  1310 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1320 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1330 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1340 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1350 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1360 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1370 */   135,  135,  135,  135,  135,  135,  135,  135,  135,  135,
+ /*  1380 */   135,   99,   99,   99,   99,   99,   99,   99,   99,   99,
+ /*  1390 */    99,   99,   99,   99,   99,   99,   99,   99,   99,   99,
+ /*  1400 */    99,   99,
+};
+#define YY_SHIFT_COUNT    (163)
+#define YY_SHIFT_MIN      (0)
+#define YY_SHIFT_MAX      (1277)
+static const unsigned short int yy_shift_ofst[] = {
+ /*     0 */   143,  127,  221,  244,  244,  244,  244,  244,  244,  244,
+ /*    10 */   244,  244,  244,  244,  244,  244,  244,  244,  244,  244,
+ /*    20 */   244,  244,  244,  244,  244,  244,  244,  276,  510,  557,
+ /*    30 */   276,  143,  347,  347,    0,   64,  143,  573,  557,  573,
+ /*    40 */   400,  400,  400,  442,  539,  557,  557,  557,  557,  557,
+ /*    50 */   557,  604,  557,  557,  667,  557,  557,  557,  557,  557,
+ /*    60 */   557,  557,  557,  557,  557,  218,   60,   60,   60,   60,
+ /*    70 */    60,  145,  315,  393,  471,  292,  292,  170,   71, 1303,
+ /*    80 */  1303, 1303, 1303,  114,  114,  338,  402,  129,  444,  367,
+ /*    90 */   683,  589, 1251,  669, 1255, 1048, 1277, 1077, 1106,   25,
+ /*   100 */    25,   25,  184,   25,   25,   25,  168,   25,  429,   83,
+ /*   110 */    92,  105,   70,  133,  138,  182,  182,  234,  257,  137,
+ /*   120 */   149,  289,  141,  155,  151,  146,  156,  147,  174,  176,
+ /*   130 */   196,  203,  204,  179,  237,  249,  213,  261,  211,  214,
+ /*   140 */   215,  222,  290,  300,  307,  278,  323,  330,  336,  246,
+ /*   150 */   274,  329,  246,  343,  345,  346,  348,  351,  352,  353,
+ /*   160 */   372,  297,  384,  377,
+};
+#define YY_REDUCE_COUNT (82)
+#define YY_REDUCE_MIN   (-129)
+#define YY_REDUCE_MAX   (1139)
+static const short yy_reduce_ofst[] = {
+ /*     0 */   363,  -96,  -32,   93,  152,  394,  512,  599,  616,  631,
+ /*    10 */   664,  680,  696,  710,  722,  735,  749,  761,  775,  791,
+ /*    20 */   807,  821,  833,  846,  860,  872,  886,  889,  668,  905,
+ /*    30 */   913,  464,  603,  779,  -61,  -61,  535,  650,  932,  945,
+ /*    40 */   794,  929,  957,  961,  964,  987,  990,  993, 1006, 1019,
+ /*    50 */  1025, 1032, 1038, 1044, 1061, 1067, 1074, 1079, 1091, 1097,
+ /*    60 */  1103, 1120, 1132, 1136, 1139,  -81, -111, -101,  -47,  -37,
+ /*    70 */   -23,  -22, -129, -129, -129,  -97,  -86,  -58, -100,  -15,
+ /*    80 */    30,   34,   24,
+};
+static const YYACTIONTYPE yy_default[] = {
+ /*     0 */   449,  443,  443,  443,  443,  443,  443,  443,  443,  443,
+ /*    10 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  443,
+ /*    20 */   443,  443,  443,  443,  443,  443,  443,  443,  473,  576,
+ /*    30 */   443,  449,  580,  485,  581,  581,  449,  443,  443,  443,
+ /*    40 */   443,  443,  443,  443,  443,  443,  443,  443,  477,  443,
+ /*    50 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  443,
+ /*    60 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  443,
+ /*    70 */   443,  443,  443,  443,  443,  443,  443,  443,  455,  470,
+ /*    80 */   508,  508,  576,  468,  493,  443,  443,  443,  471,  443,
+ /*    90 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  488,
+ /*   100 */   486,  476,  459,  512,  511,  510,  443,  566,  443,  443,
+ /*   110 */   443,  443,  443,  588,  443,  545,  544,  540,  443,  532,
+ /*   120 */   529,  443,  443,  443,  443,  443,  443,  491,  443,  443,
+ /*   130 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  443,
+ /*   140 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  592,
+ /*   150 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  443,
+ /*   160 */   443,  601,  443,  443,
+};
+/********** End of lemon-generated parsing tables *****************************/
+
+/* The next table maps tokens (terminal symbols) into fallback tokens.  
+** If a construct like the following:
+** 
+**      %fallback ID X Y Z.
+**
+** appears in the grammar, then ID becomes a fallback token for X, Y,
+** and Z.  Whenever one of the tokens X, Y, or Z is input to the parser
+** but it does not parse, the type of the token is changed to ID and
+** the parse is retried before an error is thrown.
+**
+** This feature can be used, for example, to cause some keywords in a language
+** to revert to identifiers if they keyword does not apply in the context where
+** it appears.
+*/
+#ifdef YYFALLBACK
+static const YYCODETYPE yyFallback[] = {
+    0,  /*          $ => nothing */
+    0,  /*         ID => nothing */
+    1,  /*     EDGEPT => ID */
+    0,  /*         OF => nothing */
+    0,  /*       PLUS => nothing */
+    0,  /*      MINUS => nothing */
+    0,  /*       STAR => nothing */
+    0,  /*      SLASH => nothing */
+    0,  /*    PERCENT => nothing */
+    0,  /*     UMINUS => nothing */
+    0,  /*        EOL => nothing */
+    0,  /*     ASSIGN => nothing */
+    0,  /*  PLACENAME => nothing */
+    0,  /*      COLON => nothing */
+    0,  /*     ASSERT => nothing */
+    0,  /*         LP => nothing */
+    0,  /*         EQ => nothing */
+    0,  /*         RP => nothing */
+    0,  /*     DEFINE => nothing */
+    0,  /*  CODEBLOCK => nothing */
+    0,  /*       FILL => nothing */
+    0,  /*      COLOR => nothing */
+    0,  /*  THICKNESS => nothing */
+    0,  /*      PRINT => nothing */
+    0,  /*     STRING => nothing */
+    0,  /*      COMMA => nothing */
+    0,  /*  CLASSNAME => nothing */
+    0,  /*         LB => nothing */
+    0,  /*         RB => nothing */
+    0,  /*         UP => nothing */
+    0,  /*       DOWN => nothing */
+    0,  /*       LEFT => nothing */
+    0,  /*      RIGHT => nothing */
+    0,  /*      CLOSE => nothing */
+    0,  /*       CHOP => nothing */
+    0,  /*       FROM => nothing */
+    0,  /*         TO => nothing */
+    0,  /*       THEN => nothing */
+    0,  /*    HEADING => nothing */
+    0,  /*         GO => nothing */
+    0,  /*         AT => nothing */
+    0,  /*       WITH => nothing */
+    0,  /*       SAME => nothing */
+    0,  /*         AS => nothing */
+    0,  /*        FIT => nothing */
+    0,  /*     BEHIND => nothing */
+    0,  /*      UNTIL => nothing */
+    0,  /*       EVEN => nothing */
+    0,  /*      DOT_E => nothing */
+    0,  /*     HEIGHT => nothing */
+    0,  /*      WIDTH => nothing */
+    0,  /*     RADIUS => nothing */
+    0,  /*   DIAMETER => nothing */
+    0,  /*     DOTTED => nothing */
+    0,  /*     DASHED => nothing */
+    0,  /*         CW => nothing */
+    0,  /*        CCW => nothing */
+    0,  /*     LARROW => nothing */
+    0,  /*     RARROW => nothing */
+    0,  /*    LRARROW => nothing */
+    0,  /*      INVIS => nothing */
+    0,  /*      THICK => nothing */
+    0,  /*       THIN => nothing */
+    0,  /*      SOLID => nothing */
+    0,  /*     CENTER => nothing */
+    0,  /*      LJUST => nothing */
+    0,  /*      RJUST => nothing */
+    0,  /*      ABOVE => nothing */
+    0,  /*      BELOW => nothing */
+    0,  /*     ITALIC => nothing */
+    0,  /*       BOLD => nothing */
+    0,  /*    ALIGNED => nothing */
+    0,  /*        BIG => nothing */
+    0,  /*      SMALL => nothing */
+    0,  /*        AND => nothing */
+    0,  /*         LT => nothing */
+    0,  /*         GT => nothing */
+    0,  /*         ON => nothing */
+    0,  /*        WAY => nothing */
+    0,  /*    BETWEEN => nothing */
+    0,  /*        THE => nothing */
+    0,  /*        NTH => nothing */
+    0,  /*     VERTEX => nothing */
+    0,  /*        TOP => nothing */
+    0,  /*     BOTTOM => nothing */
+    0,  /*      START => nothing */
+    0,  /*        END => nothing */
+    0,  /*         IN => nothing */
+    0,  /*       THIS => nothing */
+    0,  /*      DOT_U => nothing */
+    0,  /*       LAST => nothing */
+    0,  /*     NUMBER => nothing */
+    0,  /*      FUNC1 => nothing */
+    0,  /*      FUNC2 => nothing */
+    0,  /*       DIST => nothing */
+    0,  /*     DOT_XY => nothing */
+    0,  /*          X => nothing */
+    0,  /*          Y => nothing */
+    0,  /*      DOT_L => nothing */
+};
+#endif /* YYFALLBACK */
+
+/* The following structure represents a single element of the
+** parser's stack.  Information stored includes:
+**
+**   +  The state number for the parser at this level of the stack.
+**
+**   +  The value of the token stored at this level of the stack.
+**      (In other words, the "major" token.)
+**
+**   +  The semantic value stored at this level of the stack.  This is
+**      the information used by the action routines in the grammar.
+**      It is sometimes called the "minor" token.
+**
+** After the "shift" half of a SHIFTREDUCE action, the stateno field
+** actually contains the reduce action for the second half of the
+** SHIFTREDUCE.
+*/
+struct yyStackEntry {
+  YYACTIONTYPE stateno;  /* The state-number, or reduce action in SHIFTREDUCE */
+  YYCODETYPE major;      /* The major token value.  This is the code
+                         ** number for the token at this stack level */
+  YYMINORTYPE minor;     /* The user-supplied minor token value.  This
+                         ** is the value of the token  */
+};
+typedef struct yyStackEntry yyStackEntry;
+
+/* The state of the parser is completely contained in an instance of
+** the following structure */
+struct yyParser {
+  yyStackEntry *yytos;          /* Pointer to top element of the stack */
+#ifdef YYTRACKMAXSTACKDEPTH
+  int yyhwm;                    /* High-water mark of the stack */
+#endif
+#ifndef YYNOERRORRECOVERY
+  int yyerrcnt;                 /* Shifts left before out of the error */
+#endif
+  pik_parserARG_SDECL                /* A place to hold %extra_argument */
+  pik_parserCTX_SDECL                /* A place to hold %extra_context */
+#if YYSTACKDEPTH<=0
+  int yystksz;                  /* Current side of the stack */
+  yyStackEntry *yystack;        /* The parser's stack */
+  yyStackEntry yystk0;          /* First stack entry */
+#else
+  yyStackEntry yystack[YYSTACKDEPTH];  /* The parser's stack */
+  yyStackEntry *yystackEnd;            /* Last entry in the stack */
+#endif
+};
+typedef struct yyParser yyParser;
+
+#include <assert.h>
+#ifndef NDEBUG
+#include <stdio.h>
+static FILE *yyTraceFILE = 0;
+static char *yyTracePrompt = 0;
+#endif /* NDEBUG */
+
+#ifndef NDEBUG
+/* 
+** Turn parser tracing on by giving a stream to which to write the trace
+** and a prompt to preface each trace message.  Tracing is turned off
+** by making either argument NULL 
+**
+** Inputs:
+** <ul>
+** <li> A FILE* to which trace output should be written.
+**      If NULL, then tracing is turned off.
+** <li> A prefix string written at the beginning of every
+**      line of trace output.  If NULL, then tracing is
+**      turned off.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void pik_parserTrace(FILE *TraceFILE, char *zTracePrompt){
+  yyTraceFILE = TraceFILE;
+  yyTracePrompt = zTracePrompt;
+  if( yyTraceFILE==0 ) yyTracePrompt = 0;
+  else if( yyTracePrompt==0 ) yyTraceFILE = 0;
+}
+#endif /* NDEBUG */
+
+#if defined(YYCOVERAGE) || !defined(NDEBUG)
+/* For tracing shifts, the names of all terminals and nonterminals
+** are required.  The following table supplies these names */
+static const char *const yyTokenName[] = { 
+  /*    0 */ "$",
+  /*    1 */ "ID",
+  /*    2 */ "EDGEPT",
+  /*    3 */ "OF",
+  /*    4 */ "PLUS",
+  /*    5 */ "MINUS",
+  /*    6 */ "STAR",
+  /*    7 */ "SLASH",
+  /*    8 */ "PERCENT",
+  /*    9 */ "UMINUS",
+  /*   10 */ "EOL",
+  /*   11 */ "ASSIGN",
+  /*   12 */ "PLACENAME",
+  /*   13 */ "COLON",
+  /*   14 */ "ASSERT",
+  /*   15 */ "LP",
+  /*   16 */ "EQ",
+  /*   17 */ "RP",
+  /*   18 */ "DEFINE",
+  /*   19 */ "CODEBLOCK",
+  /*   20 */ "FILL",
+  /*   21 */ "COLOR",
+  /*   22 */ "THICKNESS",
+  /*   23 */ "PRINT",
+  /*   24 */ "STRING",
+  /*   25 */ "COMMA",
+  /*   26 */ "CLASSNAME",
+  /*   27 */ "LB",
+  /*   28 */ "RB",
+  /*   29 */ "UP",
+  /*   30 */ "DOWN",
+  /*   31 */ "LEFT",
+  /*   32 */ "RIGHT",
+  /*   33 */ "CLOSE",
+  /*   34 */ "CHOP",
+  /*   35 */ "FROM",
+  /*   36 */ "TO",
+  /*   37 */ "THEN",
+  /*   38 */ "HEADING",
+  /*   39 */ "GO",
+  /*   40 */ "AT",
+  /*   41 */ "WITH",
+  /*   42 */ "SAME",
+  /*   43 */ "AS",
+  /*   44 */ "FIT",
+  /*   45 */ "BEHIND",
+  /*   46 */ "UNTIL",
+  /*   47 */ "EVEN",
+  /*   48 */ "DOT_E",
+  /*   49 */ "HEIGHT",
+  /*   50 */ "WIDTH",
+  /*   51 */ "RADIUS",
+  /*   52 */ "DIAMETER",
+  /*   53 */ "DOTTED",
+  /*   54 */ "DASHED",
+  /*   55 */ "CW",
+  /*   56 */ "CCW",
+  /*   57 */ "LARROW",
+  /*   58 */ "RARROW",
+  /*   59 */ "LRARROW",
+  /*   60 */ "INVIS",
+  /*   61 */ "THICK",
+  /*   62 */ "THIN",
+  /*   63 */ "SOLID",
+  /*   64 */ "CENTER",
+  /*   65 */ "LJUST",
+  /*   66 */ "RJUST",
+  /*   67 */ "ABOVE",
+  /*   68 */ "BELOW",
+  /*   69 */ "ITALIC",
+  /*   70 */ "BOLD",
+  /*   71 */ "ALIGNED",
+  /*   72 */ "BIG",
+  /*   73 */ "SMALL",
+  /*   74 */ "AND",
+  /*   75 */ "LT",
+  /*   76 */ "GT",
+  /*   77 */ "ON",
+  /*   78 */ "WAY",
+  /*   79 */ "BETWEEN",
+  /*   80 */ "THE",
+  /*   81 */ "NTH",
+  /*   82 */ "VERTEX",
+  /*   83 */ "TOP",
+  /*   84 */ "BOTTOM",
+  /*   85 */ "START",
+  /*   86 */ "END",
+  /*   87 */ "IN",
+  /*   88 */ "THIS",
+  /*   89 */ "DOT_U",
+  /*   90 */ "LAST",
+  /*   91 */ "NUMBER",
+  /*   92 */ "FUNC1",
+  /*   93 */ "FUNC2",
+  /*   94 */ "DIST",
+  /*   95 */ "DOT_XY",
+  /*   96 */ "X",
+  /*   97 */ "Y",
+  /*   98 */ "DOT_L",
+  /*   99 */ "statement_list",
+  /*  100 */ "statement",
+  /*  101 */ "unnamed_statement",
+  /*  102 */ "basetype",
+  /*  103 */ "expr",
+  /*  104 */ "numproperty",
+  /*  105 */ "edge",
+  /*  106 */ "direction",
+  /*  107 */ "dashproperty",
+  /*  108 */ "colorproperty",
+  /*  109 */ "locproperty",
+  /*  110 */ "position",
+  /*  111 */ "place",
+  /*  112 */ "object",
+  /*  113 */ "objectname",
+  /*  114 */ "nth",
+  /*  115 */ "textposition",
+  /*  116 */ "rvalue",
+  /*  117 */ "lvalue",
+  /*  118 */ "even",
+  /*  119 */ "relexpr",
+  /*  120 */ "optrelexpr",
+  /*  121 */ "document",
+  /*  122 */ "print",
+  /*  123 */ "prlist",
+  /*  124 */ "pritem",
+  /*  125 */ "prsep",
+  /*  126 */ "attribute_list",
+  /*  127 */ "savelist",
+  /*  128 */ "alist",
+  /*  129 */ "attribute",
+  /*  130 */ "go",
+  /*  131 */ "boolproperty",
+  /*  132 */ "withclause",
+  /*  133 */ "between",
+  /*  134 */ "place2",
+};
+#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */
+
+#ifndef NDEBUG
+/* For tracing reduce actions, the names of all rules are required.
+*/
+static const char *const yyRuleName[] = {
+ /*   0 */ "document ::= statement_list",
+ /*   1 */ "statement_list ::= statement",
+ /*   2 */ "statement_list ::= statement_list EOL statement",
+ /*   3 */ "statement ::=",
+ /*   4 */ "statement ::= direction",
+ /*   5 */ "statement ::= lvalue ASSIGN rvalue",
+ /*   6 */ "statement ::= PLACENAME COLON unnamed_statement",
+ /*   7 */ "statement ::= PLACENAME COLON position",
+ /*   8 */ "statement ::= unnamed_statement",
+ /*   9 */ "statement ::= print prlist",
+ /*  10 */ "statement ::= ASSERT LP expr EQ expr RP",
+ /*  11 */ "statement ::= ASSERT LP position EQ position RP",
+ /*  12 */ "statement ::= DEFINE ID CODEBLOCK",
+ /*  13 */ "rvalue ::= PLACENAME",
+ /*  14 */ "pritem ::= FILL",
+ /*  15 */ "pritem ::= COLOR",
+ /*  16 */ "pritem ::= THICKNESS",
+ /*  17 */ "pritem ::= rvalue",
+ /*  18 */ "pritem ::= STRING",
+ /*  19 */ "prsep ::= COMMA",
+ /*  20 */ "unnamed_statement ::= basetype attribute_list",
+ /*  21 */ "basetype ::= CLASSNAME",
+ /*  22 */ "basetype ::= STRING textposition",
+ /*  23 */ "basetype ::= LB savelist statement_list RB",
+ /*  24 */ "savelist ::=",
+ /*  25 */ "relexpr ::= expr",
+ /*  26 */ "relexpr ::= expr PERCENT",
+ /*  27 */ "optrelexpr ::=",
+ /*  28 */ "attribute_list ::= relexpr alist",
+ /*  29 */ "attribute ::= numproperty relexpr",
+ /*  30 */ "attribute ::= dashproperty expr",
+ /*  31 */ "attribute ::= dashproperty",
+ /*  32 */ "attribute ::= colorproperty rvalue",
+ /*  33 */ "attribute ::= go direction optrelexpr",
+ /*  34 */ "attribute ::= go direction even position",
+ /*  35 */ "attribute ::= CLOSE",
+ /*  36 */ "attribute ::= CHOP",
+ /*  37 */ "attribute ::= FROM position",
+ /*  38 */ "attribute ::= TO position",
+ /*  39 */ "attribute ::= THEN",
+ /*  40 */ "attribute ::= THEN optrelexpr HEADING expr",
+ /*  41 */ "attribute ::= THEN optrelexpr EDGEPT",
+ /*  42 */ "attribute ::= GO optrelexpr HEADING expr",
+ /*  43 */ "attribute ::= GO optrelexpr EDGEPT",
+ /*  44 */ "attribute ::= AT position",
+ /*  45 */ "attribute ::= SAME",
+ /*  46 */ "attribute ::= SAME AS object",
+ /*  47 */ "attribute ::= STRING textposition",
+ /*  48 */ "attribute ::= FIT",
+ /*  49 */ "attribute ::= BEHIND object",
+ /*  50 */ "withclause ::= DOT_E edge AT position",
+ /*  51 */ "withclause ::= edge AT position",
+ /*  52 */ "numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS",
+ /*  53 */ "boolproperty ::= CW",
+ /*  54 */ "boolproperty ::= CCW",
+ /*  55 */ "boolproperty ::= LARROW",
+ /*  56 */ "boolproperty ::= RARROW",
+ /*  57 */ "boolproperty ::= LRARROW",
+ /*  58 */ "boolproperty ::= INVIS",
+ /*  59 */ "boolproperty ::= THICK",
+ /*  60 */ "boolproperty ::= THIN",
+ /*  61 */ "boolproperty ::= SOLID",
+ /*  62 */ "textposition ::=",
+ /*  63 */ "textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL",
+ /*  64 */ "position ::= expr COMMA expr",
+ /*  65 */ "position ::= place PLUS expr COMMA expr",
+ /*  66 */ "position ::= place MINUS expr COMMA expr",
+ /*  67 */ "position ::= place PLUS LP expr COMMA expr RP",
+ /*  68 */ "position ::= place MINUS LP expr COMMA expr RP",
+ /*  69 */ "position ::= LP position COMMA position RP",
+ /*  70 */ "position ::= LP position RP",
+ /*  71 */ "position ::= expr between position AND position",
+ /*  72 */ "position ::= expr LT position COMMA position GT",
+ /*  73 */ "position ::= expr ABOVE position",
+ /*  74 */ "position ::= expr BELOW position",
+ /*  75 */ "position ::= expr LEFT OF position",
+ /*  76 */ "position ::= expr RIGHT OF position",
+ /*  77 */ "position ::= expr ON HEADING EDGEPT OF position",
+ /*  78 */ "position ::= expr HEADING EDGEPT OF position",
+ /*  79 */ "position ::= expr EDGEPT OF position",
+ /*  80 */ "position ::= expr ON HEADING expr FROM position",
+ /*  81 */ "position ::= expr HEADING expr FROM position",
+ /*  82 */ "place ::= edge OF object",
+ /*  83 */ "place2 ::= object",
+ /*  84 */ "place2 ::= object DOT_E edge",
+ /*  85 */ "place2 ::= NTH VERTEX OF object",
+ /*  86 */ "object ::= nth",
+ /*  87 */ "object ::= nth OF|IN object",
+ /*  88 */ "objectname ::= THIS",
+ /*  89 */ "objectname ::= PLACENAME",
+ /*  90 */ "objectname ::= objectname DOT_U PLACENAME",
+ /*  91 */ "nth ::= NTH CLASSNAME",
+ /*  92 */ "nth ::= NTH LAST CLASSNAME",
+ /*  93 */ "nth ::= LAST CLASSNAME",
+ /*  94 */ "nth ::= LAST",
+ /*  95 */ "nth ::= NTH LB RB",
+ /*  96 */ "nth ::= NTH LAST LB RB",
+ /*  97 */ "nth ::= LAST LB RB",
+ /*  98 */ "expr ::= expr PLUS expr",
+ /*  99 */ "expr ::= expr MINUS expr",
+ /* 100 */ "expr ::= expr STAR expr",
+ /* 101 */ "expr ::= expr SLASH expr",
+ /* 102 */ "expr ::= MINUS expr",
+ /* 103 */ "expr ::= PLUS expr",
+ /* 104 */ "expr ::= LP expr RP",
+ /* 105 */ "expr ::= LP FILL|COLOR|THICKNESS RP",
+ /* 106 */ "expr ::= NUMBER",
+ /* 107 */ "expr ::= ID",
+ /* 108 */ "expr ::= FUNC1 LP expr RP",
+ /* 109 */ "expr ::= FUNC2 LP expr COMMA expr RP",
+ /* 110 */ "expr ::= DIST LP position COMMA position RP",
+ /* 111 */ "expr ::= place2 DOT_XY X",
+ /* 112 */ "expr ::= place2 DOT_XY Y",
+ /* 113 */ "expr ::= object DOT_L numproperty",
+ /* 114 */ "expr ::= object DOT_L dashproperty",
+ /* 115 */ "expr ::= object DOT_L colorproperty",
+ /* 116 */ "lvalue ::= ID",
+ /* 117 */ "lvalue ::= FILL",
+ /* 118 */ "lvalue ::= COLOR",
+ /* 119 */ "lvalue ::= THICKNESS",
+ /* 120 */ "rvalue ::= expr",
+ /* 121 */ "print ::= PRINT",
+ /* 122 */ "prlist ::= pritem",
+ /* 123 */ "prlist ::= prlist prsep pritem",
+ /* 124 */ "direction ::= UP",
+ /* 125 */ "direction ::= DOWN",
+ /* 126 */ "direction ::= LEFT",
+ /* 127 */ "direction ::= RIGHT",
+ /* 128 */ "optrelexpr ::= relexpr",
+ /* 129 */ "attribute_list ::= alist",
+ /* 130 */ "alist ::=",
+ /* 131 */ "alist ::= alist attribute",
+ /* 132 */ "attribute ::= boolproperty",
+ /* 133 */ "attribute ::= WITH withclause",
+ /* 134 */ "go ::= GO",
+ /* 135 */ "go ::=",
+ /* 136 */ "even ::= UNTIL EVEN WITH",
+ /* 137 */ "even ::= EVEN WITH",
+ /* 138 */ "dashproperty ::= DOTTED",
+ /* 139 */ "dashproperty ::= DASHED",
+ /* 140 */ "colorproperty ::= FILL",
+ /* 141 */ "colorproperty ::= COLOR",
+ /* 142 */ "position ::= place",
+ /* 143 */ "between ::= WAY BETWEEN",
+ /* 144 */ "between ::= BETWEEN",
+ /* 145 */ "between ::= OF THE WAY BETWEEN",
+ /* 146 */ "place ::= place2",
+ /* 147 */ "edge ::= CENTER",
+ /* 148 */ "edge ::= EDGEPT",
+ /* 149 */ "edge ::= TOP",
+ /* 150 */ "edge ::= BOTTOM",
+ /* 151 */ "edge ::= START",
+ /* 152 */ "edge ::= END",
+ /* 153 */ "edge ::= RIGHT",
+ /* 154 */ "edge ::= LEFT",
+ /* 155 */ "object ::= objectname",
+};
+#endif /* NDEBUG */
+
+
+#if YYSTACKDEPTH<=0
+/*
+** Try to increase the size of the parser stack.  Return the number
+** of errors.  Return 0 on success.
+*/
+static int yyGrowStack(yyParser *p){
+  int newSize;
+  int idx;
+  yyStackEntry *pNew;
+
+  newSize = p->yystksz*2 + 100;
+  idx = p->yytos ? (int)(p->yytos - p->yystack) : 0;
+  if( p->yystack==&p->yystk0 ){
+    pNew = malloc(newSize*sizeof(pNew[0]));
+    if( pNew ) pNew[0] = p->yystk0;
+  }else{
+    pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
+  }
+  if( pNew ){
+    p->yystack = pNew;
+    p->yytos = &p->yystack[idx];
+#ifndef NDEBUG
+    if( yyTraceFILE ){
+      fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n",
+              yyTracePrompt, p->yystksz, newSize);
+    }
+#endif
+    p->yystksz = newSize;
+  }
+  return pNew==0; 
+}
+#endif
+
+/* Datatype of the argument to the memory allocated passed as the
+** second argument to pik_parserAlloc() below.  This can be changed by
+** putting an appropriate #define in the %include section of the input
+** grammar.
+*/
+#ifndef YYMALLOCARGTYPE
+# define YYMALLOCARGTYPE size_t
+#endif
+
+/* Initialize a new parser that has already been allocated.
+*/
+void pik_parserInit(void *yypRawParser pik_parserCTX_PDECL){
+  yyParser *yypParser = (yyParser*)yypRawParser;
+  pik_parserCTX_STORE
+#ifdef YYTRACKMAXSTACKDEPTH
+  yypParser->yyhwm = 0;
+#endif
+#if YYSTACKDEPTH<=0
+  yypParser->yytos = NULL;
+  yypParser->yystack = NULL;
+  yypParser->yystksz = 0;
+  if( yyGrowStack(yypParser) ){
+    yypParser->yystack = &yypParser->yystk0;
+    yypParser->yystksz = 1;
+  }
+#endif
+#ifndef YYNOERRORRECOVERY
+  yypParser->yyerrcnt = -1;
+#endif
+  yypParser->yytos = yypParser->yystack;
+  yypParser->yystack[0].stateno = 0;
+  yypParser->yystack[0].major = 0;
+#if YYSTACKDEPTH>0
+  yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1];
+#endif
+}
+
+#ifndef pik_parser_ENGINEALWAYSONSTACK
+/* 
+** This function allocates a new parser.
+** The only argument is a pointer to a function which works like
+** malloc.
+**
+** Inputs:
+** A pointer to the function used to allocate memory.
+**
+** Outputs:
+** A pointer to a parser.  This pointer is used in subsequent calls
+** to pik_parser and pik_parserFree.
+*/
+void *pik_parserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) pik_parserCTX_PDECL){
+  yyParser *yypParser;
+  yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) );
+  if( yypParser ){
+    pik_parserCTX_STORE
+    pik_parserInit(yypParser pik_parserCTX_PARAM);
+  }
+  return (void*)yypParser;
+}
+#endif /* pik_parser_ENGINEALWAYSONSTACK */
+
+
+/* The following function deletes the "minor type" or semantic value
+** associated with a symbol.  The symbol can be either a terminal
+** or nonterminal. "yymajor" is the symbol code, and "yypminor" is
+** a pointer to the value to be deleted.  The code used to do the 
+** deletions is derived from the %destructor and/or %token_destructor
+** directives of the input grammar.
+*/
+static void yy_destructor(
+  yyParser *yypParser,    /* The parser */
+  YYCODETYPE yymajor,     /* Type code for object to destroy */
+  YYMINORTYPE *yypminor   /* The object to be destroyed */
+){
+  pik_parserARG_FETCH
+  pik_parserCTX_FETCH
+  switch( yymajor ){
+    /* Here is inserted the actions which take place when a
+    ** terminal or non-terminal is destroyed.  This can happen
+    ** when the symbol is popped from the stack during a
+    ** reduce or during error processing or when a parser is 
+    ** being destroyed before it is finished parsing.
+    **
+    ** Note: during a reduce, the only symbols destroyed are those
+    ** which appear on the RHS of the rule, but which are *not* used
+    ** inside the C code.
+    */
+/********* Begin destructor definitions ***************************************/
+    case 99: /* statement_list */
+{
+#line 509 "pikchr.y"
+pik_elist_free(p,(yypminor->yy227));
+#line 1750 "pikchr.c"
+}
+      break;
+    case 100: /* statement */
+    case 101: /* unnamed_statement */
+    case 102: /* basetype */
+{
+#line 511 "pikchr.y"
+pik_elem_free(p,(yypminor->yy36));
+#line 1759 "pikchr.c"
+}
+      break;
+/********* End destructor definitions *****************************************/
+    default:  break;   /* If no destructor action specified: do nothing */
+  }
+}
+
+/*
+** Pop the parser's stack once.
+**
+** If there is a destructor routine associated with the token which
+** is popped from the stack, then call it.
+*/
+static void yy_pop_parser_stack(yyParser *pParser){
+  yyStackEntry *yytos;
+  assert( pParser->yytos!=0 );
+  assert( pParser->yytos > pParser->yystack );
+  yytos = pParser->yytos--;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sPopping %s\n",
+      yyTracePrompt,
+      yyTokenName[yytos->major]);
+  }
+#endif
+  yy_destructor(pParser, yytos->major, &yytos->minor);
+}
+
+/*
+** Clear all secondary memory allocations from the parser
+*/
+void pik_parserFinalize(void *p){
+  yyParser *pParser = (yyParser*)p;
+  while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+  if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack);
+#endif
+}
+
+#ifndef pik_parser_ENGINEALWAYSONSTACK
+/* 
+** Deallocate and destroy a parser.  Destructors are called for
+** all stack elements before shutting the parser down.
+**
+** If the YYPARSEFREENEVERNULL macro exists (for example because it
+** is defined in a %include section of the input grammar) then it is
+** assumed that the input pointer is never NULL.
+*/
+void pik_parserFree(
+  void *p,                    /* The parser to be deleted */
+  void (*freeProc)(void*)     /* Function used to reclaim memory */
+){
+#ifndef YYPARSEFREENEVERNULL
+  if( p==0 ) return;
+#endif
+  pik_parserFinalize(p);
+  (*freeProc)(p);
+}
+#endif /* pik_parser_ENGINEALWAYSONSTACK */
+
+/*
+** Return the peak depth of the stack for a parser.
+*/
+#ifdef YYTRACKMAXSTACKDEPTH
+int pik_parserStackPeak(void *p){
+  yyParser *pParser = (yyParser*)p;
+  return pParser->yyhwm;
+}
+#endif
+
+/* This array of booleans keeps track of the parser statement
+** coverage.  The element yycoverage[X][Y] is set when the parser
+** is in state X and has a lookahead token Y.  In a well-tested
+** systems, every element of this matrix should end up being set.
+*/
+#if defined(YYCOVERAGE)
+static unsigned char yycoverage[YYNSTATE][YYNTOKEN];
+#endif
+
+/*
+** Write into out a description of every state/lookahead combination that
+**
+**   (1)  has not been used by the parser, and
+**   (2)  is not a syntax error.
+**
+** Return the number of missed state/lookahead combinations.
+*/
+#if defined(YYCOVERAGE)
+int pik_parserCoverage(FILE *out){
+  int stateno, iLookAhead, i;
+  int nMissed = 0;
+  for(stateno=0; stateno<YYNSTATE; stateno++){
+    i = yy_shift_ofst[stateno];
+    for(iLookAhead=0; iLookAhead<YYNTOKEN; iLookAhead++){
+      if( yy_lookahead[i+iLookAhead]!=iLookAhead ) continue;
+      if( yycoverage[stateno][iLookAhead]==0 ) nMissed++;
+      if( out ){
+        fprintf(out,"State %d lookahead %s %s\n", stateno,
+                yyTokenName[iLookAhead],
+                yycoverage[stateno][iLookAhead] ? "ok" : "missed");
+      }
+    }
+  }
+  return nMissed;
+}
+#endif
+
+/*
+** Find the appropriate action for a parser given the terminal
+** look-ahead token iLookAhead.
+*/
+static YYACTIONTYPE yy_find_shift_action(
+  YYCODETYPE iLookAhead,    /* The look-ahead token */
+  YYACTIONTYPE stateno      /* Current state number */
+){
+  int i;
+
+  if( stateno>YY_MAX_SHIFT ) return stateno;
+  assert( stateno <= YY_SHIFT_COUNT );
+#if defined(YYCOVERAGE)
+  yycoverage[stateno][iLookAhead] = 1;
+#endif
+  do{
+    i = yy_shift_ofst[stateno];
+    assert( i>=0 );
+    assert( i<=YY_ACTTAB_COUNT );
+    assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD );
+    assert( iLookAhead!=YYNOCODE );
+    assert( iLookAhead < YYNTOKEN );
+    i += iLookAhead;
+    assert( i<(int)YY_NLOOKAHEAD );
+    if( yy_lookahead[i]!=iLookAhead ){
+#ifdef YYFALLBACK
+      YYCODETYPE iFallback;            /* Fallback token */
+      assert( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) );
+      iFallback = yyFallback[iLookAhead];
+      if( iFallback!=0 ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
+             yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
+        }
+#endif
+        assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */
+        iLookAhead = iFallback;
+        continue;
+      }
+#endif
+#ifdef YYWILDCARD
+      {
+        int j = i - iLookAhead + YYWILDCARD;
+        assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) );
+        if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){
+#ifndef NDEBUG
+          if( yyTraceFILE ){
+            fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
+               yyTracePrompt, yyTokenName[iLookAhead],
+               yyTokenName[YYWILDCARD]);
+          }
+#endif /* NDEBUG */
+          return yy_action[j];
+        }
+      }
+#endif /* YYWILDCARD */
+      return yy_default[stateno];
+    }else{
+      assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) );
+      return yy_action[i];
+    }
+  }while(1);
+}
+
+/*
+** Find the appropriate action for a parser given the non-terminal
+** look-ahead token iLookAhead.
+*/
+static YYACTIONTYPE yy_find_reduce_action(
+  YYACTIONTYPE stateno,     /* Current state number */
+  YYCODETYPE iLookAhead     /* The look-ahead token */
+){
+  int i;
+#ifdef YYERRORSYMBOL
+  if( stateno>YY_REDUCE_COUNT ){
+    return yy_default[stateno];
+  }
+#else
+  assert( stateno<=YY_REDUCE_COUNT );
+#endif
+  i = yy_reduce_ofst[stateno];
+  assert( iLookAhead!=YYNOCODE );
+  i += iLookAhead;
+#ifdef YYERRORSYMBOL
+  if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){
+    return yy_default[stateno];
+  }
+#else
+  assert( i>=0 && i<YY_ACTTAB_COUNT );
+  assert( yy_lookahead[i]==iLookAhead );
+#endif
+  return yy_action[i];
+}
+
+/*
+** The following routine is called if the stack overflows.
+*/
+static void yyStackOverflow(yyParser *yypParser){
+   pik_parserARG_FETCH
+   pik_parserCTX_FETCH
+#ifndef NDEBUG
+   if( yyTraceFILE ){
+     fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
+   }
+#endif
+   while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
+   /* Here code is inserted which will execute if the parser
+   ** stack every overflows */
+/******** Begin %stack_overflow code ******************************************/
+#line 543 "pikchr.y"
+
+  pik_error(p, 0, "parser stack overflow");
+#line 1980 "pikchr.c"
+/******** End %stack_overflow code ********************************************/
+   pik_parserARG_STORE /* Suppress warning about unused %extra_argument var */
+   pik_parserCTX_STORE
+}
+
+/*
+** Print tracing information for a SHIFT action
+*/
+#ifndef NDEBUG
+static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){
+  if( yyTraceFILE ){
+    if( yyNewState<YYNSTATE ){
+      fprintf(yyTraceFILE,"%s%s '%s', go to state %d\n",
+         yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+         yyNewState);
+    }else{
+      fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n",
+         yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+         yyNewState - YY_MIN_REDUCE);
+    }
+  }
+}
+#else
+# define yyTraceShift(X,Y,Z)
+#endif
+
+/*
+** Perform a shift action.
+*/
+static void yy_shift(
+  yyParser *yypParser,          /* The parser to be shifted */
+  YYACTIONTYPE yyNewState,      /* The new state to shift in */
+  YYCODETYPE yyMajor,           /* The major token to shift in */
+  pik_parserTOKENTYPE yyMinor        /* The minor token to shift in */
+){
+  yyStackEntry *yytos;
+  yypParser->yytos++;
+#ifdef YYTRACKMAXSTACKDEPTH
+  if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
+    yypParser->yyhwm++;
+    assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) );
+  }
+#endif
+#if YYSTACKDEPTH>0 
+  if( yypParser->yytos>yypParser->yystackEnd ){
+    yypParser->yytos--;
+    yyStackOverflow(yypParser);
+    return;
+  }
+#else
+  if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){
+    if( yyGrowStack(yypParser) ){
+      yypParser->yytos--;
+      yyStackOverflow(yypParser);
+      return;
+    }
+  }
+#endif
+  if( yyNewState > YY_MAX_SHIFT ){
+    yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
+  }
+  yytos = yypParser->yytos;
+  yytos->stateno = yyNewState;
+  yytos->major = yyMajor;
+  yytos->minor.yy0 = yyMinor;
+  yyTraceShift(yypParser, yyNewState, "Shift");
+}
+
+/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side
+** of that rule */
+static const YYCODETYPE yyRuleInfoLhs[] = {
+   121,  /* (0) document ::= statement_list */
+    99,  /* (1) statement_list ::= statement */
+    99,  /* (2) statement_list ::= statement_list EOL statement */
+   100,  /* (3) statement ::= */
+   100,  /* (4) statement ::= direction */
+   100,  /* (5) statement ::= lvalue ASSIGN rvalue */
+   100,  /* (6) statement ::= PLACENAME COLON unnamed_statement */
+   100,  /* (7) statement ::= PLACENAME COLON position */
+   100,  /* (8) statement ::= unnamed_statement */
+   100,  /* (9) statement ::= print prlist */
+   100,  /* (10) statement ::= ASSERT LP expr EQ expr RP */
+   100,  /* (11) statement ::= ASSERT LP position EQ position RP */
+   100,  /* (12) statement ::= DEFINE ID CODEBLOCK */
+   116,  /* (13) rvalue ::= PLACENAME */
+   124,  /* (14) pritem ::= FILL */
+   124,  /* (15) pritem ::= COLOR */
+   124,  /* (16) pritem ::= THICKNESS */
+   124,  /* (17) pritem ::= rvalue */
+   124,  /* (18) pritem ::= STRING */
+   125,  /* (19) prsep ::= COMMA */
+   101,  /* (20) unnamed_statement ::= basetype attribute_list */
+   102,  /* (21) basetype ::= CLASSNAME */
+   102,  /* (22) basetype ::= STRING textposition */
+   102,  /* (23) basetype ::= LB savelist statement_list RB */
+   127,  /* (24) savelist ::= */
+   119,  /* (25) relexpr ::= expr */
+   119,  /* (26) relexpr ::= expr PERCENT */
+   120,  /* (27) optrelexpr ::= */
+   126,  /* (28) attribute_list ::= relexpr alist */
+   129,  /* (29) attribute ::= numproperty relexpr */
+   129,  /* (30) attribute ::= dashproperty expr */
+   129,  /* (31) attribute ::= dashproperty */
+   129,  /* (32) attribute ::= colorproperty rvalue */
+   129,  /* (33) attribute ::= go direction optrelexpr */
+   129,  /* (34) attribute ::= go direction even position */
+   129,  /* (35) attribute ::= CLOSE */
+   129,  /* (36) attribute ::= CHOP */
+   129,  /* (37) attribute ::= FROM position */
+   129,  /* (38) attribute ::= TO position */
+   129,  /* (39) attribute ::= THEN */
+   129,  /* (40) attribute ::= THEN optrelexpr HEADING expr */
+   129,  /* (41) attribute ::= THEN optrelexpr EDGEPT */
+   129,  /* (42) attribute ::= GO optrelexpr HEADING expr */
+   129,  /* (43) attribute ::= GO optrelexpr EDGEPT */
+   129,  /* (44) attribute ::= AT position */
+   129,  /* (45) attribute ::= SAME */
+   129,  /* (46) attribute ::= SAME AS object */
+   129,  /* (47) attribute ::= STRING textposition */
+   129,  /* (48) attribute ::= FIT */
+   129,  /* (49) attribute ::= BEHIND object */
+   132,  /* (50) withclause ::= DOT_E edge AT position */
+   132,  /* (51) withclause ::= edge AT position */
+   104,  /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
+   131,  /* (53) boolproperty ::= CW */
+   131,  /* (54) boolproperty ::= CCW */
+   131,  /* (55) boolproperty ::= LARROW */
+   131,  /* (56) boolproperty ::= RARROW */
+   131,  /* (57) boolproperty ::= LRARROW */
+   131,  /* (58) boolproperty ::= INVIS */
+   131,  /* (59) boolproperty ::= THICK */
+   131,  /* (60) boolproperty ::= THIN */
+   131,  /* (61) boolproperty ::= SOLID */
+   115,  /* (62) textposition ::= */
+   115,  /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */
+   110,  /* (64) position ::= expr COMMA expr */
+   110,  /* (65) position ::= place PLUS expr COMMA expr */
+   110,  /* (66) position ::= place MINUS expr COMMA expr */
+   110,  /* (67) position ::= place PLUS LP expr COMMA expr RP */
+   110,  /* (68) position ::= place MINUS LP expr COMMA expr RP */
+   110,  /* (69) position ::= LP position COMMA position RP */
+   110,  /* (70) position ::= LP position RP */
+   110,  /* (71) position ::= expr between position AND position */
+   110,  /* (72) position ::= expr LT position COMMA position GT */
+   110,  /* (73) position ::= expr ABOVE position */
+   110,  /* (74) position ::= expr BELOW position */
+   110,  /* (75) position ::= expr LEFT OF position */
+   110,  /* (76) position ::= expr RIGHT OF position */
+   110,  /* (77) position ::= expr ON HEADING EDGEPT OF position */
+   110,  /* (78) position ::= expr HEADING EDGEPT OF position */
+   110,  /* (79) position ::= expr EDGEPT OF position */
+   110,  /* (80) position ::= expr ON HEADING expr FROM position */
+   110,  /* (81) position ::= expr HEADING expr FROM position */
+   111,  /* (82) place ::= edge OF object */
+   134,  /* (83) place2 ::= object */
+   134,  /* (84) place2 ::= object DOT_E edge */
+   134,  /* (85) place2 ::= NTH VERTEX OF object */
+   112,  /* (86) object ::= nth */
+   112,  /* (87) object ::= nth OF|IN object */
+   113,  /* (88) objectname ::= THIS */
+   113,  /* (89) objectname ::= PLACENAME */
+   113,  /* (90) objectname ::= objectname DOT_U PLACENAME */
+   114,  /* (91) nth ::= NTH CLASSNAME */
+   114,  /* (92) nth ::= NTH LAST CLASSNAME */
+   114,  /* (93) nth ::= LAST CLASSNAME */
+   114,  /* (94) nth ::= LAST */
+   114,  /* (95) nth ::= NTH LB RB */
+   114,  /* (96) nth ::= NTH LAST LB RB */
+   114,  /* (97) nth ::= LAST LB RB */
+   103,  /* (98) expr ::= expr PLUS expr */
+   103,  /* (99) expr ::= expr MINUS expr */
+   103,  /* (100) expr ::= expr STAR expr */
+   103,  /* (101) expr ::= expr SLASH expr */
+   103,  /* (102) expr ::= MINUS expr */
+   103,  /* (103) expr ::= PLUS expr */
+   103,  /* (104) expr ::= LP expr RP */
+   103,  /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */
+   103,  /* (106) expr ::= NUMBER */
+   103,  /* (107) expr ::= ID */
+   103,  /* (108) expr ::= FUNC1 LP expr RP */
+   103,  /* (109) expr ::= FUNC2 LP expr COMMA expr RP */
+   103,  /* (110) expr ::= DIST LP position COMMA position RP */
+   103,  /* (111) expr ::= place2 DOT_XY X */
+   103,  /* (112) expr ::= place2 DOT_XY Y */
+   103,  /* (113) expr ::= object DOT_L numproperty */
+   103,  /* (114) expr ::= object DOT_L dashproperty */
+   103,  /* (115) expr ::= object DOT_L colorproperty */
+   117,  /* (116) lvalue ::= ID */
+   117,  /* (117) lvalue ::= FILL */
+   117,  /* (118) lvalue ::= COLOR */
+   117,  /* (119) lvalue ::= THICKNESS */
+   116,  /* (120) rvalue ::= expr */
+   122,  /* (121) print ::= PRINT */
+   123,  /* (122) prlist ::= pritem */
+   123,  /* (123) prlist ::= prlist prsep pritem */
+   106,  /* (124) direction ::= UP */
+   106,  /* (125) direction ::= DOWN */
+   106,  /* (126) direction ::= LEFT */
+   106,  /* (127) direction ::= RIGHT */
+   120,  /* (128) optrelexpr ::= relexpr */
+   126,  /* (129) attribute_list ::= alist */
+   128,  /* (130) alist ::= */
+   128,  /* (131) alist ::= alist attribute */
+   129,  /* (132) attribute ::= boolproperty */
+   129,  /* (133) attribute ::= WITH withclause */
+   130,  /* (134) go ::= GO */
+   130,  /* (135) go ::= */
+   118,  /* (136) even ::= UNTIL EVEN WITH */
+   118,  /* (137) even ::= EVEN WITH */
+   107,  /* (138) dashproperty ::= DOTTED */
+   107,  /* (139) dashproperty ::= DASHED */
+   108,  /* (140) colorproperty ::= FILL */
+   108,  /* (141) colorproperty ::= COLOR */
+   110,  /* (142) position ::= place */
+   133,  /* (143) between ::= WAY BETWEEN */
+   133,  /* (144) between ::= BETWEEN */
+   133,  /* (145) between ::= OF THE WAY BETWEEN */
+   111,  /* (146) place ::= place2 */
+   105,  /* (147) edge ::= CENTER */
+   105,  /* (148) edge ::= EDGEPT */
+   105,  /* (149) edge ::= TOP */
+   105,  /* (150) edge ::= BOTTOM */
+   105,  /* (151) edge ::= START */
+   105,  /* (152) edge ::= END */
+   105,  /* (153) edge ::= RIGHT */
+   105,  /* (154) edge ::= LEFT */
+   112,  /* (155) object ::= objectname */
+};
+
+/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number
+** of symbols on the right-hand side of that rule. */
+static const signed char yyRuleInfoNRhs[] = {
+   -1,  /* (0) document ::= statement_list */
+   -1,  /* (1) statement_list ::= statement */
+   -3,  /* (2) statement_list ::= statement_list EOL statement */
+    0,  /* (3) statement ::= */
+   -1,  /* (4) statement ::= direction */
+   -3,  /* (5) statement ::= lvalue ASSIGN rvalue */
+   -3,  /* (6) statement ::= PLACENAME COLON unnamed_statement */
+   -3,  /* (7) statement ::= PLACENAME COLON position */
+   -1,  /* (8) statement ::= unnamed_statement */
+   -2,  /* (9) statement ::= print prlist */
+   -6,  /* (10) statement ::= ASSERT LP expr EQ expr RP */
+   -6,  /* (11) statement ::= ASSERT LP position EQ position RP */
+   -3,  /* (12) statement ::= DEFINE ID CODEBLOCK */
+   -1,  /* (13) rvalue ::= PLACENAME */
+   -1,  /* (14) pritem ::= FILL */
+   -1,  /* (15) pritem ::= COLOR */
+   -1,  /* (16) pritem ::= THICKNESS */
+   -1,  /* (17) pritem ::= rvalue */
+   -1,  /* (18) pritem ::= STRING */
+   -1,  /* (19) prsep ::= COMMA */
+   -2,  /* (20) unnamed_statement ::= basetype attribute_list */
+   -1,  /* (21) basetype ::= CLASSNAME */
+   -2,  /* (22) basetype ::= STRING textposition */
+   -4,  /* (23) basetype ::= LB savelist statement_list RB */
+    0,  /* (24) savelist ::= */
+   -1,  /* (25) relexpr ::= expr */
+   -2,  /* (26) relexpr ::= expr PERCENT */
+    0,  /* (27) optrelexpr ::= */
+   -2,  /* (28) attribute_list ::= relexpr alist */
+   -2,  /* (29) attribute ::= numproperty relexpr */
+   -2,  /* (30) attribute ::= dashproperty expr */
+   -1,  /* (31) attribute ::= dashproperty */
+   -2,  /* (32) attribute ::= colorproperty rvalue */
+   -3,  /* (33) attribute ::= go direction optrelexpr */
+   -4,  /* (34) attribute ::= go direction even position */
+   -1,  /* (35) attribute ::= CLOSE */
+   -1,  /* (36) attribute ::= CHOP */
+   -2,  /* (37) attribute ::= FROM position */
+   -2,  /* (38) attribute ::= TO position */
+   -1,  /* (39) attribute ::= THEN */
+   -4,  /* (40) attribute ::= THEN optrelexpr HEADING expr */
+   -3,  /* (41) attribute ::= THEN optrelexpr EDGEPT */
+   -4,  /* (42) attribute ::= GO optrelexpr HEADING expr */
+   -3,  /* (43) attribute ::= GO optrelexpr EDGEPT */
+   -2,  /* (44) attribute ::= AT position */
+   -1,  /* (45) attribute ::= SAME */
+   -3,  /* (46) attribute ::= SAME AS object */
+   -2,  /* (47) attribute ::= STRING textposition */
+   -1,  /* (48) attribute ::= FIT */
+   -2,  /* (49) attribute ::= BEHIND object */
+   -4,  /* (50) withclause ::= DOT_E edge AT position */
+   -3,  /* (51) withclause ::= edge AT position */
+   -1,  /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
+   -1,  /* (53) boolproperty ::= CW */
+   -1,  /* (54) boolproperty ::= CCW */
+   -1,  /* (55) boolproperty ::= LARROW */
+   -1,  /* (56) boolproperty ::= RARROW */
+   -1,  /* (57) boolproperty ::= LRARROW */
+   -1,  /* (58) boolproperty ::= INVIS */
+   -1,  /* (59) boolproperty ::= THICK */
+   -1,  /* (60) boolproperty ::= THIN */
+   -1,  /* (61) boolproperty ::= SOLID */
+    0,  /* (62) textposition ::= */
+   -2,  /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */
+   -3,  /* (64) position ::= expr COMMA expr */
+   -5,  /* (65) position ::= place PLUS expr COMMA expr */
+   -5,  /* (66) position ::= place MINUS expr COMMA expr */
+   -7,  /* (67) position ::= place PLUS LP expr COMMA expr RP */
+   -7,  /* (68) position ::= place MINUS LP expr COMMA expr RP */
+   -5,  /* (69) position ::= LP position COMMA position RP */
+   -3,  /* (70) position ::= LP position RP */
+   -5,  /* (71) position ::= expr between position AND position */
+   -6,  /* (72) position ::= expr LT position COMMA position GT */
+   -3,  /* (73) position ::= expr ABOVE position */
+   -3,  /* (74) position ::= expr BELOW position */
+   -4,  /* (75) position ::= expr LEFT OF position */
+   -4,  /* (76) position ::= expr RIGHT OF position */
+   -6,  /* (77) position ::= expr ON HEADING EDGEPT OF position */
+   -5,  /* (78) position ::= expr HEADING EDGEPT OF position */
+   -4,  /* (79) position ::= expr EDGEPT OF position */
+   -6,  /* (80) position ::= expr ON HEADING expr FROM position */
+   -5,  /* (81) position ::= expr HEADING expr FROM position */
+   -3,  /* (82) place ::= edge OF object */
+   -1,  /* (83) place2 ::= object */
+   -3,  /* (84) place2 ::= object DOT_E edge */
+   -4,  /* (85) place2 ::= NTH VERTEX OF object */
+   -1,  /* (86) object ::= nth */
+   -3,  /* (87) object ::= nth OF|IN object */
+   -1,  /* (88) objectname ::= THIS */
+   -1,  /* (89) objectname ::= PLACENAME */
+   -3,  /* (90) objectname ::= objectname DOT_U PLACENAME */
+   -2,  /* (91) nth ::= NTH CLASSNAME */
+   -3,  /* (92) nth ::= NTH LAST CLASSNAME */
+   -2,  /* (93) nth ::= LAST CLASSNAME */
+   -1,  /* (94) nth ::= LAST */
+   -3,  /* (95) nth ::= NTH LB RB */
+   -4,  /* (96) nth ::= NTH LAST LB RB */
+   -3,  /* (97) nth ::= LAST LB RB */
+   -3,  /* (98) expr ::= expr PLUS expr */
+   -3,  /* (99) expr ::= expr MINUS expr */
+   -3,  /* (100) expr ::= expr STAR expr */
+   -3,  /* (101) expr ::= expr SLASH expr */
+   -2,  /* (102) expr ::= MINUS expr */
+   -2,  /* (103) expr ::= PLUS expr */
+   -3,  /* (104) expr ::= LP expr RP */
+   -3,  /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */
+   -1,  /* (106) expr ::= NUMBER */
+   -1,  /* (107) expr ::= ID */
+   -4,  /* (108) expr ::= FUNC1 LP expr RP */
+   -6,  /* (109) expr ::= FUNC2 LP expr COMMA expr RP */
+   -6,  /* (110) expr ::= DIST LP position COMMA position RP */
+   -3,  /* (111) expr ::= place2 DOT_XY X */
+   -3,  /* (112) expr ::= place2 DOT_XY Y */
+   -3,  /* (113) expr ::= object DOT_L numproperty */
+   -3,  /* (114) expr ::= object DOT_L dashproperty */
+   -3,  /* (115) expr ::= object DOT_L colorproperty */
+   -1,  /* (116) lvalue ::= ID */
+   -1,  /* (117) lvalue ::= FILL */
+   -1,  /* (118) lvalue ::= COLOR */
+   -1,  /* (119) lvalue ::= THICKNESS */
+   -1,  /* (120) rvalue ::= expr */
+   -1,  /* (121) print ::= PRINT */
+   -1,  /* (122) prlist ::= pritem */
+   -3,  /* (123) prlist ::= prlist prsep pritem */
+   -1,  /* (124) direction ::= UP */
+   -1,  /* (125) direction ::= DOWN */
+   -1,  /* (126) direction ::= LEFT */
+   -1,  /* (127) direction ::= RIGHT */
+   -1,  /* (128) optrelexpr ::= relexpr */
+   -1,  /* (129) attribute_list ::= alist */
+    0,  /* (130) alist ::= */
+   -2,  /* (131) alist ::= alist attribute */
+   -1,  /* (132) attribute ::= boolproperty */
+   -2,  /* (133) attribute ::= WITH withclause */
+   -1,  /* (134) go ::= GO */
+    0,  /* (135) go ::= */
+   -3,  /* (136) even ::= UNTIL EVEN WITH */
+   -2,  /* (137) even ::= EVEN WITH */
+   -1,  /* (138) dashproperty ::= DOTTED */
+   -1,  /* (139) dashproperty ::= DASHED */
+   -1,  /* (140) colorproperty ::= FILL */
+   -1,  /* (141) colorproperty ::= COLOR */
+   -1,  /* (142) position ::= place */
+   -2,  /* (143) between ::= WAY BETWEEN */
+   -1,  /* (144) between ::= BETWEEN */
+   -4,  /* (145) between ::= OF THE WAY BETWEEN */
+   -1,  /* (146) place ::= place2 */
+   -1,  /* (147) edge ::= CENTER */
+   -1,  /* (148) edge ::= EDGEPT */
+   -1,  /* (149) edge ::= TOP */
+   -1,  /* (150) edge ::= BOTTOM */
+   -1,  /* (151) edge ::= START */
+   -1,  /* (152) edge ::= END */
+   -1,  /* (153) edge ::= RIGHT */
+   -1,  /* (154) edge ::= LEFT */
+   -1,  /* (155) object ::= objectname */
+};
+
+static void yy_accept(yyParser*);  /* Forward Declaration */
+
+/*
+** Perform a reduce action and the shift that must immediately
+** follow the reduce.
+**
+** The yyLookahead and yyLookaheadToken parameters provide reduce actions
+** access to the lookahead token (if any).  The yyLookahead will be YYNOCODE
+** if the lookahead token has already been consumed.  As this procedure is
+** only called from one place, optimizing compilers will in-line it, which
+** means that the extra parameters have no performance impact.
+*/
+static YYACTIONTYPE yy_reduce(
+  yyParser *yypParser,         /* The parser */
+  unsigned int yyruleno,       /* Number of the rule by which to reduce */
+  int yyLookahead,             /* Lookahead token, or YYNOCODE if none */
+  pik_parserTOKENTYPE yyLookaheadToken  /* Value of the lookahead token */
+  pik_parserCTX_PDECL                   /* %extra_context */
+){
+  int yygoto;                     /* The next state */
+  YYACTIONTYPE yyact;             /* The next action */
+  yyStackEntry *yymsp;            /* The top of the parser's stack */
+  int yysize;                     /* Amount to pop the stack */
+  pik_parserARG_FETCH
+  (void)yyLookahead;
+  (void)yyLookaheadToken;
+  yymsp = yypParser->yytos;
+
+  switch( yyruleno ){
+  /* Beginning here are the reduction cases.  A typical example
+  ** follows:
+  **   case 0:
+  **  #line <lineno> <grammarfile>
+  **     { ... }           // User supplied code
+  **  #line <lineno> <thisfile>
+  **     break;
+  */
+/********** Begin reduce actions **********************************************/
+        YYMINORTYPE yylhsminor;
+      case 0: /* document ::= statement_list */
+#line 547 "pikchr.y"
+{pik_render(p,yymsp[0].minor.yy227);}
+#line 2413 "pikchr.c"
+        break;
+      case 1: /* statement_list ::= statement */
+#line 550 "pikchr.y"
+{ yylhsminor.yy227 = pik_elist_append(p,0,yymsp[0].minor.yy36); }
+#line 2418 "pikchr.c"
+  yymsp[0].minor.yy227 = yylhsminor.yy227;
+        break;
+      case 2: /* statement_list ::= statement_list EOL statement */
+#line 552 "pikchr.y"
+{ yylhsminor.yy227 = pik_elist_append(p,yymsp[-2].minor.yy227,yymsp[0].minor.yy36); }
+#line 2424 "pikchr.c"
+  yymsp[-2].minor.yy227 = yylhsminor.yy227;
+        break;
+      case 3: /* statement ::= */
+#line 555 "pikchr.y"
+{ yymsp[1].minor.yy36 = 0; }
+#line 2430 "pikchr.c"
+        break;
+      case 4: /* statement ::= direction */
+#line 556 "pikchr.y"
+{ pik_set_direction(p,yymsp[0].minor.yy0.eCode);  yylhsminor.yy36=0; }
+#line 2435 "pikchr.c"
+  yymsp[0].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 5: /* statement ::= lvalue ASSIGN rvalue */
+#line 557 "pikchr.y"
+{pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy153,&yymsp[-1].minor.yy0); yylhsminor.yy36=0;}
+#line 2441 "pikchr.c"
+  yymsp[-2].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 6: /* statement ::= PLACENAME COLON unnamed_statement */
+#line 559 "pikchr.y"
+{ yylhsminor.yy36 = yymsp[0].minor.yy36;  pik_elem_setname(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0); }
+#line 2447 "pikchr.c"
+  yymsp[-2].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 7: /* statement ::= PLACENAME COLON position */
+#line 561 "pikchr.y"
+{ yylhsminor.yy36 = pik_elem_new(p,0,0,0);
+                 if(yylhsminor.yy36){ yylhsminor.yy36->ptAt = yymsp[0].minor.yy79; pik_elem_setname(p,yylhsminor.yy36,&yymsp[-2].minor.yy0); }}
+#line 2454 "pikchr.c"
+  yymsp[-2].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 8: /* statement ::= unnamed_statement */
+#line 563 "pikchr.y"
+{yylhsminor.yy36 = yymsp[0].minor.yy36;}
+#line 2460 "pikchr.c"
+  yymsp[0].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 9: /* statement ::= print prlist */
+#line 564 "pikchr.y"
+{pik_append(p,"<br>\n",5); yymsp[-1].minor.yy36=0;}
+#line 2466 "pikchr.c"
+        break;
+      case 10: /* statement ::= ASSERT LP expr EQ expr RP */
+#line 569 "pikchr.y"
+{yymsp[-5].minor.yy36=pik_assert(p,yymsp[-3].minor.yy153,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy153);}
+#line 2471 "pikchr.c"
+        break;
+      case 11: /* statement ::= ASSERT LP position EQ position RP */
+#line 571 "pikchr.y"
+{yymsp[-5].minor.yy36=pik_position_assert(p,&yymsp[-3].minor.yy79,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy79);}
+#line 2476 "pikchr.c"
+        break;
+      case 12: /* statement ::= DEFINE ID CODEBLOCK */
+#line 572 "pikchr.y"
+{yymsp[-2].minor.yy36=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
+#line 2481 "pikchr.c"
+        break;
+      case 13: /* rvalue ::= PLACENAME */
+#line 583 "pikchr.y"
+{yylhsminor.yy153 = pik_lookup_color(p,&yymsp[0].minor.yy0);}
+#line 2486 "pikchr.c"
+  yymsp[0].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 14: /* pritem ::= FILL */
+      case 15: /* pritem ::= COLOR */ yytestcase(yyruleno==15);
+      case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno==16);
+#line 588 "pikchr.y"
+{pik_append_num(p,"",pik_value(p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.n,0));}
+#line 2494 "pikchr.c"
+        break;
+      case 17: /* pritem ::= rvalue */
+#line 591 "pikchr.y"
+{pik_append_num(p,"",yymsp[0].minor.yy153);}
+#line 2499 "pikchr.c"
+        break;
+      case 18: /* pritem ::= STRING */
+#line 592 "pikchr.y"
+{pik_append_text(p,yymsp[0].minor.yy0.z+1,yymsp[0].minor.yy0.n-2,0);}
+#line 2504 "pikchr.c"
+        break;
+      case 19: /* prsep ::= COMMA */
+#line 593 "pikchr.y"
+{pik_append(p, " ", 1);}
+#line 2509 "pikchr.c"
+        break;
+      case 20: /* unnamed_statement ::= basetype attribute_list */
+#line 596 "pikchr.y"
+{yylhsminor.yy36 = yymsp[-1].minor.yy36; pik_after_adding_attributes(p,yylhsminor.yy36);}
+#line 2514 "pikchr.c"
+  yymsp[-1].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 21: /* basetype ::= CLASSNAME */
+#line 598 "pikchr.y"
+{yylhsminor.yy36 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); }
+#line 2520 "pikchr.c"
+  yymsp[0].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 22: /* basetype ::= STRING textposition */
+#line 600 "pikchr.y"
+{yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy164; yylhsminor.yy36 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); }
+#line 2526 "pikchr.c"
+  yymsp[-1].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 23: /* basetype ::= LB savelist statement_list RB */
+#line 602 "pikchr.y"
+{ p->list = yymsp[-2].minor.yy227; yymsp[-3].minor.yy36 = pik_elem_new(p,0,0,yymsp[-1].minor.yy227); if(yymsp[-3].minor.yy36) yymsp[-3].minor.yy36->errTok = yymsp[0].minor.yy0; }
+#line 2532 "pikchr.c"
+        break;
+      case 24: /* savelist ::= */
+#line 607 "pikchr.y"
+{yymsp[1].minor.yy227 = p->list; p->list = 0;}
+#line 2537 "pikchr.c"
+        break;
+      case 25: /* relexpr ::= expr */
+#line 614 "pikchr.y"
+{yylhsminor.yy10.rAbs = yymsp[0].minor.yy153; yylhsminor.yy10.rRel = 0;}
+#line 2542 "pikchr.c"
+  yymsp[0].minor.yy10 = yylhsminor.yy10;
+        break;
+      case 26: /* relexpr ::= expr PERCENT */
+#line 615 "pikchr.y"
+{yylhsminor.yy10.rAbs = 0; yylhsminor.yy10.rRel = yymsp[-1].minor.yy153/100;}
+#line 2548 "pikchr.c"
+  yymsp[-1].minor.yy10 = yylhsminor.yy10;
+        break;
+      case 27: /* optrelexpr ::= */
+#line 617 "pikchr.y"
+{yymsp[1].minor.yy10.rAbs = 0; yymsp[1].minor.yy10.rRel = 1.0;}
+#line 2554 "pikchr.c"
+        break;
+      case 28: /* attribute_list ::= relexpr alist */
+#line 619 "pikchr.y"
+{pik_add_direction(p,0,&yymsp[-1].minor.yy10);}
+#line 2559 "pikchr.c"
+        break;
+      case 29: /* attribute ::= numproperty relexpr */
+#line 623 "pikchr.y"
+{ pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy10); }
+#line 2564 "pikchr.c"
+        break;
+      case 30: /* attribute ::= dashproperty expr */
+#line 624 "pikchr.y"
+{ pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy153); }
+#line 2569 "pikchr.c"
+        break;
+      case 31: /* attribute ::= dashproperty */
+#line 625 "pikchr.y"
+{ pik_set_dashed(p,&yymsp[0].minor.yy0,0);  }
+#line 2574 "pikchr.c"
+        break;
+      case 32: /* attribute ::= colorproperty rvalue */
+#line 626 "pikchr.y"
+{ pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy153); }
+#line 2579 "pikchr.c"
+        break;
+      case 33: /* attribute ::= go direction optrelexpr */
+#line 627 "pikchr.y"
+{ pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy10);}
+#line 2584 "pikchr.c"
+        break;
+      case 34: /* attribute ::= go direction even position */
+#line 628 "pikchr.y"
+{pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy79);}
+#line 2589 "pikchr.c"
+        break;
+      case 35: /* attribute ::= CLOSE */
+#line 629 "pikchr.y"
+{ pik_close_path(p,&yymsp[0].minor.yy0); }
+#line 2594 "pikchr.c"
+        break;
+      case 36: /* attribute ::= CHOP */
+#line 630 "pikchr.y"
+{ p->cur->bChop = 1; }
+#line 2599 "pikchr.c"
+        break;
+      case 37: /* attribute ::= FROM position */
+#line 631 "pikchr.y"
+{ pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy79); }
+#line 2604 "pikchr.c"
+        break;
+      case 38: /* attribute ::= TO position */
+#line 632 "pikchr.y"
+{ pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy79); }
+#line 2609 "pikchr.c"
+        break;
+      case 39: /* attribute ::= THEN */
+#line 633 "pikchr.y"
+{ pik_then(p, &yymsp[0].minor.yy0, p->cur); }
+#line 2614 "pikchr.c"
+        break;
+      case 40: /* attribute ::= THEN optrelexpr HEADING expr */
+      case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno==42);
+#line 635 "pikchr.y"
+{pik_move_hdg(p,&yymsp[-2].minor.yy10,&yymsp[-1].minor.yy0,yymsp[0].minor.yy153,0,&yymsp[-3].minor.yy0);}
+#line 2620 "pikchr.c"
+        break;
+      case 41: /* attribute ::= THEN optrelexpr EDGEPT */
+      case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno==43);
+#line 636 "pikchr.y"
+{pik_move_hdg(p,&yymsp[-1].minor.yy10,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);}
+#line 2626 "pikchr.c"
+        break;
+      case 44: /* attribute ::= AT position */
+#line 641 "pikchr.y"
+{ pik_set_at(p,0,&yymsp[0].minor.yy79,&yymsp[-1].minor.yy0); }
+#line 2631 "pikchr.c"
+        break;
+      case 45: /* attribute ::= SAME */
+#line 643 "pikchr.y"
+{pik_same(p,0,&yymsp[0].minor.yy0);}
+#line 2636 "pikchr.c"
+        break;
+      case 46: /* attribute ::= SAME AS object */
+#line 644 "pikchr.y"
+{pik_same(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);}
+#line 2641 "pikchr.c"
+        break;
+      case 47: /* attribute ::= STRING textposition */
+#line 645 "pikchr.y"
+{pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy164);}
+#line 2646 "pikchr.c"
+        break;
+      case 48: /* attribute ::= FIT */
+#line 646 "pikchr.y"
+{pik_size_to_fit(p,&yymsp[0].minor.yy0,3); }
+#line 2651 "pikchr.c"
+        break;
+      case 49: /* attribute ::= BEHIND object */
+#line 647 "pikchr.y"
+{pik_behind(p,yymsp[0].minor.yy36);}
+#line 2656 "pikchr.c"
+        break;
+      case 50: /* withclause ::= DOT_E edge AT position */
+      case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno==51);
+#line 655 "pikchr.y"
+{ pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy79,&yymsp[-1].minor.yy0); }
+#line 2662 "pikchr.c"
+        break;
+      case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
+#line 659 "pikchr.y"
+{yylhsminor.yy0 = yymsp[0].minor.yy0;}
+#line 2667 "pikchr.c"
+  yymsp[0].minor.yy0 = yylhsminor.yy0;
+        break;
+      case 53: /* boolproperty ::= CW */
+#line 670 "pikchr.y"
+{p->cur->cw = 1;}
+#line 2673 "pikchr.c"
+        break;
+      case 54: /* boolproperty ::= CCW */
+#line 671 "pikchr.y"
+{p->cur->cw = 0;}
+#line 2678 "pikchr.c"
+        break;
+      case 55: /* boolproperty ::= LARROW */
+#line 672 "pikchr.y"
+{p->cur->larrow=1; p->cur->rarrow=0; }
+#line 2683 "pikchr.c"
+        break;
+      case 56: /* boolproperty ::= RARROW */
+#line 673 "pikchr.y"
+{p->cur->larrow=0; p->cur->rarrow=1; }
+#line 2688 "pikchr.c"
+        break;
+      case 57: /* boolproperty ::= LRARROW */
+#line 674 "pikchr.y"
+{p->cur->larrow=1; p->cur->rarrow=1; }
+#line 2693 "pikchr.c"
+        break;
+      case 58: /* boolproperty ::= INVIS */
+#line 675 "pikchr.y"
+{p->cur->sw = 0.0;}
+#line 2698 "pikchr.c"
+        break;
+      case 59: /* boolproperty ::= THICK */
+#line 676 "pikchr.y"
+{p->cur->sw *= 1.5;}
+#line 2703 "pikchr.c"
+        break;
+      case 60: /* boolproperty ::= THIN */
+#line 677 "pikchr.y"
+{p->cur->sw *= 0.67;}
+#line 2708 "pikchr.c"
+        break;
+      case 61: /* boolproperty ::= SOLID */
+#line 678 "pikchr.y"
+{p->cur->sw = pik_value(p,"thickness",9,0);
+                               p->cur->dotted = p->cur->dashed = 0.0;}
+#line 2714 "pikchr.c"
+        break;
+      case 62: /* textposition ::= */
+#line 681 "pikchr.y"
+{yymsp[1].minor.yy164 = 0;}
+#line 2719 "pikchr.c"
+        break;
+      case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */
+#line 684 "pikchr.y"
+{yylhsminor.yy164 = (short int)pik_text_position(yymsp[-1].minor.yy164,&yymsp[0].minor.yy0);}
+#line 2724 "pikchr.c"
+  yymsp[-1].minor.yy164 = yylhsminor.yy164;
+        break;
+      case 64: /* position ::= expr COMMA expr */
+#line 687 "pikchr.y"
+{yylhsminor.yy79.x=yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[0].minor.yy153;}
+#line 2730 "pikchr.c"
+  yymsp[-2].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 65: /* position ::= place PLUS expr COMMA expr */
+#line 689 "pikchr.y"
+{yylhsminor.yy79.x=yymsp[-4].minor.yy79.x+yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[-4].minor.yy79.y+yymsp[0].minor.yy153;}
+#line 2736 "pikchr.c"
+  yymsp[-4].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 66: /* position ::= place MINUS expr COMMA expr */
+#line 690 "pikchr.y"
+{yylhsminor.yy79.x=yymsp[-4].minor.yy79.x-yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[-4].minor.yy79.y-yymsp[0].minor.yy153;}
+#line 2742 "pikchr.c"
+  yymsp[-4].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 67: /* position ::= place PLUS LP expr COMMA expr RP */
+#line 692 "pikchr.y"
+{yylhsminor.yy79.x=yymsp[-6].minor.yy79.x+yymsp[-3].minor.yy153; yylhsminor.yy79.y=yymsp[-6].minor.yy79.y+yymsp[-1].minor.yy153;}
+#line 2748 "pikchr.c"
+  yymsp[-6].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 68: /* position ::= place MINUS LP expr COMMA expr RP */
+#line 694 "pikchr.y"
+{yylhsminor.yy79.x=yymsp[-6].minor.yy79.x-yymsp[-3].minor.yy153; yylhsminor.yy79.y=yymsp[-6].minor.yy79.y-yymsp[-1].minor.yy153;}
+#line 2754 "pikchr.c"
+  yymsp[-6].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 69: /* position ::= LP position COMMA position RP */
+#line 695 "pikchr.y"
+{yymsp[-4].minor.yy79.x=yymsp[-3].minor.yy79.x; yymsp[-4].minor.yy79.y=yymsp[-1].minor.yy79.y;}
+#line 2760 "pikchr.c"
+        break;
+      case 70: /* position ::= LP position RP */
+#line 696 "pikchr.y"
+{yymsp[-2].minor.yy79=yymsp[-1].minor.yy79;}
+#line 2765 "pikchr.c"
+        break;
+      case 71: /* position ::= expr between position AND position */
+#line 698 "pikchr.y"
+{yylhsminor.yy79 = pik_position_between(yymsp[-4].minor.yy153,yymsp[-2].minor.yy79,yymsp[0].minor.yy79);}
+#line 2770 "pikchr.c"
+  yymsp[-4].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 72: /* position ::= expr LT position COMMA position GT */
+#line 700 "pikchr.y"
+{yylhsminor.yy79 = pik_position_between(yymsp[-5].minor.yy153,yymsp[-3].minor.yy79,yymsp[-1].minor.yy79);}
+#line 2776 "pikchr.c"
+  yymsp[-5].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 73: /* position ::= expr ABOVE position */
+#line 701 "pikchr.y"
+{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.y += yymsp[-2].minor.yy153;}
+#line 2782 "pikchr.c"
+  yymsp[-2].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 74: /* position ::= expr BELOW position */
+#line 702 "pikchr.y"
+{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.y -= yymsp[-2].minor.yy153;}
+#line 2788 "pikchr.c"
+  yymsp[-2].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 75: /* position ::= expr LEFT OF position */
+#line 703 "pikchr.y"
+{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.x -= yymsp[-3].minor.yy153;}
+#line 2794 "pikchr.c"
+  yymsp[-3].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 76: /* position ::= expr RIGHT OF position */
+#line 704 "pikchr.y"
+{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.x += yymsp[-3].minor.yy153;}
+#line 2800 "pikchr.c"
+  yymsp[-3].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 77: /* position ::= expr ON HEADING EDGEPT OF position */
+#line 706 "pikchr.y"
+{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-5].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);}
+#line 2806 "pikchr.c"
+  yymsp[-5].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 78: /* position ::= expr HEADING EDGEPT OF position */
+#line 708 "pikchr.y"
+{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-4].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);}
+#line 2812 "pikchr.c"
+  yymsp[-4].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 79: /* position ::= expr EDGEPT OF position */
+#line 710 "pikchr.y"
+{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-3].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);}
+#line 2818 "pikchr.c"
+  yymsp[-3].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 80: /* position ::= expr ON HEADING expr FROM position */
+#line 712 "pikchr.y"
+{yylhsminor.yy79 = pik_position_at_angle(yymsp[-5].minor.yy153,yymsp[-2].minor.yy153,yymsp[0].minor.yy79);}
+#line 2824 "pikchr.c"
+  yymsp[-5].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 81: /* position ::= expr HEADING expr FROM position */
+#line 714 "pikchr.y"
+{yylhsminor.yy79 = pik_position_at_angle(yymsp[-4].minor.yy153,yymsp[-2].minor.yy153,yymsp[0].minor.yy79);}
+#line 2830 "pikchr.c"
+  yymsp[-4].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 82: /* place ::= edge OF object */
+#line 726 "pikchr.y"
+{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);}
+#line 2836 "pikchr.c"
+  yymsp[-2].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 83: /* place2 ::= object */
+#line 727 "pikchr.y"
+{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[0].minor.yy36,0);}
+#line 2842 "pikchr.c"
+  yymsp[0].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 84: /* place2 ::= object DOT_E edge */
+#line 728 "pikchr.y"
+{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);}
+#line 2848 "pikchr.c"
+  yymsp[-2].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 85: /* place2 ::= NTH VERTEX OF object */
+#line 729 "pikchr.y"
+{yylhsminor.yy79 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy36);}
+#line 2854 "pikchr.c"
+  yymsp[-3].minor.yy79 = yylhsminor.yy79;
+        break;
+      case 86: /* object ::= nth */
+#line 741 "pikchr.y"
+{yylhsminor.yy36 = pik_find_nth(p,0,&yymsp[0].minor.yy0);}
+#line 2860 "pikchr.c"
+  yymsp[0].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 87: /* object ::= nth OF|IN object */
+#line 742 "pikchr.y"
+{yylhsminor.yy36 = pik_find_nth(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);}
+#line 2866 "pikchr.c"
+  yymsp[-2].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 88: /* objectname ::= THIS */
+#line 744 "pikchr.y"
+{yymsp[0].minor.yy36 = p->cur;}
+#line 2872 "pikchr.c"
+        break;
+      case 89: /* objectname ::= PLACENAME */
+#line 745 "pikchr.y"
+{yylhsminor.yy36 = pik_find_byname(p,0,&yymsp[0].minor.yy0);}
+#line 2877 "pikchr.c"
+  yymsp[0].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 90: /* objectname ::= objectname DOT_U PLACENAME */
+#line 747 "pikchr.y"
+{yylhsminor.yy36 = pik_find_byname(p,yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);}
+#line 2883 "pikchr.c"
+  yymsp[-2].minor.yy36 = yylhsminor.yy36;
+        break;
+      case 91: /* nth ::= NTH CLASSNAME */
+#line 749 "pikchr.y"
+{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-1].minor.yy0); }
+#line 2889 "pikchr.c"
+  yymsp[-1].minor.yy0 = yylhsminor.yy0;
+        break;
+      case 92: /* nth ::= NTH LAST CLASSNAME */
+#line 750 "pikchr.y"
+{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-2].minor.yy0); }
+#line 2895 "pikchr.c"
+  yymsp[-2].minor.yy0 = yylhsminor.yy0;
+        break;
+      case 93: /* nth ::= LAST CLASSNAME */
+#line 751 "pikchr.y"
+{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.eCode = -1;}
+#line 2901 "pikchr.c"
+        break;
+      case 94: /* nth ::= LAST */
+#line 752 "pikchr.y"
+{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -1;}
+#line 2906 "pikchr.c"
+  yymsp[0].minor.yy0 = yylhsminor.yy0;
+        break;
+      case 95: /* nth ::= NTH LB RB */
+#line 753 "pikchr.y"
+{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-2].minor.yy0);}
+#line 2912 "pikchr.c"
+  yymsp[-2].minor.yy0 = yylhsminor.yy0;
+        break;
+      case 96: /* nth ::= NTH LAST LB RB */
+#line 754 "pikchr.y"
+{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-3].minor.yy0);}
+#line 2918 "pikchr.c"
+  yymsp[-3].minor.yy0 = yylhsminor.yy0;
+        break;
+      case 97: /* nth ::= LAST LB RB */
+#line 755 "pikchr.y"
+{yymsp[-2].minor.yy0=yymsp[-1].minor.yy0; yymsp[-2].minor.yy0.eCode = -1; }
+#line 2924 "pikchr.c"
+        break;
+      case 98: /* expr ::= expr PLUS expr */
+#line 757 "pikchr.y"
+{yylhsminor.yy153=yymsp[-2].minor.yy153+yymsp[0].minor.yy153;}
+#line 2929 "pikchr.c"
+  yymsp[-2].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 99: /* expr ::= expr MINUS expr */
+#line 758 "pikchr.y"
+{yylhsminor.yy153=yymsp[-2].minor.yy153-yymsp[0].minor.yy153;}
+#line 2935 "pikchr.c"
+  yymsp[-2].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 100: /* expr ::= expr STAR expr */
+#line 759 "pikchr.y"
+{yylhsminor.yy153=yymsp[-2].minor.yy153*yymsp[0].minor.yy153;}
+#line 2941 "pikchr.c"
+  yymsp[-2].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 101: /* expr ::= expr SLASH expr */
+#line 760 "pikchr.y"
+{
+  if( yymsp[0].minor.yy153==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy153 = 0.0; }
+  else{ yylhsminor.yy153 = yymsp[-2].minor.yy153/yymsp[0].minor.yy153; }
+}
+#line 2950 "pikchr.c"
+  yymsp[-2].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 102: /* expr ::= MINUS expr */
+#line 764 "pikchr.y"
+{yymsp[-1].minor.yy153=-yymsp[0].minor.yy153;}
+#line 2956 "pikchr.c"
+        break;
+      case 103: /* expr ::= PLUS expr */
+#line 765 "pikchr.y"
+{yymsp[-1].minor.yy153=yymsp[0].minor.yy153;}
+#line 2961 "pikchr.c"
+        break;
+      case 104: /* expr ::= LP expr RP */
+#line 766 "pikchr.y"
+{yymsp[-2].minor.yy153=yymsp[-1].minor.yy153;}
+#line 2966 "pikchr.c"
+        break;
+      case 105: /* expr ::= LP FILL|COLOR|THICKNESS RP */
+#line 767 "pikchr.y"
+{yymsp[-2].minor.yy153=pik_get_var(p,&yymsp[-1].minor.yy0);}
+#line 2971 "pikchr.c"
+        break;
+      case 106: /* expr ::= NUMBER */
+#line 768 "pikchr.y"
+{yylhsminor.yy153=pik_atof(&yymsp[0].minor.yy0);}
+#line 2976 "pikchr.c"
+  yymsp[0].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 107: /* expr ::= ID */
+#line 769 "pikchr.y"
+{yylhsminor.yy153=pik_get_var(p,&yymsp[0].minor.yy0);}
+#line 2982 "pikchr.c"
+  yymsp[0].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 108: /* expr ::= FUNC1 LP expr RP */
+#line 770 "pikchr.y"
+{yylhsminor.yy153 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy153,0.0);}
+#line 2988 "pikchr.c"
+  yymsp[-3].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 109: /* expr ::= FUNC2 LP expr COMMA expr RP */
+#line 771 "pikchr.y"
+{yylhsminor.yy153 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy153,yymsp[-1].minor.yy153);}
+#line 2994 "pikchr.c"
+  yymsp[-5].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 110: /* expr ::= DIST LP position COMMA position RP */
+#line 772 "pikchr.y"
+{yymsp[-5].minor.yy153 = pik_dist(&yymsp[-3].minor.yy79,&yymsp[-1].minor.yy79);}
+#line 3000 "pikchr.c"
+        break;
+      case 111: /* expr ::= place2 DOT_XY X */
+#line 773 "pikchr.y"
+{yylhsminor.yy153 = yymsp[-2].minor.yy79.x;}
+#line 3005 "pikchr.c"
+  yymsp[-2].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 112: /* expr ::= place2 DOT_XY Y */
+#line 774 "pikchr.y"
+{yylhsminor.yy153 = yymsp[-2].minor.yy79.y;}
+#line 3011 "pikchr.c"
+  yymsp[-2].minor.yy153 = yylhsminor.yy153;
+        break;
+      case 113: /* expr ::= object DOT_L numproperty */
+      case 114: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno==114);
+      case 115: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno==115);
+#line 775 "pikchr.y"
+{yylhsminor.yy153=pik_property_of(yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);}
+#line 3019 "pikchr.c"
+  yymsp[-2].minor.yy153 = yylhsminor.yy153;
+        break;
+      default:
+      /* (116) lvalue ::= ID */ yytestcase(yyruleno==116);
+      /* (117) lvalue ::= FILL */ yytestcase(yyruleno==117);
+      /* (118) lvalue ::= COLOR */ yytestcase(yyruleno==118);
+      /* (119) lvalue ::= THICKNESS */ yytestcase(yyruleno==119);
+      /* (120) rvalue ::= expr */ yytestcase(yyruleno==120);
+      /* (121) print ::= PRINT */ yytestcase(yyruleno==121);
+      /* (122) prlist ::= pritem (OPTIMIZED OUT) */ assert(yyruleno!=122);
+      /* (123) prlist ::= prlist prsep pritem */ yytestcase(yyruleno==123);
+      /* (124) direction ::= UP */ yytestcase(yyruleno==124);
+      /* (125) direction ::= DOWN */ yytestcase(yyruleno==125);
+      /* (126) direction ::= LEFT */ yytestcase(yyruleno==126);
+      /* (127) direction ::= RIGHT */ yytestcase(yyruleno==127);
+      /* (128) optrelexpr ::= relexpr (OPTIMIZED OUT) */ assert(yyruleno!=128);
+      /* (129) attribute_list ::= alist */ yytestcase(yyruleno==129);
+      /* (130) alist ::= */ yytestcase(yyruleno==130);
+      /* (131) alist ::= alist attribute */ yytestcase(yyruleno==131);
+      /* (132) attribute ::= boolproperty (OPTIMIZED OUT) */ assert(yyruleno!=132);
+      /* (133) attribute ::= WITH withclause */ yytestcase(yyruleno==133);
+      /* (134) go ::= GO */ yytestcase(yyruleno==134);
+      /* (135) go ::= */ yytestcase(yyruleno==135);
+      /* (136) even ::= UNTIL EVEN WITH */ yytestcase(yyruleno==136);
+      /* (137) even ::= EVEN WITH */ yytestcase(yyruleno==137);
+      /* (138) dashproperty ::= DOTTED */ yytestcase(yyruleno==138);
+      /* (139) dashproperty ::= DASHED */ yytestcase(yyruleno==139);
+      /* (140) colorproperty ::= FILL */ yytestcase(yyruleno==140);
+      /* (141) colorproperty ::= COLOR */ yytestcase(yyruleno==141);
+      /* (142) position ::= place */ yytestcase(yyruleno==142);
+      /* (143) between ::= WAY BETWEEN */ yytestcase(yyruleno==143);
+      /* (144) between ::= BETWEEN */ yytestcase(yyruleno==144);
+      /* (145) between ::= OF THE WAY BETWEEN */ yytestcase(yyruleno==145);
+      /* (146) place ::= place2 */ yytestcase(yyruleno==146);
+      /* (147) edge ::= CENTER */ yytestcase(yyruleno==147);
+      /* (148) edge ::= EDGEPT */ yytestcase(yyruleno==148);
+      /* (149) edge ::= TOP */ yytestcase(yyruleno==149);
+      /* (150) edge ::= BOTTOM */ yytestcase(yyruleno==150);
+      /* (151) edge ::= START */ yytestcase(yyruleno==151);
+      /* (152) edge ::= END */ yytestcase(yyruleno==152);
+      /* (153) edge ::= RIGHT */ yytestcase(yyruleno==153);
+      /* (154) edge ::= LEFT */ yytestcase(yyruleno==154);
+      /* (155) object ::= objectname */ yytestcase(yyruleno==155);
+        break;
+/********** End reduce actions ************************************************/
+  };
+  assert( yyruleno<sizeof(yyRuleInfoLhs)/sizeof(yyRuleInfoLhs[0]) );
+  yygoto = yyRuleInfoLhs[yyruleno];
+  yysize = yyRuleInfoNRhs[yyruleno];
+  yyact = yy_find_reduce_action(yymsp[yysize].stateno,(YYCODETYPE)yygoto);
+
+  /* There are no SHIFTREDUCE actions on nonterminals because the table
+  ** generator has simplified them to pure REDUCE actions. */
+  assert( !(yyact>YY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) );
+
+  /* It is not possible for a REDUCE to be followed by an error */
+  assert( yyact!=YY_ERROR_ACTION );
+
+  yymsp += yysize+1;
+  yypParser->yytos = yymsp;
+  yymsp->stateno = (YYACTIONTYPE)yyact;
+  yymsp->major = (YYCODETYPE)yygoto;
+  yyTraceShift(yypParser, yyact, "... then shift");
+  return yyact;
+}
+
+/*
+** The following code executes when the parse fails
+*/
+#ifndef YYNOERRORRECOVERY
+static void yy_parse_failed(
+  yyParser *yypParser           /* The parser */
+){
+  pik_parserARG_FETCH
+  pik_parserCTX_FETCH
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
+  }
+#endif
+  while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
+  /* Here code is inserted which will be executed whenever the
+  ** parser fails */
+/************ Begin %parse_failure code ***************************************/
+/************ End %parse_failure code *****************************************/
+  pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */
+  pik_parserCTX_STORE
+}
+#endif /* YYNOERRORRECOVERY */
+
+/*
+** The following code executes when a syntax error first occurs.
+*/
+static void yy_syntax_error(
+  yyParser *yypParser,           /* The parser */
+  int yymajor,                   /* The major type of the error token */
+  pik_parserTOKENTYPE yyminor         /* The minor type of the error token */
+){
+  pik_parserARG_FETCH
+  pik_parserCTX_FETCH
+#define TOKEN yyminor
+/************ Begin %syntax_error code ****************************************/
+#line 535 "pikchr.y"
+
+  if( TOKEN.z && TOKEN.z[0] ){
+    pik_error(p, &TOKEN, "syntax error");
+  }else{
+    pik_error(p, 0, "syntax error");
+  }
+  UNUSED_PARAMETER(yymajor);
+#line 3130 "pikchr.c"
+/************ End %syntax_error code ******************************************/
+  pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */
+  pik_parserCTX_STORE
+}
+
+/*
+** The following is executed when the parser accepts
+*/
+static void yy_accept(
+  yyParser *yypParser           /* The parser */
+){
+  pik_parserARG_FETCH
+  pik_parserCTX_FETCH
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
+  }
+#endif
+#ifndef YYNOERRORRECOVERY
+  yypParser->yyerrcnt = -1;
+#endif
+  assert( yypParser->yytos==yypParser->yystack );
+  /* Here code is inserted which will be executed whenever the
+  ** parser accepts */
+/*********** Begin %parse_accept code *****************************************/
+/*********** End %parse_accept code *******************************************/
+  pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */
+  pik_parserCTX_STORE
+}
+
+/* The main parser program.
+** The first argument is a pointer to a structure obtained from
+** "pik_parserAlloc" which describes the current state of the parser.
+** The second argument is the major token number.  The third is
+** the minor token.  The fourth optional argument is whatever the
+** user wants (and specified in the grammar) and is available for
+** use by the action routines.
+**
+** Inputs:
+** <ul>
+** <li> A pointer to the parser (an opaque structure.)
+** <li> The major token number.
+** <li> The minor token number.
+** <li> An option argument of a grammar-specified type.
+** </ul>
+**
+** Outputs:
+** None.
+*/
+void pik_parser(
+  void *yyp,                   /* The parser */
+  int yymajor,                 /* The major token code number */
+  pik_parserTOKENTYPE yyminor       /* The value for the token */
+  pik_parserARG_PDECL               /* Optional %extra_argument parameter */
+){
+  YYMINORTYPE yyminorunion;
+  YYACTIONTYPE yyact;   /* The parser action. */
+#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
+  int yyendofinput;     /* True if we are at the end of input */
+#endif
+#ifdef YYERRORSYMBOL
+  int yyerrorhit = 0;   /* True if yymajor has invoked an error */
+#endif
+  yyParser *yypParser = (yyParser*)yyp;  /* The parser */
+  pik_parserCTX_FETCH
+  pik_parserARG_STORE
+
+  assert( yypParser->yytos!=0 );
+#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
+  yyendofinput = (yymajor==0);
+#endif
+
+  yyact = yypParser->yytos->stateno;
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    if( yyact < YY_MIN_REDUCE ){
+      fprintf(yyTraceFILE,"%sInput '%s' in state %d\n",
+              yyTracePrompt,yyTokenName[yymajor],yyact);
+    }else{
+      fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n",
+              yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE);
+    }
+  }
+#endif
+
+  while(1){ /* Exit by "break" */
+    assert( yypParser->yytos>=yypParser->yystack );
+    assert( yyact==yypParser->yytos->stateno );
+    yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact);
+    if( yyact >= YY_MIN_REDUCE ){
+      unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */
+#ifndef NDEBUG
+      assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) );
+      if( yyTraceFILE ){
+        int yysize = yyRuleInfoNRhs[yyruleno];
+        if( yysize ){
+          fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
+            yyTracePrompt,
+            yyruleno, yyRuleName[yyruleno],
+            yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action",
+            yypParser->yytos[yysize].stateno);
+        }else{
+          fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n",
+            yyTracePrompt, yyruleno, yyRuleName[yyruleno],
+            yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action");
+        }
+      }
+#endif /* NDEBUG */
+
+      /* Check that the stack is large enough to grow by a single entry
+      ** if the RHS of the rule is empty.  This ensures that there is room
+      ** enough on the stack to push the LHS value */
+      if( yyRuleInfoNRhs[yyruleno]==0 ){
+#ifdef YYTRACKMAXSTACKDEPTH
+        if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
+          yypParser->yyhwm++;
+          assert( yypParser->yyhwm ==
+                  (int)(yypParser->yytos - yypParser->yystack));
+        }
+#endif
+#if YYSTACKDEPTH>0 
+        if( yypParser->yytos>=yypParser->yystackEnd ){
+          yyStackOverflow(yypParser);
+          break;
+        }
+#else
+        if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
+          if( yyGrowStack(yypParser) ){
+            yyStackOverflow(yypParser);
+            break;
+          }
+        }
+#endif
+      }
+      yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor pik_parserCTX_PARAM);
+    }else if( yyact <= YY_MAX_SHIFTREDUCE ){
+      yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor);
+#ifndef YYNOERRORRECOVERY
+      yypParser->yyerrcnt--;
+#endif
+      break;
+    }else if( yyact==YY_ACCEPT_ACTION ){
+      yypParser->yytos--;
+      yy_accept(yypParser);
+      return;
+    }else{
+      assert( yyact == YY_ERROR_ACTION );
+      yyminorunion.yy0 = yyminor;
+#ifdef YYERRORSYMBOL
+      int yymx;
+#endif
+#ifndef NDEBUG
+      if( yyTraceFILE ){
+        fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
+      }
+#endif
+#ifdef YYERRORSYMBOL
+      /* A syntax error has occurred.
+      ** The response to an error depends upon whether or not the
+      ** grammar defines an error token "ERROR".  
+      **
+      ** This is what we do if the grammar does define ERROR:
+      **
+      **  * Call the %syntax_error function.
+      **
+      **  * Begin popping the stack until we enter a state where
+      **    it is legal to shift the error symbol, then shift
+      **    the error symbol.
+      **
+      **  * Set the error count to three.
+      **
+      **  * Begin accepting and shifting new tokens.  No new error
+      **    processing will occur until three tokens have been
+      **    shifted successfully.
+      **
+      */
+      if( yypParser->yyerrcnt<0 ){
+        yy_syntax_error(yypParser,yymajor,yyminor);
+      }
+      yymx = yypParser->yytos->major;
+      if( yymx==YYERRORSYMBOL || yyerrorhit ){
+#ifndef NDEBUG
+        if( yyTraceFILE ){
+          fprintf(yyTraceFILE,"%sDiscard input token %s\n",
+             yyTracePrompt,yyTokenName[yymajor]);
+        }
+#endif
+        yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion);
+        yymajor = YYNOCODE;
+      }else{
+        while( yypParser->yytos > yypParser->yystack ){
+          yyact = yy_find_reduce_action(yypParser->yytos->stateno,
+                                        YYERRORSYMBOL);
+          if( yyact<=YY_MAX_SHIFTREDUCE ) break;
+          yy_pop_parser_stack(yypParser);
+        }
+        if( yypParser->yytos <= yypParser->yystack || yymajor==0 ){
+          yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+          yy_parse_failed(yypParser);
+#ifndef YYNOERRORRECOVERY
+          yypParser->yyerrcnt = -1;
+#endif
+          yymajor = YYNOCODE;
+        }else if( yymx!=YYERRORSYMBOL ){
+          yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor);
+        }
+      }
+      yypParser->yyerrcnt = 3;
+      yyerrorhit = 1;
+      if( yymajor==YYNOCODE ) break;
+      yyact = yypParser->yytos->stateno;
+#elif defined(YYNOERRORRECOVERY)
+      /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to
+      ** do any kind of error recovery.  Instead, simply invoke the syntax
+      ** error routine and continue going as if nothing had happened.
+      **
+      ** Applications can set this macro (for example inside %include) if
+      ** they intend to abandon the parse upon the first syntax error seen.
+      */
+      yy_syntax_error(yypParser,yymajor, yyminor);
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      break;
+#else  /* YYERRORSYMBOL is not defined */
+      /* This is what we do if the grammar does not define ERROR:
+      **
+      **  * Report an error message, and throw away the input token.
+      **
+      **  * If the input token is $, then fail the parse.
+      **
+      ** As before, subsequent error messages are suppressed until
+      ** three input tokens have been successfully shifted.
+      */
+      if( yypParser->yyerrcnt<=0 ){
+        yy_syntax_error(yypParser,yymajor, yyminor);
+      }
+      yypParser->yyerrcnt = 3;
+      yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
+      if( yyendofinput ){
+        yy_parse_failed(yypParser);
+#ifndef YYNOERRORRECOVERY
+        yypParser->yyerrcnt = -1;
+#endif
+      }
+      break;
+#endif
+    }
+  }
+#ifndef NDEBUG
+  if( yyTraceFILE ){
+    yyStackEntry *i;
+    char cDiv = '[';
+    fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt);
+    for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){
+      fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]);
+      cDiv = ' ';
+    }
+    fprintf(yyTraceFILE,"]\n");
+  }
+#endif
+  return;
+}
+
+/*
+** Return the fallback token corresponding to canonical token iToken, or
+** 0 if iToken has no fallback.
+*/
+int pik_parserFallback(int iToken){
+#ifdef YYFALLBACK
+  assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) );
+  return yyFallback[iToken];
+#else
+  (void)iToken;
+  return 0;
+#endif
+}
+#line 780 "pikchr.y"
+
+
+
+/* Chart of the 148 official CSS color names with their
+** corresponding RGB values thru Color Module Level 4:
+** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
+**
+** Two new names "None" and "Off" are added with a value
+** of -1.
+*/
+static const struct {
+  const char *zName;  /* Name of the color */
+  int val;            /* RGB value */
+} aColor[] = {
+  { "AliceBlue",                   0xf0f8ff },
+  { "AntiqueWhite",                0xfaebd7 },
+  { "Aqua",                        0x00ffff },
+  { "Aquamarine",                  0x7fffd4 },
+  { "Azure",                       0xf0ffff },
+  { "Beige",                       0xf5f5dc },
+  { "Bisque",                      0xffe4c4 },
+  { "Black",                       0x000000 },
+  { "BlanchedAlmond",              0xffebcd },
+  { "Blue",                        0x0000ff },
+  { "BlueViolet",                  0x8a2be2 },
+  { "Brown",                       0xa52a2a },
+  { "BurlyWood",                   0xdeb887 },
+  { "CadetBlue",                   0x5f9ea0 },
+  { "Chartreuse",                  0x7fff00 },
+  { "Chocolate",                   0xd2691e },
+  { "Coral",                       0xff7f50 },
+  { "CornflowerBlue",              0x6495ed },
+  { "Cornsilk",                    0xfff8dc },
+  { "Crimson",                     0xdc143c },
+  { "Cyan",                        0x00ffff },
+  { "DarkBlue",                    0x00008b },
+  { "DarkCyan",                    0x008b8b },
+  { "DarkGoldenrod",               0xb8860b },
+  { "DarkGray",                    0xa9a9a9 },
+  { "DarkGreen",                   0x006400 },
+  { "DarkGrey",                    0xa9a9a9 },
+  { "DarkKhaki",                   0xbdb76b },
+  { "DarkMagenta",                 0x8b008b },
+  { "DarkOliveGreen",              0x556b2f },
+  { "DarkOrange",                  0xff8c00 },
+  { "DarkOrchid",                  0x9932cc },
+  { "DarkRed",                     0x8b0000 },
+  { "DarkSalmon",                  0xe9967a },
+  { "DarkSeaGreen",                0x8fbc8f },
+  { "DarkSlateBlue",               0x483d8b },
+  { "DarkSlateGray",               0x2f4f4f },
+  { "DarkSlateGrey",               0x2f4f4f },
+  { "DarkTurquoise",               0x00ced1 },
+  { "DarkViolet",                  0x9400d3 },
+  { "DeepPink",                    0xff1493 },
+  { "DeepSkyBlue",                 0x00bfff },
+  { "DimGray",                     0x696969 },
+  { "DimGrey",                     0x696969 },
+  { "DodgerBlue",                  0x1e90ff },
+  { "Firebrick",                   0xb22222 },
+  { "FloralWhite",                 0xfffaf0 },
+  { "ForestGreen",                 0x228b22 },
+  { "Fuchsia",                     0xff00ff },
+  { "Gainsboro",                   0xdcdcdc },
+  { "GhostWhite",                  0xf8f8ff },
+  { "Gold",                        0xffd700 },
+  { "Goldenrod",                   0xdaa520 },
+  { "Gray",                        0x808080 },
+  { "Green",                       0x008000 },
+  { "GreenYellow",                 0xadff2f },
+  { "Grey",                        0x808080 },
+  { "Honeydew",                    0xf0fff0 },
+  { "HotPink",                     0xff69b4 },
+  { "IndianRed",                   0xcd5c5c },
+  { "Indigo",                      0x4b0082 },
+  { "Ivory",                       0xfffff0 },
+  { "Khaki",                       0xf0e68c },
+  { "Lavender",                    0xe6e6fa },
+  { "LavenderBlush",               0xfff0f5 },
+  { "LawnGreen",                   0x7cfc00 },
+  { "LemonChiffon",                0xfffacd },
+  { "LightBlue",                   0xadd8e6 },
+  { "LightCoral",                  0xf08080 },
+  { "LightCyan",                   0xe0ffff },
+  { "LightGoldenrodYellow",        0xfafad2 },
+  { "LightGray",                   0xd3d3d3 },
+  { "LightGreen",                  0x90ee90 },
+  { "LightGrey",                   0xd3d3d3 },
+  { "LightPink",                   0xffb6c1 },
+  { "LightSalmon",                 0xffa07a },
+  { "LightSeaGreen",               0x20b2aa },
+  { "LightSkyBlue",                0x87cefa },
+  { "LightSlateGray",              0x778899 },
+  { "LightSlateGrey",              0x778899 },
+  { "LightSteelBlue",              0xb0c4de },
+  { "LightYellow",                 0xffffe0 },
+  { "Lime",                        0x00ff00 },
+  { "LimeGreen",                   0x32cd32 },
+  { "Linen",                       0xfaf0e6 },
+  { "Magenta",                     0xff00ff },
+  { "Maroon",                      0x800000 },
+  { "MediumAquamarine",            0x66cdaa },
+  { "MediumBlue",                  0x0000cd },
+  { "MediumOrchid",                0xba55d3 },
+  { "MediumPurple",                0x9370db },
+  { "MediumSeaGreen",              0x3cb371 },
+  { "MediumSlateBlue",             0x7b68ee },
+  { "MediumSpringGreen",           0x00fa9a },
+  { "MediumTurquoise",             0x48d1cc },
+  { "MediumVioletRed",             0xc71585 },
+  { "MidnightBlue",                0x191970 },
+  { "MintCream",                   0xf5fffa },
+  { "MistyRose",                   0xffe4e1 },
+  { "Moccasin",                    0xffe4b5 },
+  { "NavajoWhite",                 0xffdead },
+  { "Navy",                        0x000080 },
+  { "None",                              -1 },  /* Non-standard addition */
+  { "Off",                               -1 },  /* Non-standard addition */
+  { "OldLace",                     0xfdf5e6 },
+  { "Olive",                       0x808000 },
+  { "OliveDrab",                   0x6b8e23 },
+  { "Orange",                      0xffa500 },
+  { "OrangeRed",                   0xff4500 },
+  { "Orchid",                      0xda70d6 },
+  { "PaleGoldenrod",               0xeee8aa },
+  { "PaleGreen",                   0x98fb98 },
+  { "PaleTurquoise",               0xafeeee },
+  { "PaleVioletRed",               0xdb7093 },
+  { "PapayaWhip",                  0xffefd5 },
+  { "PeachPuff",                   0xffdab9 },
+  { "Peru",                        0xcd853f },
+  { "Pink",                        0xffc0cb },
+  { "Plum",                        0xdda0dd },
+  { "PowderBlue",                  0xb0e0e6 },
+  { "Purple",                      0x800080 },
+  { "RebeccaPurple",               0x663399 },
+  { "Red",                         0xff0000 },
+  { "RosyBrown",                   0xbc8f8f },
+  { "RoyalBlue",                   0x4169e1 },
+  { "SaddleBrown",                 0x8b4513 },
+  { "Salmon",                      0xfa8072 },
+  { "SandyBrown",                  0xf4a460 },
+  { "SeaGreen",                    0x2e8b57 },
+  { "Seashell",                    0xfff5ee },
+  { "Sienna",                      0xa0522d },
+  { "Silver",                      0xc0c0c0 },
+  { "SkyBlue",                     0x87ceeb },
+  { "SlateBlue",                   0x6a5acd },
+  { "SlateGray",                   0x708090 },
+  { "SlateGrey",                   0x708090 },
+  { "Snow",                        0xfffafa },
+  { "SpringGreen",                 0x00ff7f },
+  { "SteelBlue",                   0x4682b4 },
+  { "Tan",                         0xd2b48c },
+  { "Teal",                        0x008080 },
+  { "Thistle",                     0xd8bfd8 },
+  { "Tomato",                      0xff6347 },
+  { "Turquoise",                   0x40e0d0 },
+  { "Violet",                      0xee82ee },
+  { "Wheat",                       0xf5deb3 },
+  { "White",                       0xffffff },
+  { "WhiteSmoke",                  0xf5f5f5 },
+  { "Yellow",                      0xffff00 },
+  { "YellowGreen",                 0x9acd32 },
+};
+
+/* Built-in variable names.
+**
+** This array is constant.  When a script changes the value of one of
+** these built-ins, a new PVar record is added at the head of
+** the Pik.pVar list, which is searched first.  Thus the new PVar entry
+** will override this default value.
+**
+** Units are in inches, except for "color" and "fill" which are 
+** interpreted as 24-bit RGB values.
+**
+** Binary search used.  Must be kept in sorted order.
+*/
+static const struct { const char *zName; PNum val; } aBuiltin[] = {
+  { "arcrad",      0.25  },
+  { "arrowhead",   2.0   },
+  { "arrowht",     0.08  },
+  { "arrowwid",    0.06  },
+  { "boxht",       0.5   },
+  { "boxrad",      0.0   },
+  { "boxwid",      0.75  },
+  { "charht",      0.14  },
+  { "charwid",     0.08  },
+  { "circlerad",   0.25  },
+  { "color",       0.0   },
+  { "cylht",       0.5   },
+  { "cylrad",      0.075 },
+  { "cylwid",      0.75  },
+  { "dashwid",     0.05  },
+  { "dotrad",      0.015 },
+  { "ellipseht",   0.5   },
+  { "ellipsewid",  0.75  },
+  { "fileht",      0.75  },
+  { "filerad",     0.15  },
+  { "filewid",     0.5   },
+  { "fill",        -1.0  },
+  { "lineht",      0.5   },
+  { "linewid",     0.5   },
+  { "movewid",     0.5   },
+  { "ovalht",      0.5   },
+  { "ovalwid",     1.0   },
+  { "scale",       1.0   },
+  { "textht",      0.5   },
+  { "textwid",     0.75  },
+  { "thickness",   0.015 },
+};
+
+
+/* Methods for the "arc" class */
+static void arcInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "arcrad",6,0);
+  pObj->h = pObj->w;
+}
+/* Hack: Arcs are here rendered as quadratic Bezier curves rather
+** than true arcs.  Multiple reasons: (1) the legacy-PIC parameters
+** that control arcs are obscure and I could not figure out what they
+** mean based on available documentation.  (2) Arcs are rarely used,
+** and so do not seem that important.
+*/
+static PPoint arcControlPoint(int cw, PPoint f, PPoint t, PNum rScale){
+  PPoint m;
+  PNum dx, dy;
+  m.x = 0.5*(f.x+t.x);
+  m.y = 0.5*(f.y+t.y);
+  dx = t.x - f.x;
+  dy = t.y - f.y;
+  if( cw ){
+    m.x -= 0.5*rScale*dy;
+    m.y += 0.5*rScale*dx;
+  }else{
+    m.x += 0.5*rScale*dy;
+    m.y -= 0.5*rScale*dx;
+  }
+  return m;
+}
+static void arcCheck(Pik *p, PObj *pObj){
+  PPoint m;
+  if( p->nTPath>2 ){
+    pik_error(p, &pObj->errTok, "arc geometry error");
+    return;
+  }
+  m = arcControlPoint(pObj->cw, p->aTPath[0], p->aTPath[1], 0.5);
+  pik_bbox_add_xy(&pObj->bbox, m.x, m.y);
+}
+static void arcRender(Pik *p, PObj *pObj){
+  PPoint f, m, t;
+  if( pObj->nPath<2 ) return;
+  if( pObj->sw<=0.0 ) return;
+  f = pObj->aPath[0];
+  t = pObj->aPath[1];
+  m = arcControlPoint(pObj->cw,f,t,1.0);
+  if( pObj->larrow ){
+    pik_draw_arrowhead(p,&m,&f,pObj);
+  }
+  if( pObj->rarrow ){
+    pik_draw_arrowhead(p,&m,&t,pObj);
+  }
+  pik_append_xy(p,"<path d=\"M", f.x, f.y);
+  pik_append_xy(p,"Q", m.x, m.y);
+  pik_append_xy(p," ", t.x, t.y);
+  pik_append(p,"\" ",2);
+  pik_append_style(p,pObj,0);
+  pik_append(p,"\" />\n", -1);
+
+  pik_append_txt(p, pObj, 0);
+}
+
+
+/* Methods for the "arrow" class */
+static void arrowInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "linewid",7,0);
+  pObj->h = pik_value(p, "lineht",6,0);
+  pObj->rad = pik_value(p, "linerad",7,0);
+  pObj->rarrow = 1;
+}
+
+/* Methods for the "box" class */
+static void boxInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "boxwid",6,0);
+  pObj->h = pik_value(p, "boxht",5,0);
+  pObj->rad = pik_value(p, "boxrad",6,0);
+}
+/* Return offset from the center of the box to the compass point 
+** given by parameter cp */
+static PPoint boxOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PNum rx;
+  if( rad<=0.0 ){
+    rx = 0.0;
+  }else{
+    if( rad>w2 ) rad = w2;
+    if( rad>h2 ) rad = h2;
+    rx = 0.29289321881345252392*rad;
+  }
+  switch( cp ){
+    case CP_C:                                   break;
+    case CP_N:   pt.x = 0.0;      pt.y = h2;     break;
+    case CP_NE:  pt.x = w2-rx;    pt.y = h2-rx;  break;
+    case CP_E:   pt.x = w2;       pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2-rx;    pt.y = rx-h2;  break;
+    case CP_S:   pt.x = 0.0;      pt.y = -h2;    break;
+    case CP_SW:  pt.x = rx-w2;    pt.y = rx-h2;  break;
+    case CP_W:   pt.x = -w2;      pt.y = 0.0;    break;
+    case CP_NW:  pt.x = rx-w2;    pt.y = h2-rx;  break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+static PPoint boxChop(Pik *p, PObj *pObj, PPoint *pPt){
+  PNum dx, dy;
+  int cp = CP_C;
+  PPoint chop = pObj->ptAt;
+  if( pObj->w<=0.0 ) return chop;
+  if( pObj->h<=0.0 ) return chop;
+  dx = (pPt->x - pObj->ptAt.x)*pObj->h/pObj->w;
+  dy = (pPt->y - pObj->ptAt.y);
+  if( dx>0.0 ){
+    if( dy>=2.414*dx ){
+      cp = CP_N;
+    }else if( dy>=0.414*dx ){
+      cp = CP_NE;
+    }else if( dy>=-0.414*dx ){
+      cp = CP_E;
+    }else if( dy>-2.414*dx ){
+      cp = CP_SE;
+    }else{
+      cp = CP_S;
+    }
+  }else{
+    if( dy>=-2.414*dx ){
+      cp = CP_N;
+    }else if( dy>=-0.414*dx ){
+      cp = CP_NW;
+    }else if( dy>=0.414*dx ){
+      cp = CP_W;
+    }else if( dy>2.414*dx ){
+      cp = CP_SW;
+    }else{
+      cp = CP_S;
+    }
+  }
+  chop = pObj->type->xOffset(p,pObj,cp);
+  chop.x += pObj->ptAt.x;
+  chop.y += pObj->ptAt.y;
+  return chop;
+}
+static void boxFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h;
+  UNUSED_PARAMETER(p);
+}
+static void boxRender(Pik *p, PObj *pObj){
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    if( rad<=0.0 ){
+      pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y-h2);
+      pik_append_xy(p,"L", pt.x+w2,pt.y-h2);
+      pik_append_xy(p,"L", pt.x+w2,pt.y+h2);
+      pik_append_xy(p,"L", pt.x-w2,pt.y+h2);
+      pik_append(p,"Z\" ",-1);
+    }else{
+      /*
+      **         ----       - y3
+      **        /    \
+      **       /      \     _ y2
+      **      |        |    
+      **      |        |    _ y1
+      **       \      /
+      **        \    /
+      **         ----       _ y0
+      **
+      **      '  '  '  '
+      **     x0 x1 x2 x3
+      */
+      PNum x0,x1,x2,x3,y0,y1,y2,y3;
+      if( rad>w2 ) rad = w2;
+      if( rad>h2 ) rad = h2;
+      x0 = pt.x - w2;
+      x1 = x0 + rad;
+      x3 = pt.x + w2;
+      x2 = x3 - rad;
+      y0 = pt.y - h2;
+      y1 = y0 + rad;
+      y3 = pt.y + h2;
+      y2 = y3 - rad;
+      pik_append_xy(p,"<path d=\"M", x1, y0);
+      if( x2>x1 ) pik_append_xy(p, "L", x2, y0);
+      pik_append_arc(p, rad, rad, x3, y1);
+      if( y2>y1 ) pik_append_xy(p, "L", x3, y2);
+      pik_append_arc(p, rad, rad, x2, y3);
+      if( x2>x1 ) pik_append_xy(p, "L", x1, y3);
+      pik_append_arc(p, rad, rad, x0, y2);
+      if( y2>y1 ) pik_append_xy(p, "L", x0, y1);
+      pik_append_arc(p, rad, rad, x1, y0);
+      pik_append(p,"Z\" ",-1);
+    }
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "circle" class */
+static void circleInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "circlerad",9,0)*2;
+  pObj->h = pObj->w;
+  pObj->rad = 0.5*pObj->w;
+}
+static void circleNumProp(Pik *p, PObj *pObj, PToken *pId){
+  /* For a circle, the width must equal the height and both must
+  ** be twice the radius.  Enforce those constraints. */
+  switch( pId->eType ){
+    case T_DIAMETER:
+    case T_RADIUS:
+      pObj->w = pObj->h = 2.0*pObj->rad;
+      break;
+    case T_WIDTH:
+      pObj->h = pObj->w;
+      pObj->rad = 0.5*pObj->w;
+      break;
+    case T_HEIGHT:
+      pObj->w = pObj->h;
+      pObj->rad = 0.5*pObj->w;
+      break;
+  }
+  UNUSED_PARAMETER(p);
+}
+static PPoint circleChop(Pik *p, PObj *pObj, PPoint *pPt){
+  PPoint chop;
+  PNum dx = pPt->x - pObj->ptAt.x;
+  PNum dy = pPt->y - pObj->ptAt.y;
+  PNum dist = hypot(dx,dy);
+  if( dist<pObj->rad || dist<=0 ) return pObj->ptAt;
+  chop.x = pObj->ptAt.x + dx*pObj->rad/dist;
+  chop.y = pObj->ptAt.y + dy*pObj->rad/dist;
+  UNUSED_PARAMETER(p);
+  return chop;
+}
+static void circleFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  PNum mx = 0.0;
+  if( w>0 ) mx = w;
+  if( h>mx ) mx = h;
+  if( w*h>0 && (w*w + h*h) > mx*mx ){
+    mx = hypot(w,h);
+  }
+  if( mx>0.0 ){
+    pObj->rad = 0.5*mx;
+    pObj->w = pObj->h = mx;
+  }
+  UNUSED_PARAMETER(p);
+}
+
+static void circleRender(Pik *p, PObj *pObj){
+  PNum r = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    pik_append_x(p,"<circle cx=\"", pt.x, "\"");
+    pik_append_y(p," cy=\"", pt.y, "\"");
+    pik_append_dis(p," r=\"", r, "\" ");
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "cylinder" class */
+static void cylinderInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "cylwid",6,0);
+  pObj->h = pik_value(p, "cylht",5,0);
+  pObj->rad = pik_value(p, "cylrad",6,0); /* Minor radius of ellipses */
+}
+static void cylinderFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h + 0.25*pObj->rad + pObj->sw;
+  UNUSED_PARAMETER(p);
+}
+static void cylinderRender(Pik *p, PObj *pObj){
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    if( rad>h2 ){
+      rad = h2;
+    }else if( rad<0 ){
+      rad = 0;
+    }
+    pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y+h2-rad);
+    pik_append_xy(p,"L", pt.x-w2,pt.y-h2+rad);
+    pik_append_arc(p,w2,rad,pt.x+w2,pt.y-h2+rad);
+    pik_append_xy(p,"L", pt.x+w2,pt.y+h2-rad);
+    pik_append_arc(p,w2,rad,pt.x-w2,pt.y+h2-rad);
+    pik_append_arc(p,w2,rad,pt.x+w2,pt.y+h2-rad);
+    pik_append(p,"\" ",-1);
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+static PPoint cylinderOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w2 = pObj->w*0.5;
+  PNum h1 = pObj->h*0.5;
+  PNum h2 = h1 - pObj->rad;
+  switch( cp ){
+    case CP_C:                                break;
+    case CP_N:   pt.x = 0.0;   pt.y = h1;     break;
+    case CP_NE:  pt.x = w2;    pt.y = h2;     break;
+    case CP_E:   pt.x = w2;    pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2;    pt.y = -h2;    break;
+    case CP_S:   pt.x = 0.0;   pt.y = -h1;    break;
+    case CP_SW:  pt.x = -w2;   pt.y = -h2;    break;
+    case CP_W:   pt.x = -w2;   pt.y = 0.0;    break;
+    case CP_NW:  pt.x = -w2;   pt.y = h2;     break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+
+/* Methods for the "dot" class */
+static void dotInit(Pik *p, PObj *pObj){
+  pObj->rad = pik_value(p, "dotrad",6,0);
+  pObj->h = pObj->w = pObj->rad*6;
+  pObj->fill = pObj->color;
+}
+static void dotNumProp(Pik *p, PObj *pObj, PToken *pId){
+  switch( pId->eType ){
+    case T_COLOR:
+      pObj->fill = pObj->color;
+      break;
+    case T_FILL:
+      pObj->color = pObj->fill;
+      break;
+  }
+  UNUSED_PARAMETER(p);
+}
+static void dotCheck(Pik *p, PObj *pObj){
+  pObj->w = pObj->h = 0;
+  pik_bbox_addellipse(&pObj->bbox, pObj->ptAt.x, pObj->ptAt.y,
+                       pObj->rad, pObj->rad);
+  UNUSED_PARAMETER(p);
+}
+static PPoint dotOffset(Pik *p, PObj *pObj, int cp){
+  UNUSED_PARAMETER(p);
+  UNUSED_PARAMETER(pObj);
+  UNUSED_PARAMETER(cp);
+  return cZeroPoint;
+}
+static void dotRender(Pik *p, PObj *pObj){
+  PNum r = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    pik_append_x(p,"<circle cx=\"", pt.x, "\"");
+    pik_append_y(p," cy=\"", pt.y, "\"");
+    pik_append_dis(p," r=\"", r, "\"");
+    pik_append_style(p,pObj,2);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+
+
+/* Methods for the "ellipse" class */
+static void ellipseInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "ellipsewid",10,0);
+  pObj->h = pik_value(p, "ellipseht",9,0);
+}
+static PPoint ellipseChop(Pik *p, PObj *pObj, PPoint *pPt){
+  PPoint chop;
+  PNum s, dq, dist;
+  PNum dx = pPt->x - pObj->ptAt.x;
+  PNum dy = pPt->y - pObj->ptAt.y;
+  if( pObj->w<=0.0 ) return pObj->ptAt;
+  if( pObj->h<=0.0 ) return pObj->ptAt;
+  s = pObj->h/pObj->w;
+  dq = dx*s;
+  dist = hypot(dq,dy);
+  if( dist<pObj->h ) return pObj->ptAt;
+  chop.x = pObj->ptAt.x + 0.5*dq*pObj->h/(dist*s);
+  chop.y = pObj->ptAt.y + 0.5*dy*pObj->h/dist;
+  UNUSED_PARAMETER(p);
+  return chop;
+}
+static PPoint ellipseOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w = pObj->w*0.5;
+  PNum w2 = w*0.70710678118654747608;
+  PNum h = pObj->h*0.5;
+  PNum h2 = h*0.70710678118654747608;
+  switch( cp ){
+    case CP_C:                                break;
+    case CP_N:   pt.x = 0.0;   pt.y = h;      break;
+    case CP_NE:  pt.x = w2;    pt.y = h2;     break;
+    case CP_E:   pt.x = w;     pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2;    pt.y = -h2;    break;
+    case CP_S:   pt.x = 0.0;   pt.y = -h;     break;
+    case CP_SW:  pt.x = -w2;   pt.y = -h2;    break;
+    case CP_W:   pt.x = -w;    pt.y = 0.0;    break;
+    case CP_NW:  pt.x = -w2;   pt.y = h2;     break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+static void ellipseRender(Pik *p, PObj *pObj){
+  PNum w = pObj->w;
+  PNum h = pObj->h;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    pik_append_x(p,"<ellipse cx=\"", pt.x, "\"");
+    pik_append_y(p," cy=\"", pt.y, "\"");
+    pik_append_dis(p," rx=\"", w/2.0, "\"");
+    pik_append_dis(p," ry=\"", h/2.0, "\" ");
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "file" object */
+static void fileInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "filewid",7,0);
+  pObj->h = pik_value(p, "fileht",6,0);
+  pObj->rad = pik_value(p, "filerad",7,0);
+}
+/* Return offset from the center of the file to the compass point 
+** given by parameter cp */
+static PPoint fileOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rx = pObj->rad;
+  PNum mn = w2<h2 ? w2 : h2;
+  if( rx>mn ) rx = mn;
+  if( rx<mn*0.25 ) rx = mn*0.25;
+  pt.x = pt.y = 0.0;
+  rx *= 0.5;
+  switch( cp ){
+    case CP_C:                                   break;
+    case CP_N:   pt.x = 0.0;      pt.y = h2;     break;
+    case CP_NE:  pt.x = w2-rx;    pt.y = h2-rx;  break;
+    case CP_E:   pt.x = w2;       pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2;       pt.y = -h2;    break;
+    case CP_S:   pt.x = 0.0;      pt.y = -h2;    break;
+    case CP_SW:  pt.x = -w2;      pt.y = -h2;    break;
+    case CP_W:   pt.x = -w2;      pt.y = 0.0;    break;
+    case CP_NW:  pt.x = -w2;      pt.y = h2;     break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+static void fileFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h + 2*pObj->rad;
+  UNUSED_PARAMETER(p);
+}
+static void fileRender(Pik *p, PObj *pObj){
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  PNum mn = w2<h2 ? w2 : h2;
+  if( rad>mn ) rad = mn;
+  if( rad<mn*0.25 ) rad = mn*0.25;
+  if( pObj->sw>0.0 ){
+    pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y-h2);
+    pik_append_xy(p,"L", pt.x+w2,pt.y-h2);
+    pik_append_xy(p,"L", pt.x+w2,pt.y+(h2-rad));
+    pik_append_xy(p,"L", pt.x+(w2-rad),pt.y+h2);
+    pik_append_xy(p,"L", pt.x-w2,pt.y+h2);
+    pik_append(p,"Z\" ",-1);
+    pik_append_style(p,pObj,1);
+    pik_append(p,"\" />\n",-1);
+    pik_append_xy(p,"<path d=\"M", pt.x+(w2-rad), pt.y+h2);
+    pik_append_xy(p,"L", pt.x+(w2-rad),pt.y+(h2-rad));
+    pik_append_xy(p,"L", pt.x+w2, pt.y+(h2-rad));
+    pik_append(p,"\" ",-1);
+    pik_append_style(p,pObj,0);
+    pik_append(p,"\" />\n",-1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+
+/* Methods for the "line" class */
+static void lineInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "linewid",7,0);
+  pObj->h = pik_value(p, "lineht",6,0);
+  pObj->rad = pik_value(p, "linerad",7,0);
+}
+static PPoint lineOffset(Pik *p, PObj *pObj, int cp){
+#if 0
+  /* In legacy PIC, the .center of an unclosed line is half way between
+  ** its .start and .end. */
+  if( cp==CP_C && !pObj->bClose ){
+    PPoint out;
+    out.x = 0.5*(pObj->ptEnter.x + pObj->ptExit.x) - pObj->ptAt.x;
+    out.y = 0.5*(pObj->ptEnter.x + pObj->ptExit.y) - pObj->ptAt.y;
+    return out;
+  }
+#endif
+  return boxOffset(p,pObj,cp);
+}
+static void lineRender(Pik *p, PObj *pObj){
+  int i;
+  if( pObj->sw>0.0 ){
+    const char *z = "<path d=\"M";
+    int n = pObj->nPath;
+    if( pObj->larrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj);
+    }
+    if( pObj->rarrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj);
+    }
+    for(i=0; i<pObj->nPath; i++){
+      pik_append_xy(p,z,pObj->aPath[i].x,pObj->aPath[i].y);
+      z = "L";
+    }
+    if( pObj->bClose ){
+      pik_append(p,"Z",1);
+    }else{
+      pObj->fill = -1.0;
+    }
+    pik_append(p,"\" ",-1);
+    pik_append_style(p,pObj,pObj->bClose?3:0);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "move" class */
+static void moveInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "movewid",7,0);
+  pObj->h = pObj->w;
+  pObj->fill = -1.0;
+  pObj->color = -1.0;
+  pObj->sw = -1.0;
+}
+static void moveRender(Pik *p, PObj *pObj){
+  /* No-op */
+  UNUSED_PARAMETER(p);
+  UNUSED_PARAMETER(pObj);
+}
+
+/* Methods for the "oval" class */
+static void ovalInit(Pik *p, PObj *pObj){
+  pObj->h = pik_value(p, "ovalht",6,0);
+  pObj->w = pik_value(p, "ovalwid",7,0);
+  pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
+}
+static void ovalNumProp(Pik *p, PObj *pObj, PToken *pId){
+  UNUSED_PARAMETER(p);
+  UNUSED_PARAMETER(pId);
+  /* Always adjust the radius to be half of the smaller of
+  ** the width and height. */
+  pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
+}
+static void ovalFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  UNUSED_PARAMETER(p);
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h;
+  if( pObj->w<pObj->h ) pObj->w = pObj->h;
+  pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
+}
+
+
+
+/* Methods for the "spline" class */
+static void splineInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "linewid",7,0);
+  pObj->h = pik_value(p, "lineht",6,0);
+  pObj->rad = 1000;
+}
+/* Return a point along the path from "f" to "t" that is r units
+** prior to reaching "t", except if the path is less than 2*r total,
+** return the midpoint.
+*/
+static PPoint radiusMidpoint(PPoint f, PPoint t, PNum r, int *pbMid){
+  PNum dx = t.x - f.x;
+  PNum dy = t.y - f.y;
+  PNum dist = hypot(dx,dy);
+  PPoint m;
+  if( dist<=0.0 ) return t;
+  dx /= dist;
+  dy /= dist;
+  if( r > 0.5*dist ){
+    r = 0.5*dist;
+    *pbMid = 1;
+  }else{
+    *pbMid = 0;
+  }
+  m.x = t.x - r*dx;
+  m.y = t.y - r*dy;
+  return m;
+}
+static void radiusPath(Pik *p, PObj *pObj, PNum r){
+  int i;
+  int n = pObj->nPath;
+  const PPoint *a = pObj->aPath;
+  PPoint m;
+  PPoint an = a[n-1];
+  int isMid = 0;
+  int iLast = pObj->bClose ? n : n-1;
+
+  pik_append_xy(p,"<path d=\"M", a[0].x, a[0].y);
+  m = radiusMidpoint(a[0], a[1], r, &isMid);
+  pik_append_xy(p," L ",m.x,m.y);
+  for(i=1; i<iLast; i++){
+    an = i<n-1 ? a[i+1] : a[0];
+    m = radiusMidpoint(an,a[i],r, &isMid);
+    pik_append_xy(p," Q ",a[i].x,a[i].y);
+    pik_append_xy(p," ",m.x,m.y);
+    if( !isMid ){
+      m = radiusMidpoint(a[i],an,r, &isMid);
+      pik_append_xy(p," L ",m.x,m.y);
+    }
+  }
+  pik_append_xy(p," L ",an.x,an.y);
+  if( pObj->bClose ){
+    pik_append(p,"Z",1);
+  }else{
+    pObj->fill = -1.0;
+  }
+  pik_append(p,"\" ",-1);
+  pik_append_style(p,pObj,pObj->bClose?3:0);
+  pik_append(p,"\" />\n", -1);
+}
+static void splineRender(Pik *p, PObj *pObj){
+  if( pObj->sw>0.0 ){
+    int n = pObj->nPath;
+    PNum r = pObj->rad;
+    if( n<3 || r<=0.0 ){
+      lineRender(p,pObj);
+      return;
+    }
+    if( pObj->larrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj);
+    }
+    if( pObj->rarrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj);
+    }
+    radiusPath(p,pObj,pObj->rad);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+
+/* Methods for the "text" class */
+static void textInit(Pik *p, PObj *pObj){
+  pik_value(p, "textwid",7,0);
+  pik_value(p, "textht",6,0);
+  pObj->sw = 0.0;
+}
+static PPoint textOffset(Pik *p, PObj *pObj, int cp){
+  /* Automatically slim-down the width and height of text
+  ** statements so that the bounding box tightly encloses the text,
+  ** then get boxOffset() to do the offset computation.
+  */
+  pik_size_to_fit(p, &pObj->errTok,3);
+  return boxOffset(p, pObj, cp);
+}
+
+/* Methods for the "sublist" class */
+static void sublistInit(Pik *p, PObj *pObj){
+  PList *pList = pObj->pSublist;
+  int i;
+  UNUSED_PARAMETER(p);
+  pik_bbox_init(&pObj->bbox);
+  for(i=0; i<pList->n; i++){
+    pik_bbox_addbox(&pObj->bbox, &pList->a[i]->bbox);
+  }
+  pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x;
+  pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y;
+  pObj->ptAt.x = 0.5*(pObj->bbox.ne.x + pObj->bbox.sw.x);
+  pObj->ptAt.y = 0.5*(pObj->bbox.ne.y + pObj->bbox.sw.y);
+  pObj->mCalc |= A_WIDTH|A_HEIGHT|A_RADIUS;
+}
+
+
+/*
+** The following array holds all the different kinds of objects.
+** The special [] object is separate.
+*/
+static const PClass aClass[] = {
+   {  /* name */          "arc",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         arcInit,
+      /* xNumProp */      0,
+      /* xCheck */        arcCheck,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       arcRender
+   },
+   {  /* name */          "arrow",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         arrowInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       lineOffset,
+      /* xFit */          0,
+      /* xRender */       splineRender 
+   },
+   {  /* name */          "box",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         boxInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       boxOffset,
+      /* xFit */          boxFit,
+      /* xRender */       boxRender 
+   },
+   {  /* name */          "circle",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         circleInit,
+      /* xNumProp */      circleNumProp,
+      /* xCheck */        0,
+      /* xChop */         circleChop,
+      /* xOffset */       ellipseOffset,
+      /* xFit */          circleFit,
+      /* xRender */       circleRender 
+   },
+   {  /* name */          "cylinder",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         cylinderInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       cylinderOffset,
+      /* xFit */          cylinderFit,
+      /* xRender */       cylinderRender
+   },
+   {  /* name */          "dot",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         dotInit,
+      /* xNumProp */      dotNumProp,
+      /* xCheck */        dotCheck,
+      /* xChop */         circleChop,
+      /* xOffset */       dotOffset,
+      /* xFit */          0,
+      /* xRender */       dotRender 
+   },
+   {  /* name */          "ellipse",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         ellipseInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         ellipseChop,
+      /* xOffset */       ellipseOffset,
+      /* xFit */          boxFit,
+      /* xRender */       ellipseRender
+   },
+   {  /* name */          "file",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         fileInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       fileOffset,
+      /* xFit */          fileFit,
+      /* xRender */       fileRender 
+   },
+   {  /* name */          "line",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         lineInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       lineOffset,
+      /* xFit */          0,
+      /* xRender */       splineRender
+   },
+   {  /* name */          "move",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         moveInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       moveRender
+   },
+   {  /* name */          "oval",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         ovalInit,
+      /* xNumProp */      ovalNumProp,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       boxOffset,
+      /* xFit */          ovalFit,
+      /* xRender */       boxRender
+   },
+   {  /* name */          "spline",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         splineInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       lineOffset,
+      /* xFit */          0,
+      /* xRender */       splineRender
+   },
+   {  /* name */          "text",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         textInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       textOffset,
+      /* xFit */          boxFit,
+      /* xRender */       boxRender 
+   },
+};
+static const PClass sublistClass = 
+   {  /* name */          "[]",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         sublistInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       0 
+   };
+static const PClass noopClass = 
+   {  /* name */          "noop",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         0,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       0
+   };
+
+
+/*
+** Reduce the length of the line segment by amt (if possible) by
+** modifying the location of *t.
+*/
+static void pik_chop(PPoint *f, PPoint *t, PNum amt){
+  PNum dx = t->x - f->x;
+  PNum dy = t->y - f->y;
+  PNum dist = hypot(dx,dy);
+  PNum r;
+  if( dist<=amt ){
+    *t = *f;
+    return;
+  }
+  r = 1.0 - amt/dist;
+  t->x = f->x + r*dx;
+  t->y = f->y + r*dy;
+}
+
+/*
+** Draw an arrowhead on the end of the line segment from pFrom to pTo.
+** Also, shorten the line segment (by changing the value of pTo) so that
+** the shaft of the arrow does not extend into the arrowhead.
+*/
+static void pik_draw_arrowhead(Pik *p, PPoint *f, PPoint *t, PObj *pObj){
+  PNum dx = t->x - f->x;
+  PNum dy = t->y - f->y;
+  PNum dist = hypot(dx,dy);
+  PNum h = p->hArrow * pObj->sw;
+  PNum w = p->wArrow * pObj->sw;
+  PNum e1, ddx, ddy;
+  PNum bx, by;
+  if( pObj->color<0.0 ) return;
+  if( pObj->sw<=0.0 ) return;
+  if( dist<=0.0 ) return;  /* Unable */
+  dx /= dist;
+  dy /= dist;
+  e1 = dist - h;
+  if( e1<0.0 ){
+    e1 = 0.0;
+    h = dist;
+  }
+  ddx = -w*dy;
+  ddy = w*dx;
+  bx = f->x + e1*dx;
+  by = f->y + e1*dy;
+  pik_append_xy(p,"<polygon points=\"", t->x, t->y);
+  pik_append_xy(p," ",bx-ddx, by-ddy);
+  pik_append_xy(p," ",bx+ddx, by+ddy);
+  pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n",0);
+  pik_chop(f,t,h/2);
+}
+
+/*
+** Compute the relative offset to an edge location from the reference for a
+** an statement.
+*/
+static PPoint pik_elem_offset(Pik *p, PObj *pObj, int cp){
+  return pObj->type->xOffset(p, pObj, cp);
+}
+
+
+/*
+** Append raw text to zOut
+*/
+static void pik_append(Pik *p, const char *zText, int n){
+  if( n<0 ) n = (int)strlen(zText);
+  if( p->nOut+n>=p->nOutAlloc ){
+    int nNew = (p->nOut+n)*2 + 1;
+    char *z = realloc(p->zOut, nNew);
+    if( z==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }
+    p->zOut = z;
+    p->nOutAlloc = nNew;
+  }
+  memcpy(p->zOut+p->nOut, zText, n);
+  p->nOut += n;
+  p->zOut[p->nOut] = 0;
+}
+
+/*
+** Given a string and its length, returns true if the string begins
+** with a construct which syntactically matches an HTML entity escape
+** sequence (without checking for whether it's a known entity). Always
+** returns false if zText[0] is false or n<4. Entities match the
+** equivalent of the regexes `&#[0-9]{2,};` and
+** `&[a-zA-Z][a-zA-Z0-9]+;`.
+*/
+static int pik_isentity(char const * zText, int n){
+  int i = 0;
+  if( n<4 || '&'!=zText[0] ) return 0;
+  n--;
+  zText++;
+  if( '#'==zText[0] ){
+    zText++;
+    n--;
+    for(i=0; i<n; i++){
+      if( i>1 && ';'==zText[i] ) return 1;
+      else if( zText[i]<'0' || zText[i]>'9' ) return 0;
+      /* Note that &#nn; values nn<32d are not legal entities. */
+    }
+  }else{
+    for(i=0; i<n; i++){
+      if( i>1 && ';'==zText[i] ) return 1;
+      else if( i>0 && zText[i]>='0' && zText[i]<='9' ){
+          continue;
+      }else if( zText[i]<'A' || zText[i]>'z'
+               || (zText[i]>'Z' && zText[i]<'a') ) return 0;
+    }
+  }
+  return 0;
+}
+
+/*
+** Append text to zOut with HTML characters escaped.
+**
+**   *  The space character is changed into non-breaking space (U+00a0)
+**      if mFlags has the 0x01 bit set. This is needed when outputting
+**      text to preserve leading and trailing whitespace.  Turns out we
+**      cannot use &nbsp; as that is an HTML-ism and is not valid in XML.
+**
+**   *  The "&" character is changed into "&amp;" if mFlags has the
+**      0x02 bit set.  This is needed when generating error message text.
+**
+**   *  Except for the above, only "<" and ">" are escaped.
+*/
+static void pik_append_text(Pik *p, const char *zText, int n, int mFlags){
+  int i;
+  char c = 0;
+  int bQSpace = mFlags & 1;
+  int bQAmp = mFlags & 2;
+  if( n<0 ) n = (int)strlen(zText);
+  while( n>0 ){
+    for(i=0; i<n; i++){
+      c = zText[i];
+      if( c=='<' || c=='>' ) break;
+      if( c==' ' && bQSpace ) break;
+      if( c=='&' && bQAmp ) break;
+    }
+    if( i ) pik_append(p, zText, i);
+    if( i==n ) break;
+    switch( c ){
+      case '<': {  pik_append(p, "&lt;", 4);  break;  }
+      case '>': {  pik_append(p, "&gt;", 4);  break;  }
+      case ' ': {  pik_append(p, "\302\240;", 2);  break;  }
+      case '&':
+        if( pik_isentity(zText+i, n-i) ){ pik_append(p, "&", 1); }
+        else { pik_append(p, "&amp;", 5); }
+    }
+    i++;
+    n -= i;
+    zText += i;
+    i = 0;
+  }
+}
+
+/*
+** Append error message text.  This is either a raw append, or an append
+** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag
+** is set.
+*/
+static void pik_append_errtxt(Pik *p, const char *zText, int n){
+  if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){
+    pik_append(p, zText, n);
+  }else{
+    pik_append_text(p, zText, n, 0);
+  }
+}
+
+/* Append a PNum value
+*/
+static void pik_append_num(Pik *p, const char *z,PNum v){
+  char buf[100];
+  snprintf(buf, sizeof(buf)-1, "%.10g", (double)v);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, z, -1);
+  pik_append(p, buf, -1);
+}
+
+/* Append a PPoint value  (Used for debugging only)
+*/
+static void pik_append_point(Pik *p, const char *z, PPoint *pPt){
+  char buf[100];
+  snprintf(buf, sizeof(buf)-1, "%.10g,%.10g", 
+          (double)pPt->x, (double)pPt->y);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, z, -1);
+  pik_append(p, buf, -1);
+}
+
+/*
+** Invert the RGB color so that it is appropriate for dark mode.
+** Variable x hold the initial color.  The color is intended for use
+** as a background color if isBg is true, and as a foreground color
+** if isBg is false.
+*/
+static int pik_color_to_dark_mode(int x, int isBg){
+  int r, g, b;
+  int mn, mx;
+  x = 0xffffff - x;
+  r = (x>>16) & 0xff;
+  g = (x>>8) & 0xff;
+  b = x & 0xff;
+  mx = r;
+  if( g>mx ) mx = g;
+  if( b>mx ) mx = b;
+  mn = r;
+  if( g<mn ) mn = g;
+  if( b<mn ) mn = b;
+  r = mn + (mx-r);
+  g = mn + (mx-g);
+  b = mn + (mx-b);
+  if( isBg ){
+    if( mx>127 ){
+      r = (127*r)/mx;
+      g = (127*g)/mx;
+      b = (127*b)/mx;
+    }
+  }else{
+    if( mn<128 && mx>mn ){
+      r = 127 + ((r-mn)*128)/(mx-mn);
+      g = 127 + ((g-mn)*128)/(mx-mn);
+      b = 127 + ((b-mn)*128)/(mx-mn);
+    }
+  }
+  return r*0x10000 + g*0x100 + b;
+}
+
+/* Append a PNum value surrounded by text.  Do coordinate transformations
+** on the value.
+*/
+static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){
+  char buf[200];
+  v -= p->bbox.sw.x;
+  snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+static void pik_append_y(Pik *p, const char *z1, PNum v, const char *z2){
+  char buf[200];
+  v = p->bbox.ne.y - v;
+  snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+static void pik_append_xy(Pik *p, const char *z1, PNum x, PNum y){
+  char buf[200];
+  x = x - p->bbox.sw.x;
+  y = p->bbox.ne.y - y;
+  snprintf(buf, sizeof(buf)-1, "%s%g,%g", z1, p->rScale*x, p->rScale*y);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+static void pik_append_dis(Pik *p, const char *z1, PNum v, const char *z2){
+  char buf[200];
+  snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+
+/* Append a color specification to the output.
+**
+** In PIKCHR_DARK_MODE, the color is inverted.  The "bg" flags indicates that
+** the color is intended for use as a background color if true, or as a
+** foreground color if false.  The distinction only matters for color
+** inversions in PIKCHR_DARK_MODE.
+*/
+static void pik_append_clr(Pik *p,const char *z1,PNum v,const char *z2,int bg){
+  char buf[200];
+  int x = pik_round(v);
+  int r, g, b;
+  if( x==0 && p->fgcolor>0 && !bg ){
+    x = p->fgcolor;
+  }else if( bg && x>=0xffffff && p->bgcolor>0 ){
+    x = p->bgcolor;
+  }else if( p->mFlags & PIKCHR_DARK_MODE ){
+    x = pik_color_to_dark_mode(x,bg);
+  }
+  r = (x>>16) & 0xff;
+  g = (x>>8) & 0xff;
+  b = x & 0xff;
+  snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+
+/* Append an SVG path A record:
+**
+**    A r1 r2 0 0 0 x y
+*/
+static void pik_append_arc(Pik *p, PNum r1, PNum r2, PNum x, PNum y){
+  char buf[200];
+  x = x - p->bbox.sw.x;
+  y = p->bbox.ne.y - y;
+  snprintf(buf, sizeof(buf)-1, "A%g %g 0 0 0 %g %g", 
+     p->rScale*r1, p->rScale*r2,
+     p->rScale*x, p->rScale*y);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+
+/* Append a style="..." text.  But, leave the quote unterminated, in case
+** the caller wants to add some more.
+**
+** eFill is non-zero to fill in the background, or 0 if no fill should
+** occur.  Non-zero values of eFill determine the "bg" flag to pik_append_clr()
+** for cases when pObj->fill==pObj->color
+**
+**     1        fill is background, and color is foreground.
+**     2        fill and color are both foreground.  (Used by "dot" objects)
+**     3        fill and color are both background.  (Used by most other objs)
+*/
+static void pik_append_style(Pik *p, PObj *pObj, int eFill){
+  int clrIsBg = 0;
+  pik_append(p, " style=\"", -1);
+  if( pObj->fill>=0 && eFill ){
+    int fillIsBg = 1;
+    if( pObj->fill==pObj->color ){
+      if( eFill==2 ) fillIsBg = 0;
+      if( eFill==3 ) clrIsBg = 1;
+    }
+    pik_append_clr(p, "fill:", pObj->fill, ";", fillIsBg);
+  }else{
+    pik_append(p,"fill:none;",-1);
+  }
+  if( pObj->sw>0.0 && pObj->color>=0.0 ){
+    PNum sw = pObj->sw;
+    pik_append_dis(p, "stroke-width:", sw, ";");
+    if( pObj->nPath>2 && pObj->rad<=pObj->sw ){
+      pik_append(p, "stroke-linejoin:round;", -1);
+    }
+    pik_append_clr(p, "stroke:",pObj->color,";",clrIsBg);
+    if( pObj->dotted>0.0 ){
+      PNum v = pObj->dotted;
+      if( sw<2.1/p->rScale ) sw = 2.1/p->rScale;
+      pik_append_dis(p,"stroke-dasharray:",sw,"");
+      pik_append_dis(p,",",v,";");
+    }else if( pObj->dashed>0.0 ){
+      PNum v = pObj->dashed;
+      pik_append_dis(p,"stroke-dasharray:",v,"");
+      pik_append_dis(p,",",v,";");
+    }
+  }
+}
+
+/*
+** Compute the vertical locations for all text items in the
+** object pObj.  In other words, set every pObj->aTxt[*].eCode
+** value to contain exactly one of: TP_ABOVE2, TP_ABOVE, TP_CENTER,
+** TP_BELOW, or TP_BELOW2 is set.
+*/
+static void pik_txt_vertical_layout(PObj *pObj){
+  int n, i;
+  PToken *aTxt;
+  n = pObj->nTxt;
+  if( n==0 ) return;
+  aTxt = pObj->aTxt;
+  if( n==1 ){
+    if( (aTxt[0].eCode & TP_VMASK)==0 ){
+      aTxt[0].eCode |= TP_CENTER;
+    }
+  }else{
+    int allSlots = 0;
+    int aFree[5];
+    int iSlot;
+    int j, mJust;
+    /* If there is more than one TP_ABOVE, change the first to TP_ABOVE2. */
+    for(j=mJust=0, i=n-1; i>=0; i--){
+      if( aTxt[i].eCode & TP_ABOVE ){
+        if( j==0 ){
+          j++;
+          mJust = aTxt[i].eCode & TP_JMASK;
+        }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){
+          j++;
+        }else{
+          aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_ABOVE2;
+          break;
+        }
+      }
+    }
+    /* If there is more than one TP_BELOW, change the last to TP_BELOW2 */
+    for(j=mJust=0, i=0; i<n; i++){
+      if( aTxt[i].eCode & TP_BELOW ){
+        if( j==0 ){
+          j++;
+          mJust = aTxt[i].eCode & TP_JMASK;
+        }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){
+          j++;
+        }else{
+          aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_BELOW2;
+          break;
+        }
+      }
+    }
+    /* Compute a mask of all slots used */
+    for(i=0; i<n; i++) allSlots |= aTxt[i].eCode & TP_VMASK;
+    /* Set of an array of available slots */
+    if( n==2
+     && ((aTxt[0].eCode|aTxt[1].eCode)&TP_JMASK)==(TP_LJUST|TP_RJUST)
+    ){
+      /* Special case of two texts that have opposite justification:
+      ** Allow them both to float to center. */
+      iSlot = 2;
+      aFree[0] = aFree[1] = TP_CENTER;
+    }else{
+      /* Set up the arrow so that available slots are filled from top to
+      ** bottom */
+      iSlot = 0;
+      if( n>=4 && (allSlots & TP_ABOVE2)==0 ) aFree[iSlot++] = TP_ABOVE2;
+      if( (allSlots & TP_ABOVE)==0 ) aFree[iSlot++] = TP_ABOVE;
+      if( (n&1)!=0 ) aFree[iSlot++] = TP_CENTER;
+      if( (allSlots & TP_BELOW)==0 ) aFree[iSlot++] = TP_BELOW;
+      if( n>=4 && (allSlots & TP_BELOW2)==0 ) aFree[iSlot++] = TP_BELOW2;
+    }
+    /* Set the VMASK for all unassigned texts */
+    for(i=iSlot=0; i<n; i++){
+      if( (aTxt[i].eCode & TP_VMASK)==0 ){
+        aTxt[i].eCode |= aFree[iSlot++];
+      }
+    }
+  }
+}
+
+/* Return the font scaling factor associated with the input text attribute.
+*/
+static PNum pik_font_scale(PToken *t){
+  PNum scale = 1.0;
+  if( t->eCode & TP_BIG    ) scale *= 1.25;
+  if( t->eCode & TP_SMALL  ) scale *= 0.8;
+  if( t->eCode & TP_XTRA   ) scale *= scale;
+  return scale;
+}
+
+/* Append multiple <text> SVG elements for the text fields of the PObj.
+** Parameters:
+**
+**    p          The Pik object into which we are rendering
+**
+**    pObj       Object containing the text to be rendered
+**
+**    pBox       If not NULL, do no rendering at all.  Instead
+**               expand the box object so that it will include all
+**               of the text.
+*/
+static void pik_append_txt(Pik *p, PObj *pObj, PBox *pBox){
+  PNum jw;          /* Justification margin relative to center */
+  PNum ha2 = 0.0;   /* Height of the top row of text */
+  PNum ha1 = 0.0;   /* Height of the second "above" row */
+  PNum hc = 0.0;    /* Height of the center row */
+  PNum hb1 = 0.0;   /* Height of the first "below" row of text */
+  PNum hb2 = 0.0;   /* Height of the second "below" row */
+  PNum yBase = 0.0;
+  int n, i, nz;
+  PNum x, y, orig_y, s;
+  const char *z;
+  PToken *aTxt;
+  unsigned allMask = 0;
+
+  if( p->nErr ) return;
+  if( pObj->nTxt==0 ) return;
+  aTxt = pObj->aTxt;
+  n = pObj->nTxt;
+  pik_txt_vertical_layout(pObj);
+  x = pObj->ptAt.x;
+  for(i=0; i<n; i++) allMask |= pObj->aTxt[i].eCode;
+  if( pObj->type->isLine ){
+    hc = pObj->sw*1.5;
+  }else if( pObj->rad>0.0 && pObj->type->xInit==cylinderInit ){
+    yBase = -0.75*pObj->rad;
+  }
+  if( allMask & TP_CENTER ){
+    for(i=0; i<n; i++){
+      if( pObj->aTxt[i].eCode & TP_CENTER ){
+        s = pik_font_scale(pObj->aTxt+i);
+        if( hc<s*p->charHeight ) hc = s*p->charHeight;
+      }
+    }
+  }
+  if( allMask & TP_ABOVE ){
+    for(i=0; i<n; i++){
+      if( pObj->aTxt[i].eCode & TP_ABOVE ){
+        s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+        if( ha1<s ) ha1 = s;
+      }
+    }
+    if( allMask & TP_ABOVE2 ){
+      for(i=0; i<n; i++){
+        if( pObj->aTxt[i].eCode & TP_ABOVE2 ){
+          s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+          if( ha2<s ) ha2 = s;
+        }
+      }
+    }
+  }
+  if( allMask & TP_BELOW ){
+    for(i=0; i<n; i++){
+      if( pObj->aTxt[i].eCode & TP_BELOW ){
+        s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+        if( hb1<s ) hb1 = s;
+      }
+    }
+    if( allMask & TP_BELOW2 ){
+      for(i=0; i<n; i++){
+        if( pObj->aTxt[i].eCode & TP_BELOW2 ){
+          s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+          if( hb2<s ) hb2 = s;
+        }
+      }
+    }
+  }
+  if( pObj->type->eJust==1 ){
+    jw = 0.5*(pObj->w - 0.5*(p->charWidth + pObj->sw));
+  }else{
+    jw = 0.0;
+  }
+  for(i=0; i<n; i++){
+    PToken *t = &aTxt[i];
+    PNum xtraFontScale = pik_font_scale(t);
+    PNum nx = 0;
+    orig_y = pObj->ptAt.y;
+    y = yBase;
+    if( t->eCode & TP_ABOVE2 ) y += 0.5*hc + ha1 + 0.5*ha2;
+    if( t->eCode & TP_ABOVE  ) y += 0.5*hc + 0.5*ha1;
+    if( t->eCode & TP_BELOW  ) y -= 0.5*hc + 0.5*hb1;
+    if( t->eCode & TP_BELOW2 ) y -= 0.5*hc + hb1 + 0.5*hb2;
+    if( t->eCode & TP_LJUST  ) nx -= jw;
+    if( t->eCode & TP_RJUST  ) nx += jw;
+
+    if( pBox!=0 ){
+      /* If pBox is not NULL, do not draw any <text>.  Instead, just expand
+      ** pBox to include the text */
+      PNum cw = pik_text_length(t)*p->charWidth*xtraFontScale*0.01;
+      PNum ch = p->charHeight*0.5*xtraFontScale;
+      PNum x0, y0, x1, y1;  /* Boundary of text relative to pObj->ptAt */
+      if( t->eCode & TP_BOLD ) cw *= 1.1;
+      if( t->eCode & TP_RJUST ){
+        x0 = nx;
+        y0 = y-ch;
+        x1 = nx-cw;
+        y1 = y+ch;
+      }else if( t->eCode & TP_LJUST ){
+        x0 = nx;
+        y0 = y-ch;
+        x1 = nx+cw;
+        y1 = y+ch;
+      }else{
+        x0 = nx+cw/2;
+        y0 = y+ch;
+        x1 = nx-cw/2;
+        y1 = y-ch;
+      }
+      if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){
+        int nn = pObj->nPath;
+        PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x;
+        PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y;
+        if( dx!=0 || dy!=0 ){
+          PNum dist = hypot(dx,dy);
+          PNum tt;
+          dx /= dist;
+          dy /= dist;
+          tt = dx*x0 - dy*y0;
+          y0 = dy*x0 - dx*y0;
+          x0 = tt;
+          tt = dx*x1 - dy*y1;
+          y1 = dy*x1 - dx*y1;
+          x1 = tt;
+        }
+      }
+      pik_bbox_add_xy(pBox, x+x0, orig_y+y0);
+      pik_bbox_add_xy(pBox, x+x1, orig_y+y1);
+      continue;
+    }
+    nx += x;
+    y += orig_y;
+
+    pik_append_x(p, "<text x=\"", nx, "\"");
+    pik_append_y(p, " y=\"", y, "\"");
+    if( t->eCode & TP_RJUST ){
+      pik_append(p, " text-anchor=\"end\"", -1);
+    }else if( t->eCode & TP_LJUST ){
+      pik_append(p, " text-anchor=\"start\"", -1);
+    }else{
+      pik_append(p, " text-anchor=\"middle\"", -1);
+    }
+    if( t->eCode & TP_ITALIC ){
+      pik_append(p, " font-style=\"italic\"", -1);
+    }
+    if( t->eCode & TP_BOLD ){
+      pik_append(p, " font-weight=\"bold\"", -1);
+    }
+    if( pObj->color>=0.0 ){
+      pik_append_clr(p, " fill=\"", pObj->color, "\"",0);
+    }
+    xtraFontScale *= p->fontScale;
+    if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){
+      pik_append_num(p, " font-size=\"", xtraFontScale*100.0);
+      pik_append(p, "%\"", 2);
+    }
+    if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){
+      int nn = pObj->nPath;
+      PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x;
+      PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y;
+      if( dx!=0 || dy!=0 ){
+        PNum ang = atan2(dy,dx)*-180/M_PI;
+        pik_append_num(p, " transform=\"rotate(", ang);
+        pik_append_xy(p, " ", x, orig_y);
+        pik_append(p,")\"",2);
+      }
+    }
+    pik_append(p," dominant-baseline=\"central\">",-1);
+    if( t->n>=2 && t->z[0]=='"' ){
+      z = t->z+1;
+      nz = t->n-2;
+    }else{
+      z = t->z;
+      nz = t->n;
+    }
+    while( nz>0 ){
+      int j;
+      for(j=0; j<nz && z[j]!='\\'; j++){}
+      if( j ) pik_append_text(p, z, j, 0x3);
+      if( j<nz && (j+1==nz || z[j+1]=='\\') ){
+        pik_append(p, "&#92;", -1);
+        j++;
+      }
+      nz -= j+1;
+      z += j+1;
+    }
+    pik_append(p, "</text>\n", -1);
+  }
+}
+
+/*
+** Append text (that will go inside of a <pre>...</pre>) that
+** shows the context of an error token.
+*/
+static void pik_error_context(Pik *p, PToken *pErr, int nContext){
+  int iErrPt;           /* Index of first byte of error from start of input */
+  int iErrCol;          /* Column of the error token on its line */
+  int iStart;           /* Start position of the error context */
+  int iEnd;             /* End position of the error context */
+  int iLineno;          /* Line number of the error */
+  int iFirstLineno;     /* Line number of start of error context */
+  int i;                /* Loop counter */
+  int iBump = 0;        /* Bump the location of the error cursor */
+  char zLineno[20];     /* Buffer in which to generate line numbers */
+
+  iErrPt = (int)(pErr->z - p->sIn.z);
+  if( iErrPt>=(int)p->sIn.n ){
+    iErrPt = p->sIn.n-1;
+    iBump = 1;
+  }else{
+    while( iErrPt>0 && (p->sIn.z[iErrPt]=='\n' || p->sIn.z[iErrPt]=='\r') ){
+      iErrPt--;
+      iBump = 1;
+    }
+  }
+  iLineno = 1;
+  for(i=0; i<iErrPt; i++){
+    if( p->sIn.z[i]=='\n' ){
+      iLineno++;
+    }
+  }
+  iStart = 0;
+  iFirstLineno = 1;
+  while( iFirstLineno+nContext<iLineno ){
+    while( p->sIn.z[iStart]!='\n' ){ iStart++; }
+    iStart++;
+    iFirstLineno++;
+  }
+  for(iEnd=iErrPt; p->sIn.z[iEnd]!=0 && p->sIn.z[iEnd]!='\n'; iEnd++){}
+  i = iStart;
+  while( iFirstLineno<=iLineno ){
+    snprintf(zLineno,sizeof(zLineno)-1,"/* %4d */  ", iFirstLineno++);
+    zLineno[sizeof(zLineno)-1] = 0;
+    pik_append(p, zLineno, -1);
+    for(i=iStart; p->sIn.z[i]!=0 && p->sIn.z[i]!='\n'; i++){}
+    pik_append_errtxt(p, p->sIn.z+iStart, i-iStart);
+    iStart = i+1;
+    pik_append(p, "\n", 1);
+  }
+  for(iErrCol=0, i=iErrPt; i>0 && p->sIn.z[i]!='\n'; iErrCol++, i--){}
+  for(i=0; i<iErrCol+11+iBump; i++){ pik_append(p, " ", 1); }
+  for(i=0; i<(int)pErr->n; i++) pik_append(p, "^", 1);
+  pik_append(p, "\n", 1);
+}
+
+
+/*
+** Generate an error message for the output.  pErr is the token at which
+** the error should point.  zMsg is the text of the error message. If
+** either pErr or zMsg is NULL, generate an out-of-memory error message.
+**
+** This routine is a no-op if there has already been an error reported.
+*/
+static void pik_error(Pik *p, PToken *pErr, const char *zMsg){
+  int i;
+  if( p==0 ) return;
+  if( p->nErr ) return;
+  p->nErr++;
+  if( zMsg==0 ){
+    if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){
+      pik_append(p, "\nOut of memory\n", -1);
+    }else{
+      pik_append(p, "\n<div><p>Out of memory</p></div>\n", -1);
+    }
+    return;
+  }
+  if( pErr==0 ){
+    pik_append(p, "\n", 1);
+    pik_append_errtxt(p, zMsg, -1);
+    return;
+  }
+  if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
+    pik_append(p, "<div><pre>\n", -1);
+  }
+  pik_error_context(p, pErr, 5);
+  pik_append(p, "ERROR: ", -1);
+  pik_append_errtxt(p, zMsg, -1);
+  pik_append(p, "\n", 1);
+  for(i=p->nCtx-1; i>=0; i--){
+    pik_append(p, "Called from:\n", -1);
+    pik_error_context(p, &p->aCtx[i], 0);
+  }
+  if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
+    pik_append(p, "</pre></div>\n", -1);
+  }
+}
+
+/*
+** Process an "assert( e1 == e2 )" statement.  Always return NULL.
+*/
+static PObj *pik_assert(Pik *p, PNum e1, PToken *pEq, PNum e2){
+  char zE1[100], zE2[100], zMsg[300];
+
+  /* Convert the numbers to strings using %g for comparison.  This
+  ** limits the precision of the comparison to account for rounding error. */
+  snprintf(zE1, sizeof(zE1), "%g", e1); zE1[sizeof(zE1)-1] = 0;
+  snprintf(zE2, sizeof(zE2), "%g", e2); zE1[sizeof(zE2)-1] = 0;
+  if( strcmp(zE1,zE2)!=0 ){
+    snprintf(zMsg, sizeof(zMsg), "%.50s != %.50s", zE1, zE2);
+    pik_error(p, pEq, zMsg);
+  }
+  return 0;
+}
+
+/*
+** Process an "assert( place1 == place2 )" statement.  Always return NULL.
+*/
+static PObj *pik_position_assert(Pik *p, PPoint *e1, PToken *pEq, PPoint *e2){
+  char zE1[100], zE2[100], zMsg[210];
+
+  /* Convert the numbers to strings using %g for comparison.  This
+  ** limits the precision of the comparison to account for rounding error. */
+  snprintf(zE1, sizeof(zE1), "(%g,%g)", e1->x, e1->y); zE1[sizeof(zE1)-1] = 0;
+  snprintf(zE2, sizeof(zE2), "(%g,%g)", e2->x, e2->y); zE1[sizeof(zE2)-1] = 0;
+  if( strcmp(zE1,zE2)!=0 ){
+    snprintf(zMsg, sizeof(zMsg), "%s != %s", zE1, zE2);
+    pik_error(p, pEq, zMsg);
+  }
+  return 0;
+}
+
+/* Free a complete list of objects */
+static void pik_elist_free(Pik *p, PList *pList){
+  int i;
+  if( pList==0 ) return;
+  for(i=0; i<pList->n; i++){
+    pik_elem_free(p, pList->a[i]);
+  }
+  free(pList->a);
+  free(pList);
+  return;
+}
+
+/* Free a single object, and its substructure */
+static void pik_elem_free(Pik *p, PObj *pObj){
+  if( pObj==0 ) return;
+  free(pObj->zName);
+  pik_elist_free(p, pObj->pSublist);
+  free(pObj->aPath);
+  free(pObj);
+}
+
+/* Convert a numeric literal into a number.  Return that number.
+** There is no error handling because the tokenizer has already
+** assured us that the numeric literal is valid.
+**
+** Allowed number forms:
+**
+**   (1)    Floating point literal
+**   (2)    Same as (1) but followed by a unit: "cm", "mm", "in",
+**          "px", "pt", or "pc".
+**   (3)    Hex integers: 0x000000
+**
+** This routine returns the result in inches.  If a different unit
+** is specified, the conversion happens automatically.
+*/
+PNum pik_atof(PToken *num){
+  char *endptr;
+  PNum ans;
+  if( num->n>=3 && num->z[0]=='0' && (num->z[1]=='x'||num->z[1]=='X') ){
+    return (PNum)strtol(num->z+2, 0, 16);
+  }
+  ans = strtod(num->z, &endptr);
+  if( (int)(endptr - num->z)==(int)num->n-2 ){
+    char c1 = endptr[0];
+    char c2 = endptr[1];
+    if( c1=='c' && c2=='m' ){
+      ans /= 2.54;
+    }else if( c1=='m' && c2=='m' ){
+      ans /= 25.4;
+    }else if( c1=='p' && c2=='x' ){
+      ans /= 96;
+    }else if( c1=='p' && c2=='t' ){
+      ans /= 72;
+    }else if( c1=='p' && c2=='c' ){
+      ans /= 6;
+    }
+  }
+  return ans;
+}
+
+/*
+** Compute the distance between two points
+*/
+static PNum pik_dist(PPoint *pA, PPoint *pB){
+  PNum dx, dy;
+  dx = pB->x - pA->x;
+  dy = pB->y - pA->y;
+  return hypot(dx,dy);
+}
+
+/* Return true if a bounding box is empty.
+*/
+static int pik_bbox_isempty(PBox *p){
+  return p->sw.x>p->ne.x;
+}
+
+/* Return true if point pPt is contained within the bounding box pBox
+*/
+static int pik_bbox_contains_point(PBox *pBox, PPoint *pPt){
+  if( pik_bbox_isempty(pBox) ) return 0;
+  if( pPt->x < pBox->sw.x ) return 0;
+  if( pPt->x > pBox->ne.x ) return 0;
+  if( pPt->y < pBox->sw.y ) return 0;
+  if( pPt->y > pBox->ne.y ) return 0;
+  return 1;
+}
+
+/* Initialize a bounding box to an empty container
+*/
+static void pik_bbox_init(PBox *p){
+  p->sw.x = 1.0;
+  p->sw.y = 1.0;
+  p->ne.x = 0.0;
+  p->ne.y = 0.0;
+}
+
+/* Enlarge the PBox of the first argument so that it fully
+** covers the second PBox
+*/
+static void pik_bbox_addbox(PBox *pA, PBox *pB){
+  if( pik_bbox_isempty(pA) ){
+    *pA = *pB;
+  }
+  if( pik_bbox_isempty(pB) ) return;
+  if( pA->sw.x>pB->sw.x ) pA->sw.x = pB->sw.x;
+  if( pA->sw.y>pB->sw.y ) pA->sw.y = pB->sw.y;
+  if( pA->ne.x<pB->ne.x ) pA->ne.x = pB->ne.x;
+  if( pA->ne.y<pB->ne.y ) pA->ne.y = pB->ne.y;
+}
+
+/* Enlarge the PBox of the first argument, if necessary, so that
+** it contains the point described by the 2nd and 3rd arguments.
+*/
+static void pik_bbox_add_xy(PBox *pA, PNum x, PNum y){
+  if( pik_bbox_isempty(pA) ){
+    pA->ne.x = x;
+    pA->ne.y = y;
+    pA->sw.x = x;
+    pA->sw.y = y;
+    return;
+  }
+  if( pA->sw.x>x ) pA->sw.x = x;
+  if( pA->sw.y>y ) pA->sw.y = y;
+  if( pA->ne.x<x ) pA->ne.x = x;
+  if( pA->ne.y<y ) pA->ne.y = y;
+}
+
+/* Enlarge the PBox so that it is able to contain an ellipse
+** centered at x,y and with radiuses rx and ry.
+*/
+static void pik_bbox_addellipse(PBox *pA, PNum x, PNum y, PNum rx, PNum ry){
+  if( pik_bbox_isempty(pA) ){
+    pA->ne.x = x+rx;
+    pA->ne.y = y+ry;
+    pA->sw.x = x-rx;
+    pA->sw.y = y-ry;
+    return;
+  }
+  if( pA->sw.x>x-rx ) pA->sw.x = x-rx;
+  if( pA->sw.y>y-ry ) pA->sw.y = y-ry;
+  if( pA->ne.x<x+rx ) pA->ne.x = x+rx;
+  if( pA->ne.y<y+ry ) pA->ne.y = y+ry;
+}
+
+
+
+/* Append a new object onto the end of an object list.  The
+** object list is created if it does not already exist.  Return
+** the new object list.
+*/
+static PList *pik_elist_append(Pik *p, PList *pList, PObj *pObj){
+  if( pObj==0 ) return pList;
+  if( pList==0 ){
+    pList = malloc(sizeof(*pList));
+    if( pList==0 ){
+      pik_error(p, 0, 0);
+      pik_elem_free(p, pObj);
+      return 0;
+    }
+    memset(pList, 0, sizeof(*pList));
+  }
+  if( pList->n>=pList->nAlloc ){
+    int nNew = (pList->n+5)*2;
+    PObj **pNew = realloc(pList->a, sizeof(PObj*)*nNew);
+    if( pNew==0 ){
+      pik_error(p, 0, 0);
+      pik_elem_free(p, pObj);
+      return pList;
+    }
+    pList->nAlloc = nNew;
+    pList->a = pNew;
+  }
+  pList->a[pList->n++] = pObj;
+  p->list = pList;
+  return pList;
+}
+
+/* Convert an object class name into a PClass pointer
+*/
+static const PClass *pik_find_class(PToken *pId){
+  int first = 0;
+  int last = count(aClass) - 1;
+  do{
+    int mid = (first+last)/2;
+    int c = strncmp(aClass[mid].zName, pId->z, pId->n);
+    if( c==0 ){
+      c = aClass[mid].zName[pId->n]!=0;
+      if( c==0 ) return &aClass[mid];
+    }
+    if( c<0 ){
+      first = mid + 1;
+    }else{
+      last = mid - 1;
+    }
+  }while( first<=last );
+  return 0;
+}
+
+/* Allocate and return a new PObj object.
+**
+** If pId!=0 then pId is an identifier that defines the object class.
+** If pStr!=0 then it is a STRING literal that defines a text object.
+** If pSublist!=0 then this is a [...] object. If all three parameters
+** are NULL then this is a no-op object used to define a PLACENAME.
+*/
+static PObj *pik_elem_new(Pik *p, PToken *pId, PToken *pStr,PList *pSublist){
+  PObj *pNew;
+  int miss = 0;
+
+  if( p->nErr ) return 0;
+  pNew = malloc( sizeof(*pNew) );
+  if( pNew==0 ){
+    pik_error(p,0,0);
+    pik_elist_free(p, pSublist);
+    return 0;
+  }
+  memset(pNew, 0, sizeof(*pNew));
+  p->cur = pNew;
+  p->nTPath = 1;
+  p->thenFlag = 0;
+  if( p->list==0 || p->list->n==0 ){
+    pNew->ptAt.x = pNew->ptAt.y = 0.0;
+    pNew->eWith = CP_C;
+  }else{
+    PObj *pPrior = p->list->a[p->list->n-1];
+    pNew->ptAt = pPrior->ptExit;
+    switch( p->eDir ){
+      default:         pNew->eWith = CP_W;   break;
+      case DIR_LEFT:   pNew->eWith = CP_E;   break;
+      case DIR_UP:     pNew->eWith = CP_S;   break;
+      case DIR_DOWN:   pNew->eWith = CP_N;   break;
+    }
+  }
+  p->aTPath[0] = pNew->ptAt;
+  pNew->with = pNew->ptAt;
+  pNew->outDir = pNew->inDir = p->eDir;
+  pNew->iLayer = pik_value_int(p, "layer", 5, &miss);
+  if( miss ) pNew->iLayer = 1000;
+  if( pNew->iLayer<0 ) pNew->iLayer = 0;
+  if( pSublist ){
+    pNew->type = &sublistClass;
+    pNew->pSublist = pSublist;
+    sublistClass.xInit(p,pNew);
+    return pNew;
+  }
+  if( pStr ){
+    PToken n;
+    n.z = "text";
+    n.n = 4;
+    pNew->type = pik_find_class(&n);
+    assert( pNew->type!=0 );
+    pNew->errTok = *pStr;
+    pNew->type->xInit(p, pNew);
+    pik_add_txt(p, pStr, pStr->eCode);
+    return pNew;
+  }
+  if( pId ){
+    const PClass *pClass;
+    pNew->errTok = *pId;
+    pClass = pik_find_class(pId);
+    if( pClass ){
+      pNew->type = pClass;
+      pNew->sw = pik_value(p, "thickness",9,0);
+      pNew->fill = pik_value(p, "fill",4,0);
+      pNew->color = pik_value(p, "color",5,0);
+      pClass->xInit(p, pNew);
+      return pNew;
+    }
+    pik_error(p, pId, "unknown object type");
+    pik_elem_free(p, pNew);
+    return 0;
+  }
+  pNew->type = &noopClass;
+  pNew->ptExit = pNew->ptEnter = pNew->ptAt;
+  return pNew;
+}
+
+/*
+** If the ID token in the argument is the name of a macro, return
+** the PMacro object for that macro
+*/
+static PMacro *pik_find_macro(Pik *p, PToken *pId){
+  PMacro *pMac;
+  for(pMac = p->pMacros; pMac; pMac=pMac->pNext){
+    if( pMac->macroName.n==pId->n
+     && strncmp(pMac->macroName.z,pId->z,pId->n)==0
+    ){
+      return pMac;
+    }
+  }
+  return 0;
+}
+
+/* Add a new macro
+*/
+static void pik_add_macro(
+  Pik *p,          /* Current Pikchr diagram */
+  PToken *pId,     /* The ID token that defines the macro name */
+  PToken *pCode    /* Macro body inside of {...} */
+){
+  PMacro *pNew = pik_find_macro(p, pId);
+  if( pNew==0 ){
+    pNew = malloc( sizeof(*pNew) );
+    if( pNew==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }
+    pNew->pNext = p->pMacros;
+    p->pMacros = pNew;
+    pNew->macroName = *pId;
+  }
+  pNew->macroBody.z = pCode->z+1;
+  pNew->macroBody.n = pCode->n-2;
+  pNew->inUse = 0;
+}
+
+
+/*
+** Set the output direction and exit point for an object
+*/
+static void pik_elem_set_exit(PObj *pObj, int eDir){
+  assert( ValidDir(eDir) );
+  pObj->outDir = eDir;
+  if( !pObj->type->isLine || pObj->bClose ){
+    pObj->ptExit = pObj->ptAt;
+    switch( pObj->outDir ){
+      default:         pObj->ptExit.x += pObj->w*0.5;  break;
+      case DIR_LEFT:   pObj->ptExit.x -= pObj->w*0.5;  break;
+      case DIR_UP:     pObj->ptExit.y += pObj->h*0.5;  break;
+      case DIR_DOWN:   pObj->ptExit.y -= pObj->h*0.5;  break;
+    }
+  }
+}
+
+/* Change the layout direction.
+*/
+static void pik_set_direction(Pik *p, int eDir){
+  assert( ValidDir(eDir) );
+  p->eDir = (unsigned char)eDir;
+
+  /* It seems to make sense to reach back into the last object and
+  ** change its exit point (its ".end") to correspond to the new
+  ** direction.  Things just seem to work better this way.  However,
+  ** legacy PIC does *not* do this.
+  **
+  ** The difference can be seen in a script like this:
+  **
+  **      arrow; circle; down; arrow
+  **
+  ** You can make pikchr render the above exactly like PIC
+  ** by deleting the following three lines.  But I (drh) think
+  ** it works better with those lines in place.
+  */
+  if( p->list && p->list->n ){
+    pik_elem_set_exit(p->list->a[p->list->n-1], eDir);
+  }
+}
+
+/* Move all coordinates contained within an object (and within its
+** substructure) by dx, dy
+*/
+static void pik_elem_move(PObj *pObj, PNum dx, PNum dy){
+  int i;
+  pObj->ptAt.x += dx;
+  pObj->ptAt.y += dy;
+  pObj->ptEnter.x += dx;
+  pObj->ptEnter.y += dy;
+  pObj->ptExit.x += dx;
+  pObj->ptExit.y += dy;
+  pObj->bbox.ne.x += dx;
+  pObj->bbox.ne.y += dy;
+  pObj->bbox.sw.x += dx;
+  pObj->bbox.sw.y += dy;
+  for(i=0; i<pObj->nPath; i++){
+    pObj->aPath[i].x += dx;
+    pObj->aPath[i].y += dy;
+  }
+  if( pObj->pSublist ){
+    pik_elist_move(pObj->pSublist, dx, dy);
+  }
+}
+static void pik_elist_move(PList *pList, PNum dx, PNum dy){
+  int i;
+  for(i=0; i<pList->n; i++){
+    pik_elem_move(pList->a[i], dx, dy);
+  }
+}
+
+/*
+** Check to see if it is ok to set the value of paraemeter mThis.
+** Return 0 if it is ok. If it not ok, generate an appropriate
+** error message and return non-zero.
+**
+** Flags are set in pObj so that the same object or conflicting
+** objects may not be set again.
+**
+** To be ok, bit mThis must be clear and no more than one of
+** the bits identified by mBlockers may be set.
+*/
+static int pik_param_ok(
+  Pik *p,             /* For storing the error message (if any) */
+  PObj *pObj,       /* The object under construction */
+  PToken *pId,        /* Make the error point to this token */
+  int mThis           /* Value we are trying to set */
+){
+  if( pObj->mProp & mThis ){
+    pik_error(p, pId, "value is already set");
+    return 1;
+  }
+  if( pObj->mCalc & mThis ){
+    pik_error(p, pId, "value already fixed by prior constraints");
+    return 1;
+  }
+  pObj->mProp |= mThis;
+  return 0;
+}
+
+
+/*
+** Set a numeric property like "width 7" or "radius 200%".
+**
+** The rAbs term is an absolute value to add in.  rRel is
+** a relative value by which to change the current value.
+*/
+void pik_set_numprop(Pik *p, PToken *pId, PRel *pVal){
+  PObj *pObj = p->cur;
+  switch( pId->eType ){
+    case T_HEIGHT:
+      if( pik_param_ok(p, pObj, pId, A_HEIGHT) ) return;
+      pObj->h = pObj->h*pVal->rRel + pVal->rAbs;
+      break;
+    case T_WIDTH:
+      if( pik_param_ok(p, pObj, pId, A_WIDTH) ) return;
+      pObj->w = pObj->w*pVal->rRel + pVal->rAbs;
+      break;
+    case T_RADIUS:
+      if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return;
+      pObj->rad = pObj->rad*pVal->rRel + pVal->rAbs;
+      break;
+    case T_DIAMETER:
+      if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return;
+      pObj->rad = pObj->rad*pVal->rRel + 0.5*pVal->rAbs; /* diam it 2x rad */
+      break;
+    case T_THICKNESS:
+      if( pik_param_ok(p, pObj, pId, A_THICKNESS) ) return;
+      pObj->sw = pObj->sw*pVal->rRel + pVal->rAbs;
+      break;
+  }
+  if( pObj->type->xNumProp ){
+    pObj->type->xNumProp(p, pObj, pId);
+  }
+  return;
+}
+
+/*
+** Set a color property.  The argument is an RGB value.
+*/
+void pik_set_clrprop(Pik *p, PToken *pId, PNum rClr){
+  PObj *pObj = p->cur;
+  switch( pId->eType ){
+    case T_FILL:
+      if( pik_param_ok(p, pObj, pId, A_FILL) ) return;
+      pObj->fill = rClr;
+      break;
+    case T_COLOR:
+      if( pik_param_ok(p, pObj, pId, A_COLOR) ) return;
+      pObj->color = rClr;
+      break;
+  }
+  if( pObj->type->xNumProp ){
+    pObj->type->xNumProp(p, pObj, pId);
+  }
+  return;
+}
+
+/*
+** Set a "dashed" property like "dash 0.05"
+**
+** Use the value supplied by pVal if available.  If pVal==0, use
+** a default.
+*/
+void pik_set_dashed(Pik *p, PToken *pId, PNum *pVal){
+  PObj *pObj = p->cur;
+  PNum v;
+  switch( pId->eType ){
+    case T_DOTTED:  {
+      v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal;
+      pObj->dotted = v;
+      pObj->dashed = 0.0;
+      break;
+    }
+    case T_DASHED:  {
+      v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal;
+      pObj->dashed = v;
+      pObj->dotted = 0.0;
+      break;
+    }
+  }
+}
+
+/*
+** If the current path information came from a "same" or "same as"
+** reset it.
+*/
+static void pik_reset_samepath(Pik *p){
+  if( p->samePath ){
+    p->samePath = 0;
+    p->nTPath = 1;
+  }
+}
+
+
+/* Add a new term to the path for a line-oriented object by transferring
+** the information in the ptTo field over onto the path and into ptFrom
+** resetting the ptTo.
+*/
+static void pik_then(Pik *p, PToken *pToken, PObj *pObj){
+  int n;
+  if( !pObj->type->isLine ){
+    pik_error(p, pToken, "use with line-oriented objects only");
+    return;
+  }
+  n = p->nTPath - 1;
+  if( n<1 && (pObj->mProp & A_FROM)==0 ){
+    pik_error(p, pToken, "no prior path points");
+    return;
+  }
+  p->thenFlag = 1;
+}
+
+/* Advance to the next entry in p->aTPath.  Return its index.
+*/
+static int pik_next_rpath(Pik *p, PToken *pErr){
+  int n = p->nTPath - 1;
+  if( n+1>=(int)count(p->aTPath) ){
+    pik_error(0, pErr, "too many path elements");
+    return n;
+  }
+  n++;
+  p->nTPath++;
+  p->aTPath[n] = p->aTPath[n-1];
+  p->mTPath = 0;
+  return n;
+}
+
+/* Add a direction term to an object.  "up 0.5", or "left 3", or "down"
+** or "down 50%".
+*/
+static void pik_add_direction(Pik *p, PToken *pDir, PRel *pVal){
+  PObj *pObj = p->cur;
+  int n;
+  int dir;
+  if( !pObj->type->isLine ){
+    if( pDir ){
+      pik_error(p, pDir, "use with line-oriented objects only");
+    }else{
+      PToken x = pik_next_semantic_token(&pObj->errTok);
+      pik_error(p, &x, "syntax error");
+    }
+    return;
+  }
+  pik_reset_samepath(p);
+  n = p->nTPath - 1;
+  if( p->thenFlag || p->mTPath==3 || n==0 ){
+    n = pik_next_rpath(p, pDir);
+    p->thenFlag = 0;
+  }
+  dir = pDir ? pDir->eCode : p->eDir;
+  switch( dir ){
+    case DIR_UP:
+       if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].y += pVal->rAbs + pObj->h*pVal->rRel;
+       p->mTPath |= 2;
+       break;
+    case DIR_DOWN:
+       if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].y -= pVal->rAbs + pObj->h*pVal->rRel;
+       p->mTPath |= 2;
+       break;
+    case DIR_RIGHT:
+       if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].x += pVal->rAbs + pObj->w*pVal->rRel;
+       p->mTPath |= 1;
+       break;
+    case DIR_LEFT:
+       if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].x -= pVal->rAbs + pObj->w*pVal->rRel;
+       p->mTPath |= 1;
+       break;
+  }
+  pObj->outDir = dir;
+}
+
+/* Process a movement attribute of one of these forms:
+**
+**         pDist   pHdgKW  rHdg    pEdgept
+**     GO distance HEADING angle
+**     GO distance               compasspoint
+*/
+static void pik_move_hdg(
+  Pik *p,              /* The Pikchr context */
+  PRel *pDist,         /* Distance to move */
+  PToken *pHeading,    /* "heading" keyword if present */
+  PNum rHdg,           /* Angle argument to "heading" keyword */
+  PToken *pEdgept,     /* EDGEPT keyword "ne", "sw", etc... */
+  PToken *pErr         /* Token to use for error messages */
+){
+  PObj *pObj = p->cur;
+  int n;
+  PNum rDist = pDist->rAbs + pik_value(p,"linewid",7,0)*pDist->rRel;
+  if( !pObj->type->isLine ){
+    pik_error(p, pErr, "use with line-oriented objects only");
+    return;
+  }
+  pik_reset_samepath(p);
+  do{
+    n = pik_next_rpath(p, pErr);
+  }while( n<1 );
+  if( pHeading ){
+    rHdg = fmod(rHdg,360.0);
+  }else if( pEdgept->eEdge==CP_C ){
+    pik_error(p, pEdgept, "syntax error");
+    return;
+  }else{
+    rHdg = pik_hdg_angle[pEdgept->eEdge];
+  }
+  if( rHdg<=45.0 ){
+    pObj->outDir = DIR_UP;
+  }else if( rHdg<=135.0 ){
+    pObj->outDir = DIR_RIGHT;
+  }else if( rHdg<=225.0 ){
+    pObj->outDir = DIR_DOWN;
+  }else if( rHdg<=315.0 ){
+    pObj->outDir = DIR_LEFT;
+  }else{
+    pObj->outDir = DIR_UP;
+  }
+  rHdg *= 0.017453292519943295769;  /* degrees to radians */
+  p->aTPath[n].x += rDist*sin(rHdg);
+  p->aTPath[n].y += rDist*cos(rHdg);
+  p->mTPath = 2;
+}
+
+
+/* Process a movement attribute of the form "right until even with ..."
+**
+** pDir is the first keyword, "right" or "left" or "up" or "down".
+** The movement is in that direction until its closest approach to
+** the point specified by pPoint.
+*/
+static void pik_evenwith(Pik *p, PToken *pDir, PPoint *pPlace){
+  PObj *pObj = p->cur;
+  int n;
+  if( !pObj->type->isLine ){
+    pik_error(p, pDir, "use with line-oriented objects only");
+    return;
+  }
+  pik_reset_samepath(p);
+  n = p->nTPath - 1;
+  if( p->thenFlag || p->mTPath==3 || n==0 ){
+    n = pik_next_rpath(p, pDir);
+    p->thenFlag = 0;
+  }
+  switch( pDir->eCode ){
+    case DIR_DOWN:
+    case DIR_UP:
+       if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].y = pPlace->y;
+       p->mTPath |= 2;
+       break;
+    case DIR_RIGHT:
+    case DIR_LEFT:
+       if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].x = pPlace->x;
+       p->mTPath |= 1;
+       break;
+  }
+  pObj->outDir = pDir->eCode;
+}
+
+/* If the last referenced object is centered at point pPt then return
+** a pointer to that object.  If there is no prior object reference,
+** or if the points are not the same, return NULL.
+**
+** This is a side-channel hack used to find the objects at which a
+** line begins and ends.  For example, in
+**
+**        arrow from OBJ1 to OBJ2 chop
+**
+** The arrow object is normally just handed the coordinates of the
+** centers for OBJ1 and OBJ2.  But we also want to know the specific
+** object named in case there are multiple objects centered at the
+** same point.
+**
+** See forum post 1d46e3a0bc
+*/
+static PObj *pik_last_ref_object(Pik *p, PPoint *pPt){
+  PObj *pRes = 0;
+  if( p->lastRef==0 ) return 0;
+  if( p->lastRef->ptAt.x==pPt->x
+   && p->lastRef->ptAt.y==pPt->y
+  ){
+    pRes = p->lastRef;
+  }
+  p->lastRef = 0;
+  return pRes;
+}
+
+/* Set the "from" of an object
+*/
+static void pik_set_from(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){
+  if( !pObj->type->isLine ){
+    pik_error(p, pTk, "use \"at\" to position this object");
+    return;
+  }
+  if( pObj->mProp & A_FROM ){
+    pik_error(p, pTk, "line start location already fixed");
+    return;
+  }
+  if( pObj->bClose ){
+    pik_error(p, pTk, "polygon is closed");
+    return;
+  }
+  if( p->nTPath>1 ){
+    PNum dx = pPt->x - p->aTPath[0].x;
+    PNum dy = pPt->y - p->aTPath[0].y;
+    int i;
+    for(i=1; i<p->nTPath; i++){
+      p->aTPath[i].x += dx;
+      p->aTPath[i].y += dy;
+    }
+  }
+  p->aTPath[0] = *pPt;
+  p->mTPath = 3;
+  pObj->mProp |= A_FROM;
+  pObj->pFrom = pik_last_ref_object(p, pPt);
+}
+
+/* Set the "to" of an object
+*/
+static void pik_add_to(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){
+  int n = p->nTPath-1;
+  if( !pObj->type->isLine ){
+    pik_error(p, pTk, "use \"at\" to position this object");
+    return;
+  }
+  if( pObj->bClose ){
+    pik_error(p, pTk, "polygon is closed");
+    return;
+  }
+  pik_reset_samepath(p);
+  if( n==0 || p->mTPath==3 || p->thenFlag ){
+    n = pik_next_rpath(p, pTk);
+  }
+  p->aTPath[n] = *pPt;
+  p->mTPath = 3;
+  pObj->pTo = pik_last_ref_object(p, pPt);
+}
+
+static void pik_close_path(Pik *p, PToken *pErr){
+  PObj *pObj = p->cur;
+  if( p->nTPath<3 ){
+    pik_error(p, pErr,
+      "need at least 3 vertexes in order to close the polygon");
+    return;
+  }
+  if( pObj->bClose ){
+    pik_error(p, pErr, "polygon already closed");
+    return;
+  }
+  pObj->bClose = 1;
+}
+
+/* Lower the layer of the current object so that it is behind the
+** given object.
+*/
+static void pik_behind(Pik *p, PObj *pOther){
+  PObj *pObj = p->cur;
+  if( p->nErr==0 && pObj->iLayer>=pOther->iLayer ){
+    pObj->iLayer = pOther->iLayer - 1;
+  }
+}
+
+
+/* Set the "at" of an object
+*/
+static void pik_set_at(Pik *p, PToken *pEdge, PPoint *pAt, PToken *pErrTok){
+  PObj *pObj;
+  static unsigned char eDirToCp[] = { CP_E, CP_S, CP_W, CP_N };
+  if( p->nErr ) return;
+  pObj = p->cur;
+
+  if( pObj->type->isLine ){
+    pik_error(p, pErrTok, "use \"from\" and \"to\" to position this object");
+    return;
+  }
+  if( pObj->mProp & A_AT ){
+    pik_error(p, pErrTok, "location fixed by prior \"at\"");
+    return;
+  }
+  pObj->mProp |= A_AT;
+  pObj->eWith = pEdge ? pEdge->eEdge : CP_C;
+  if( pObj->eWith>=CP_END ){
+    int dir = pObj->eWith==CP_END ? pObj->outDir : pObj->inDir;
+    pObj->eWith = eDirToCp[dir];
+  }
+  pObj->with = *pAt;
+}
+
+/*
+** Try to add a text attribute to an object
+*/
+static void pik_add_txt(Pik *p, PToken *pTxt, int iPos){
+  PObj *pObj = p->cur;
+  PToken *pT;
+  if( pObj->nTxt >= count(pObj->aTxt) ){
+    pik_error(p, pTxt, "too many text terms");
+    return;
+  }
+  pT = &pObj->aTxt[pObj->nTxt++];
+  *pT = *pTxt;
+  pT->eCode = (short)iPos;
+}
+
+/* Merge "text-position" flags
+*/
+static int pik_text_position(int iPrev, PToken *pFlag){
+  int iRes = iPrev;
+  switch( pFlag->eType ){
+    case T_LJUST:    iRes = (iRes&~TP_JMASK) | TP_LJUST;  break;
+    case T_RJUST:    iRes = (iRes&~TP_JMASK) | TP_RJUST;  break;
+    case T_ABOVE:    iRes = (iRes&~TP_VMASK) | TP_ABOVE;  break;
+    case T_CENTER:   iRes = (iRes&~TP_VMASK) | TP_CENTER; break;
+    case T_BELOW:    iRes = (iRes&~TP_VMASK) | TP_BELOW;  break;
+    case T_ITALIC:   iRes |= TP_ITALIC;                   break; 
+    case T_BOLD:     iRes |= TP_BOLD;                     break; 
+    case T_ALIGNED:  iRes |= TP_ALIGN;                    break;
+    case T_BIG:      if( iRes & TP_BIG ) iRes |= TP_XTRA;
+                     else iRes = (iRes &~TP_SZMASK)|TP_BIG;   break;
+    case T_SMALL:    if( iRes & TP_SMALL ) iRes |= TP_XTRA;
+                     else iRes = (iRes &~TP_SZMASK)|TP_SMALL; break;
+  }
+  return iRes;
+}
+
+/*
+** Table of scale-factor estimates for variable-width characters.
+** Actual character widths vary by font.  These numbers are only
+** guesses.  And this table only provides data for ASCII.
+**
+** 100 means normal width.
+*/
+static const unsigned char awChar[] = {
+  /* Skip initial 32 control characters */
+  /* ' ' */  45,
+  /* '!' */  55,
+  /* '"' */  62,
+  /* '#' */  115,
+  /* '$' */  90,
+  /* '%' */  132,
+  /* '&' */  125,
+  /* '\''*/  40,
+
+  /* '(' */  55,
+  /* ')' */  55,
+  /* '*' */  71,
+  /* '+' */  115,
+  /* ',' */  45,
+  /* '-' */  48,
+  /* '.' */  45,
+  /* '/' */  50,
+
+  /* '0' */  91,
+  /* '1' */  91,
+  /* '2' */  91,
+  /* '3' */  91,
+  /* '4' */  91,
+  /* '5' */  91,
+  /* '6' */  91,
+  /* '7' */  91,
+
+  /* '8' */  91,
+  /* '9' */  91,
+  /* ':' */  50,
+  /* ';' */  50,
+  /* '<' */ 120,
+  /* '=' */ 120,
+  /* '>' */ 120,
+  /* '?' */  78,
+
+  /* '@' */ 142,
+  /* 'A' */ 102,
+  /* 'B' */ 105,
+  /* 'C' */ 110,
+  /* 'D' */ 115,
+  /* 'E' */ 105,
+  /* 'F' */  98,
+  /* 'G' */ 105,
+
+  /* 'H' */ 125,
+  /* 'I' */  58,
+  /* 'J' */  58,
+  /* 'K' */ 107,
+  /* 'L' */  95,
+  /* 'M' */ 145,
+  /* 'N' */ 125,
+  /* 'O' */ 115,
+
+  /* 'P' */  95,
+  /* 'Q' */ 115,
+  /* 'R' */ 107,
+  /* 'S' */  95,
+  /* 'T' */  97,
+  /* 'U' */ 118,
+  /* 'V' */ 102,
+  /* 'W' */ 150,
+
+  /* 'X' */ 100,
+  /* 'Y' */  93,
+  /* 'Z' */ 100,
+  /* '[' */  58,
+  /* '\\'*/  50,
+  /* ']' */  58,
+  /* '^' */ 119,
+  /* '_' */  72,
+
+  /* '`' */  72,
+  /* 'a' */  86,
+  /* 'b' */  92,
+  /* 'c' */  80,
+  /* 'd' */  92,
+  /* 'e' */  85,
+  /* 'f' */  52,
+  /* 'g' */  92,
+
+  /* 'h' */  92,
+  /* 'i' */  47,
+  /* 'j' */  47,
+  /* 'k' */  88,
+  /* 'l' */  48,
+  /* 'm' */ 135,
+  /* 'n' */  92,
+  /* 'o' */  86,
+
+  /* 'p' */  92,
+  /* 'q' */  92,
+  /* 'r' */  69,
+  /* 's' */  75,
+  /* 't' */  58,
+  /* 'u' */  92,
+  /* 'v' */  80,
+  /* 'w' */ 121,
+
+  /* 'x' */  81,
+  /* 'y' */  80,
+  /* 'z' */  76,
+  /* '{' */  91,
+  /* '|'*/   49,
+  /* '}' */  91,
+  /* '~' */ 118,
+};
+
+/* Return an estimate of the width of the displayed characters
+** in a character string.  The returned value is 100 times the
+** average character width.
+**
+** Omit "\" used to escape characters.  And count entities like
+** "&lt;" as a single character.  Multi-byte UTF8 characters count
+** as a single character.
+**
+** Attempt to scale the answer by the actual characters seen.  Wide
+** characters count more than narrow characters.  But the widths are
+** only guesses.
+*/
+static int pik_text_length(const PToken *pToken){
+  int n = pToken->n;
+  const char *z = pToken->z;
+  int cnt, j;
+  for(j=1, cnt=0; j<n-1; j++){
+    char c = z[j];
+    if( c=='\\' && z[j+1]!='&' ){
+      c = z[++j];
+    }else if( c=='&' ){
+      int k;
+      for(k=j+1; k<j+7 && z[k]!=0 && z[k]!=';'; k++){}
+      if( z[k]==';' ) j = k;
+      cnt += 150;
+      continue;
+    }
+    if( (c & 0xc0)==0xc0 ){
+      while( j+1<n-1 && (z[j+1]&0xc0)==0x80 ){ j++; }
+      cnt += 100;
+      continue;
+    }
+    if( c>=0x20 && c<=0x7e ){
+      cnt += awChar[c-0x20];
+    }else{
+      cnt += 100;
+    }
+  }
+  return cnt;
+}
+
+/* Adjust the width, height, and/or radius of the object so that
+** it fits around the text that has been added so far.
+**
+**    (1) Only text specified prior to this attribute is considered.
+**    (2) The text size is estimated based on the charht and charwid
+**        variable settings.
+**    (3) The fitted attributes can be changed again after this
+**        attribute, for example using "width 110%" if this auto-fit
+**        underestimates the text size.
+**    (4) Previously set attributes will not be altered.  In other words,
+**        "width 1in fit" might cause the height to change, but the
+**        width is now set.
+**    (5) This only works for attributes that have an xFit method.
+**
+** The eWhich parameter is:
+**
+**    1:   Fit horizontally only
+**    2:   Fit vertically only
+**    3:   Fit both ways
+*/
+static void pik_size_to_fit(Pik *p, PToken *pFit, int eWhich){
+  PObj *pObj;
+  PNum w, h;
+  PBox bbox;
+  if( p->nErr ) return;
+  pObj = p->cur;
+
+  if( pObj->nTxt==0 ){
+    pik_error(0, pFit, "no text to fit to");
+    return;
+  }
+  if( pObj->type->xFit==0 ) return;
+  pik_bbox_init(&bbox);
+  pik_compute_layout_settings(p);
+  pik_append_txt(p, pObj, &bbox);
+  w = (eWhich & 1)!=0 ? (bbox.ne.x - bbox.sw.x) + p->charWidth : 0;
+  if( eWhich & 2 ){
+    PNum h1, h2;
+    h1 = (bbox.ne.y - pObj->ptAt.y);
+    h2 = (pObj->ptAt.y - bbox.sw.y);
+    h = 2.0*( h1<h2 ? h2 : h1 ) + 0.5*p->charHeight;
+  }else{
+    h = 0;
+  }
+  pObj->type->xFit(p, pObj, w, h);
+  pObj->mProp |= A_FIT;
+}
+
+/* Set a local variable name to "val".
+**
+** The name might be a built-in variable or a color name.  In either case,
+** a new application-defined variable is set.  Since app-defined variables
+** are searched first, this will override any built-in variables.
+*/
+static void pik_set_var(Pik *p, PToken *pId, PNum val, PToken *pOp){
+  PVar *pVar = p->pVar;
+  while( pVar ){
+    if( pik_token_eq(pId,pVar->zName)==0 ) break;
+    pVar = pVar->pNext;
+  }
+  if( pVar==0 ){
+    char *z;
+    pVar = malloc( pId->n+1 + sizeof(*pVar) );
+    if( pVar==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }
+    pVar->zName = z = (char*)&pVar[1];
+    memcpy(z, pId->z, pId->n);
+    z[pId->n] = 0;
+    pVar->pNext = p->pVar;
+    pVar->val = pik_value(p, pId->z, pId->n, 0);
+    p->pVar = pVar;
+  }
+  switch( pOp->eCode ){
+    case T_PLUS:  pVar->val += val; break;
+    case T_STAR:  pVar->val *= val; break;
+    case T_MINUS: pVar->val -= val; break;
+    case T_SLASH:
+      if( val==0.0 ){
+        pik_error(p, pOp, "division by zero");
+      }else{
+        pVar->val /= val;
+      }
+      break;
+    default:      pVar->val = val; break;
+  }
+  p->bLayoutVars = 0;  /* Clear the layout setting cache */
+}
+
+/*
+** Round a PNum into the nearest integer
+*/
+static int pik_round(PNum v){
+  if( isnan(v) ) return 0;
+  if( v < -2147483647 ) return (-2147483647-1);
+  if( v >= 2147483647 ) return 2147483647;
+  return (int)v;
+}
+
+/*
+** Search for the variable named z[0..n-1] in:
+**
+**   * Application defined variables
+**   * Built-in variables
+**
+** Return the value of the variable if found.  If not found
+** return 0.0.  Also if pMiss is not NULL, then set it to 1
+** if not found.
+**
+** This routine is a subroutine to pik_get_var().  But it is also
+** used by object implementations to look up (possibly overwritten)
+** values for built-in variables like "boxwid".
+*/
+static PNum pik_value(Pik *p, const char *z, int n, int *pMiss){
+  PVar *pVar;
+  int first, last, mid, c;
+  for(pVar=p->pVar; pVar; pVar=pVar->pNext){
+    if( strncmp(pVar->zName,z,n)==0 && pVar->zName[n]==0 ){
+      return pVar->val;
+    }
+  }
+  first = 0;
+  last = count(aBuiltin)-1;
+  while( first<=last ){
+    mid = (first+last)/2;
+    c = strncmp(z,aBuiltin[mid].zName,n);
+    if( c==0 && aBuiltin[mid].zName[n] ) c = 1;
+    if( c==0 ) return aBuiltin[mid].val;
+    if( c>0 ){
+      first = mid+1;
+    }else{
+      last = mid-1;
+    }
+  }
+  if( pMiss ) *pMiss = 1;
+  return 0.0;
+}
+static int pik_value_int(Pik *p, const char *z, int n, int *pMiss){
+  return pik_round(pik_value(p,z,n,pMiss));
+}
+
+/*
+** Look up a color-name.  Unlike other names in this program, the
+** color-names are not case sensitive.  So "DarkBlue" and "darkblue"
+** and "DARKBLUE" all find the same value (139).
+**
+** If not found, return -99.0.  Also post an error if p!=NULL.
+**
+** Special color names "None" and "Off" return -1.0 without causing
+** an error.
+*/
+static PNum pik_lookup_color(Pik *p, PToken *pId){
+  int first, last, mid, c = 0;
+  first = 0;
+  last = count(aColor)-1;
+  while( first<=last ){
+    const char *zClr;
+    int c1, c2;
+    unsigned int i;
+    mid = (first+last)/2;
+    zClr = aColor[mid].zName;
+    for(i=0; i<pId->n; i++){
+      c1 = zClr[i]&0x7f;
+      if( isupper(c1) ) c1 = tolower(c1);
+      c2 = pId->z[i]&0x7f;
+      if( isupper(c2) ) c2 = tolower(c2);
+      c = c2 - c1;
+      if( c ) break;
+    }
+    if( c==0 && aColor[mid].zName[pId->n] ) c = -1;
+    if( c==0 ) return (double)aColor[mid].val;
+    if( c>0 ){
+      first = mid+1;
+    }else{
+      last = mid-1;
+    }
+  }
+  if( p ) pik_error(p, pId, "not a known color name");
+  return -99.0;
+}
+
+/* Get the value of a variable.
+**
+** Search in order:
+**
+**    *  Application defined variables
+**    *  Built-in variables
+**    *  Color names
+**
+** If no such variable is found, throw an error.
+*/
+static PNum pik_get_var(Pik *p, PToken *pId){
+  int miss = 0;
+  PNum v = pik_value(p, pId->z, pId->n, &miss);
+  if( miss==0 ) return v;
+  v = pik_lookup_color(0, pId);
+  if( v>-90.0 ) return v;
+  pik_error(p,pId,"no such variable");
+  return 0.0;
+}
+
+/* Convert a T_NTH token (ex: "2nd", "5th"} into a numeric value and
+** return that value.  Throw an error if the value is too big.
+*/
+static short int pik_nth_value(Pik *p, PToken *pNth){
+  int i = atoi(pNth->z);
+  if( i>1000 ){
+    pik_error(p, pNth, "value too big - max '1000th'");
+    i = 1;
+  }
+  if( i==0 && pik_token_eq(pNth,"first")==0 ) i = 1;
+  return (short int)i;
+}
+
+/* Search for the NTH object.
+**
+** If pBasis is not NULL then it should be a [] object.  Use the
+** sublist of that [] object for the search.  If pBasis is not a []
+** object, then throw an error.
+**
+** The pNth token describes the N-th search.  The pNth->eCode value
+** is one more than the number of items to skip.  It is negative
+** to search backwards.  If pNth->eType==T_ID, then it is the name
+** of a class to search for.  If pNth->eType==T_LB, then
+** search for a [] object.  If pNth->eType==T_LAST, then search for
+** any type.
+**
+** Raise an error if the item is not found.
+*/
+static PObj *pik_find_nth(Pik *p, PObj *pBasis, PToken *pNth){
+  PList *pList;
+  int i, n;
+  const PClass *pClass;
+  if( pBasis==0 ){
+    pList = p->list;
+  }else{
+    pList = pBasis->pSublist;
+  }
+  if( pList==0 ){
+    pik_error(p, pNth, "no such object");
+    return 0;
+  }
+  if( pNth->eType==T_LAST ){
+    pClass = 0;
+  }else if( pNth->eType==T_LB ){
+    pClass = &sublistClass;
+  }else{
+    pClass = pik_find_class(pNth);
+    if( pClass==0 ){
+      pik_error(0, pNth, "no such object type");
+      return 0;
+    }
+  }
+  n = pNth->eCode;
+  if( n<0 ){
+    for(i=pList->n-1; i>=0; i--){
+      PObj *pObj = pList->a[i];
+      if( pClass && pObj->type!=pClass ) continue;
+      n++;
+      if( n==0 ){ return pObj; }
+    }
+  }else{
+    for(i=0; i<pList->n; i++){
+      PObj *pObj = pList->a[i];
+      if( pClass && pObj->type!=pClass ) continue;
+      n--;
+      if( n==0 ){ return pObj; }
+    }
+  }
+  pik_error(p, pNth, "no such object");
+  return 0;
+}
+
+/* Search for an object by name.
+**
+** Search in pBasis->pSublist if pBasis is not NULL.  If pBasis is NULL
+** then search in p->list.
+*/
+static PObj *pik_find_byname(Pik *p, PObj *pBasis, PToken *pName){
+  PList *pList;
+  int i, j;
+  if( pBasis==0 ){
+    pList = p->list;
+  }else{
+    pList = pBasis->pSublist;
+  }
+  if( pList==0 ){
+    pik_error(p, pName, "no such object");
+    return 0;
+  }
+  /* First look explicitly tagged objects */
+  for(i=pList->n-1; i>=0; i--){
+    PObj *pObj = pList->a[i];
+    if( pObj->zName && pik_token_eq(pName,pObj->zName)==0 ){
+      p->lastRef = pObj;
+      return pObj;
+    }
+  }
+  /* If not found, do a second pass looking for any object containing
+  ** text which exactly matches pName */
+  for(i=pList->n-1; i>=0; i--){
+    PObj *pObj = pList->a[i];
+    for(j=0; j<pObj->nTxt; j++){
+      if( pObj->aTxt[j].n==pName->n+2
+       && memcmp(pObj->aTxt[j].z+1,pName->z,pName->n)==0 ){
+        p->lastRef = pObj;
+        return pObj;
+      }
+    }
+  }
+  pik_error(p, pName, "no such object");
+  return 0;
+}
+
+/* Change most of the settings for the current object to be the
+** same as the pOther object, or the most recent object of the same
+** type if pOther is NULL.
+*/
+static void pik_same(Pik *p, PObj *pOther, PToken *pErrTok){
+  PObj *pObj = p->cur;
+  if( p->nErr ) return;
+  if( pOther==0 ){
+    int i;
+    for(i=(p->list ? p->list->n : 0)-1; i>=0; i--){
+      pOther = p->list->a[i];
+      if( pOther->type==pObj->type ) break;
+    }
+    if( i<0 ){
+      pik_error(p, pErrTok, "no prior objects of the same type");
+      return;
+    }
+  }
+  if( pOther->nPath && pObj->type->isLine ){
+    PNum dx, dy;
+    int i;
+    dx = p->aTPath[0].x - pOther->aPath[0].x;
+    dy = p->aTPath[0].y - pOther->aPath[0].y;
+    for(i=1; i<pOther->nPath; i++){
+      p->aTPath[i].x = pOther->aPath[i].x + dx;
+      p->aTPath[i].y = pOther->aPath[i].y + dy;
+    }
+    p->nTPath = pOther->nPath;
+    p->mTPath = 3;
+    p->samePath = 1;
+  }
+  if( !pObj->type->isLine ){
+    pObj->w = pOther->w;
+    pObj->h = pOther->h;
+  }
+  pObj->rad = pOther->rad;
+  pObj->sw = pOther->sw;
+  pObj->dashed = pOther->dashed;
+  pObj->dotted = pOther->dotted;
+  pObj->fill = pOther->fill;
+  pObj->color = pOther->color;
+  pObj->cw = pOther->cw;
+  pObj->larrow = pOther->larrow;
+  pObj->rarrow = pOther->rarrow;
+  pObj->bClose = pOther->bClose;
+  pObj->bChop = pOther->bChop;
+  pObj->inDir = pOther->inDir;
+  pObj->outDir = pOther->outDir;
+  pObj->iLayer = pOther->iLayer;
+}
+
+
+/* Return a "Place" associated with object pObj.  If pEdge is NULL
+** return the center of the object.  Otherwise, return the corner
+** described by pEdge.
+*/
+static PPoint pik_place_of_elem(Pik *p, PObj *pObj, PToken *pEdge){
+  PPoint pt = cZeroPoint;
+  const PClass *pClass;
+  if( pObj==0 ) return pt;
+  if( pEdge==0 ){
+    return pObj->ptAt;
+  }
+  pClass = pObj->type;
+  if( pEdge->eType==T_EDGEPT || (pEdge->eEdge>0 && pEdge->eEdge<CP_END) ){
+    pt = pClass->xOffset(p, pObj, pEdge->eEdge);
+    pt.x += pObj->ptAt.x;
+    pt.y += pObj->ptAt.y;
+    return pt;
+  }
+  if( pEdge->eType==T_START ){
+    return pObj->ptEnter;
+  }else{
+    return pObj->ptExit;
+  }
+}
+
+/* Do a linear interpolation of two positions.
+*/
+static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2){
+  PPoint out;
+  out.x = p2.x*x + p1.x*(1.0 - x);
+  out.y = p2.y*x + p1.y*(1.0 - x);
+  return out;
+}
+
+/* Compute the position that is dist away from pt at an heading angle of r
+**
+** The angle is a compass heading in degrees.  North is 0 (or 360).
+** East is 90.  South is 180.  West is 270.  And so forth.
+*/
+static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt){
+  r *= 0.017453292519943295769;  /* degrees to radians */
+  pt.x += dist*sin(r);
+  pt.y += dist*cos(r);
+  return pt;
+}
+
+/* Compute the position that is dist away at a compass point
+*/
+static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt){
+  return pik_position_at_angle(dist, pik_hdg_angle[pD->eEdge], pt);
+}
+
+/* Return the coordinates for the n-th vertex of a line.
+*/
+static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj){
+  static const PPoint zero = {0, 0};
+  int n;
+  if( p->nErr || pObj==0 ) return p->aTPath[0];
+  if( !pObj->type->isLine ){
+    pik_error(p, pErr, "object is not a line");
+    return zero;
+  }
+  n = atoi(pNth->z);
+  if( n<1 || n>pObj->nPath ){
+    pik_error(p, pNth, "no such vertex");
+    return zero;
+  }
+  return pObj->aPath[n-1];
+}
+
+/* Return the value of a property of an object.
+*/
+static PNum pik_property_of(PObj *pObj, PToken *pProp){
+  PNum v = 0.0;
+  if( pObj ){
+    switch( pProp->eType ){
+      case T_HEIGHT:    v = pObj->h;            break;
+      case T_WIDTH:     v = pObj->w;            break;
+      case T_RADIUS:    v = pObj->rad;          break;
+      case T_DIAMETER:  v = pObj->rad*2.0;      break;
+      case T_THICKNESS: v = pObj->sw;           break;
+      case T_DASHED:    v = pObj->dashed;       break;
+      case T_DOTTED:    v = pObj->dotted;       break;
+      case T_FILL:      v = pObj->fill;         break;
+      case T_COLOR:     v = pObj->color;        break;
+      case T_X:         v = pObj->ptAt.x;       break;
+      case T_Y:         v = pObj->ptAt.y;       break;
+      case T_TOP:       v = pObj->bbox.ne.y;    break;
+      case T_BOTTOM:    v = pObj->bbox.sw.y;    break;
+      case T_LEFT:      v = pObj->bbox.sw.x;    break;
+      case T_RIGHT:     v = pObj->bbox.ne.x;    break;
+    }
+  }
+  return v;
+}
+
+/* Compute one of the built-in functions
+*/
+static PNum pik_func(Pik *p, PToken *pFunc, PNum x, PNum y){
+  PNum v = 0.0;
+  switch( pFunc->eCode ){
+    case FN_ABS:  v = x<0.0 ? -x : x;  break;
+    case FN_COS:  v = cos(x);          break;
+    case FN_INT:  v = rint(x);         break;
+    case FN_SIN:  v = sin(x);          break;
+    case FN_SQRT:
+      if( x<0.0 ){
+        pik_error(p, pFunc, "sqrt of negative value");
+        v = 0.0;
+      }else{
+        v = sqrt(x);
+      }
+      break;
+    case FN_MAX:  v = x>y ? x : y;   break;
+    case FN_MIN:  v = x<y ? x : y;   break;
+    default:      v = 0.0;
+  }
+  return v;
+}
+
+/* Attach a name to an object
+*/
+static void pik_elem_setname(Pik *p, PObj *pObj, PToken *pName){
+  if( pObj==0 ) return;
+  if( pName==0 ) return;
+  free(pObj->zName);
+  pObj->zName = malloc(pName->n+1);
+  if( pObj->zName==0 ){
+    pik_error(p,0,0);
+  }else{
+    memcpy(pObj->zName,pName->z,pName->n);
+    pObj->zName[pName->n] = 0;
+  }
+  return;
+}
+
+/*
+** Search for object located at *pCenter that has an xChop method and
+** that does not enclose point pOther.
+**
+** Return a pointer to the object, or NULL if not found.
+*/
+static PObj *pik_find_chopper(PList *pList, PPoint *pCenter, PPoint *pOther){
+  int i;
+  if( pList==0 ) return 0;
+  for(i=pList->n-1; i>=0; i--){
+    PObj *pObj = pList->a[i];
+    if( pObj->type->xChop!=0
+     && pObj->ptAt.x==pCenter->x
+     && pObj->ptAt.y==pCenter->y
+     && !pik_bbox_contains_point(&pObj->bbox, pOther)
+    ){
+      return pObj;
+    }else if( pObj->pSublist ){
+      pObj = pik_find_chopper(pObj->pSublist,pCenter,pOther);
+      if( pObj ) return pObj;
+    }
+  }
+  return 0;
+}
+
+/*
+** There is a line traveling from pFrom to pTo.
+**
+** If pObj is not null and is a choppable object, then chop at
+** the boundary of pObj - where the line crosses the boundary
+** of pObj.
+**
+** If pObj is NULL or has no xChop method, then search for some
+** other object centered at pTo that is choppable and use it
+** instead.
+*/
+static void pik_autochop(Pik *p, PPoint *pFrom, PPoint *pTo, PObj *pObj){
+  if( pObj==0 || pObj->type->xChop==0 ){
+    pObj = pik_find_chopper(p->list, pTo, pFrom);
+  }
+  if( pObj ){
+    *pTo = pObj->type->xChop(p, pObj, pFrom);
+  }
+}
+
+/* This routine runs after all attributes have been received
+** on an object.
+*/
+static void pik_after_adding_attributes(Pik *p, PObj *pObj){
+  int i;
+  PPoint ofst;
+  PNum dx, dy;
+
+  if( p->nErr ) return;
+
+  /* Position block objects */
+  if( pObj->type->isLine==0 ){
+    /* A height or width less than or equal to zero means "autofit".
+    ** Change the height or width to be big enough to contain the text,
+    */
+    if( pObj->h<=0.0 ){
+      if( pObj->nTxt==0 ){
+        pObj->h = 0.0;
+      }else if( pObj->w<=0.0 ){
+        pik_size_to_fit(p, &pObj->errTok, 3);
+      }else{
+        pik_size_to_fit(p, &pObj->errTok, 2);
+      }
+    }
+    if( pObj->w<=0.0 ){
+      if( pObj->nTxt==0 ){
+        pObj->w = 0.0;
+      }else{
+        pik_size_to_fit(p, &pObj->errTok, 1);
+      }
+    }
+    ofst = pik_elem_offset(p, pObj, pObj->eWith);
+    dx = (pObj->with.x - ofst.x) - pObj->ptAt.x;
+    dy = (pObj->with.y - ofst.y) - pObj->ptAt.y;
+    if( dx!=0 || dy!=0 ){
+      pik_elem_move(pObj, dx, dy);
+    }
+  }
+
+  /* For a line object with no movement specified, a single movement
+  ** of the default length in the current direction
+  */
+  if( pObj->type->isLine && p->nTPath<2 ){
+    pik_next_rpath(p, 0);
+    assert( p->nTPath==2 );
+    switch( pObj->inDir ){
+      default:        p->aTPath[1].x += pObj->w; break;
+      case DIR_DOWN:  p->aTPath[1].y -= pObj->h; break;
+      case DIR_LEFT:  p->aTPath[1].x -= pObj->w; break;
+      case DIR_UP:    p->aTPath[1].y += pObj->h; break;
+    }
+    if( pObj->type->xInit==arcInit ){
+      pObj->outDir = (pObj->inDir + (pObj->cw ? 1 : 3))%4;
+      p->eDir = (unsigned char)pObj->outDir;
+      switch( pObj->outDir ){
+        default:        p->aTPath[1].x += pObj->w; break;
+        case DIR_DOWN:  p->aTPath[1].y -= pObj->h; break;
+        case DIR_LEFT:  p->aTPath[1].x -= pObj->w; break;
+        case DIR_UP:    p->aTPath[1].y += pObj->h; break;
+      }
+    }
+  }
+
+  /* Initialize the bounding box prior to running xCheck */
+  pik_bbox_init(&pObj->bbox);
+
+  /* Run object-specific code */
+  if( pObj->type->xCheck!=0 ){
+    pObj->type->xCheck(p,pObj);
+    if( p->nErr ) return;
+  }
+
+  /* Compute final bounding box, entry and exit points, center
+  ** point (ptAt) and path for the object
+  */
+  if( pObj->type->isLine ){
+    pObj->aPath = malloc( sizeof(PPoint)*p->nTPath );
+    if( pObj->aPath==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }else{
+      pObj->nPath = p->nTPath;
+      for(i=0; i<p->nTPath; i++){
+        pObj->aPath[i] = p->aTPath[i];
+      }
+    }
+
+    /* "chop" processing:
+    ** If the line goes to the center of an object with an
+    ** xChop method, then use the xChop method to trim the line.
+    */
+    if( pObj->bChop && pObj->nPath>=2 ){
+      int n = pObj->nPath;
+      pik_autochop(p, &pObj->aPath[n-2], &pObj->aPath[n-1], pObj->pTo);
+      pik_autochop(p, &pObj->aPath[1], &pObj->aPath[0], pObj->pFrom);
+    }
+
+    pObj->ptEnter = pObj->aPath[0];
+    pObj->ptExit = pObj->aPath[pObj->nPath-1];
+
+    /* Compute the center of the line based on the bounding box over
+    ** the vertexes.  This is a difference from PIC.  In Pikchr, the
+    ** center of a line is the center of its bounding box. In PIC, the
+    ** center of a line is halfway between its .start and .end.  For
+    ** straight lines, this is the same point, but for multi-segment
+    ** lines the result is usually diferent */
+    for(i=0; i<pObj->nPath; i++){
+      pik_bbox_add_xy(&pObj->bbox, pObj->aPath[i].x, pObj->aPath[i].y);
+    }
+    pObj->ptAt.x = (pObj->bbox.ne.x + pObj->bbox.sw.x)/2.0;
+    pObj->ptAt.y = (pObj->bbox.ne.y + pObj->bbox.sw.y)/2.0;
+
+    /* Reset the width and height of the object to be the width and height
+    ** of the bounding box over vertexes */
+    pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x;
+    pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y;
+
+    /* If this is a polygon (if it has the "close" attribute), then
+    ** adjust the exit point */
+    if( pObj->bClose ){
+      /* For "closed" lines, the .end is one of the .e, .s, .w, or .n
+      ** points of the bounding box, as with block objects. */
+      pik_elem_set_exit(pObj, pObj->inDir);
+    }
+  }else{
+    PNum w2 = pObj->w/2.0;
+    PNum h2 = pObj->h/2.0;
+    pObj->ptEnter = pObj->ptAt;
+    pObj->ptExit = pObj->ptAt;
+    switch( pObj->inDir ){
+      default:         pObj->ptEnter.x -= w2;  break;
+      case DIR_LEFT:   pObj->ptEnter.x += w2;  break;
+      case DIR_UP:     pObj->ptEnter.y -= h2;  break;
+      case DIR_DOWN:   pObj->ptEnter.y += h2;  break;
+    }
+    switch( pObj->outDir ){
+      default:         pObj->ptExit.x += w2;  break;
+      case DIR_LEFT:   pObj->ptExit.x -= w2;  break;
+      case DIR_UP:     pObj->ptExit.y += h2;  break;
+      case DIR_DOWN:   pObj->ptExit.y -= h2;  break;
+    }
+    pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x - w2, pObj->ptAt.y - h2);
+    pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x + w2, pObj->ptAt.y + h2);
+  }
+  p->eDir = (unsigned char)pObj->outDir;
+}
+
+/* Show basic information about each object as a comment in the
+** generated HTML.  Used for testing and debugging.  Activated
+** by the (undocumented) "debug = 1;"
+** command.
+*/
+static void pik_elem_render(Pik *p, PObj *pObj){
+  char *zDir;
+  if( pObj==0 ) return;
+  pik_append(p,"<!-- ", -1);
+  if( pObj->zName ){
+    pik_append_text(p, pObj->zName, -1, 0);
+    pik_append(p, ": ", 2);
+  }
+  pik_append_text(p, pObj->type->zName, -1, 0);
+  if( pObj->nTxt ){
+    pik_append(p, " \"", 2);
+    pik_append_text(p, pObj->aTxt[0].z+1, pObj->aTxt[0].n-2, 1);
+    pik_append(p, "\"", 1);
+  }
+  pik_append_num(p, " w=", pObj->w);
+  pik_append_num(p, " h=", pObj->h);
+  pik_append_point(p, " center=", &pObj->ptAt);
+  pik_append_point(p, " enter=", &pObj->ptEnter);
+  switch( pObj->outDir ){
+    default:        zDir = " right";  break;
+    case DIR_LEFT:  zDir = " left";   break;
+    case DIR_UP:    zDir = " up";     break;
+    case DIR_DOWN:  zDir = " down";   break;
+  }
+  pik_append_point(p, " exit=", &pObj->ptExit);
+  pik_append(p, zDir, -1);
+  pik_append(p, " -->\n", -1);
+}
+
+/* Render a list of objects
+*/
+void pik_elist_render(Pik *p, PList *pList){
+  int i;
+  int iNextLayer = 0;
+  int iThisLayer;
+  int bMoreToDo;
+  int miss = 0;
+  int mDebug = pik_value_int(p, "debug", 5, 0);
+  PNum colorLabel;
+  do{
+    bMoreToDo = 0;
+    iThisLayer = iNextLayer;
+    iNextLayer = 0x7fffffff;
+    for(i=0; i<pList->n; i++){
+      PObj *pObj = pList->a[i];
+      void (*xRender)(Pik*,PObj*);
+      if( pObj->iLayer>iThisLayer ){
+        if( pObj->iLayer<iNextLayer ) iNextLayer = pObj->iLayer;
+        bMoreToDo = 1;
+        continue; /* Defer until another round */
+      }else if( pObj->iLayer<iThisLayer ){
+        continue;
+      }
+      if( mDebug & 1 ) pik_elem_render(p, pObj);
+      xRender = pObj->type->xRender;
+      if( xRender ){
+        xRender(p, pObj);
+      }
+      if( pObj->pSublist ){
+        pik_elist_render(p, pObj->pSublist);
+      }
+    }
+  }while( bMoreToDo );
+
+  /* If the color_debug_label value is defined, then go through
+  ** and paint a dot at every label location */
+  colorLabel = pik_value(p, "debug_label_color", 17, &miss);
+  if( miss==0 && colorLabel>=0.0 ){
+    PObj dot;
+    memset(&dot, 0, sizeof(dot));
+    dot.type = &noopClass;
+    dot.rad = 0.015;
+    dot.sw = 0.015;
+    dot.fill = colorLabel;
+    dot.color = colorLabel;
+    dot.nTxt = 1;
+    dot.aTxt[0].eCode = TP_ABOVE;
+    for(i=0; i<pList->n; i++){
+      PObj *pObj = pList->a[i];
+      if( pObj->zName==0 ) continue;
+      dot.ptAt = pObj->ptAt;
+      dot.aTxt[0].z = pObj->zName;
+      dot.aTxt[0].n = (int)strlen(pObj->zName);
+      dotRender(p, &dot);
+    }
+  }
+}
+
+/* Add all objects of the list pList to the bounding box
+*/
+static void pik_bbox_add_elist(Pik *p, PList *pList, PNum wArrow){
+  int i;
+  for(i=0; i<pList->n; i++){
+    PObj *pObj = pList->a[i];
+    if( pObj->sw>0.0 ) pik_bbox_addbox(&p->bbox, &pObj->bbox);
+    pik_append_txt(p, pObj, &p->bbox);
+    if( pObj->pSublist ) pik_bbox_add_elist(p, pObj->pSublist, wArrow);
+
+
+    /* Expand the bounding box to account for arrowheads on lines */
+    if( pObj->type->isLine && pObj->nPath>0 ){
+      if( pObj->larrow ){
+        pik_bbox_addellipse(&p->bbox, pObj->aPath[0].x, pObj->aPath[0].y,
+                            wArrow, wArrow);
+      }
+      if( pObj->rarrow ){
+        int j = pObj->nPath-1;
+        pik_bbox_addellipse(&p->bbox, pObj->aPath[j].x, pObj->aPath[j].y,
+                            wArrow, wArrow);
+      }
+    }
+  }
+}
+
+/* Recompute key layout parameters from variables. */
+static void pik_compute_layout_settings(Pik *p){
+  PNum thickness;  /* Line thickness */
+  PNum wArrow;     /* Width of arrowheads */
+
+  /* Set up rendering parameters */
+  if( p->bLayoutVars ) return;
+  thickness = pik_value(p,"thickness",9,0);
+  if( thickness<=0.01 ) thickness = 0.01;
+  wArrow = 0.5*pik_value(p,"arrowwid",8,0);
+  p->wArrow = wArrow/thickness;
+  p->hArrow = pik_value(p,"arrowht",7,0)/thickness;
+  p->fontScale = pik_value(p,"fontscale",9,0);
+  if( p->fontScale<=0.0 ) p->fontScale = 1.0;
+  p->rScale = 144.0;
+  p->charWidth = pik_value(p,"charwid",7,0)*p->fontScale;
+  p->charHeight = pik_value(p,"charht",6,0)*p->fontScale;
+  p->bLayoutVars = 1;
+}
+
+/* Render a list of objects.  Write the SVG into p->zOut.
+** Delete the input object_list before returnning.
+*/
+static void pik_render(Pik *p, PList *pList){
+  if( pList==0 ) return;
+  if( p->nErr==0 ){
+    PNum thickness;  /* Stroke width */
+    PNum margin;     /* Extra bounding box margin */
+    PNum w, h;       /* Drawing width and height */
+    PNum wArrow;
+    PNum pikScale;   /* Value of the "scale" variable */
+    int miss = 0;
+
+    /* Set up rendering parameters */
+    pik_compute_layout_settings(p);
+    thickness = pik_value(p,"thickness",9,0);
+    if( thickness<=0.01 ) thickness = 0.01;
+    margin = pik_value(p,"margin",6,0);
+    margin += thickness;
+    wArrow = p->wArrow*thickness;
+    miss = 0;
+    p->fgcolor = pik_value_int(p,"fgcolor",7,&miss);
+    if( miss ){
+      PToken t;
+      t.z = "fgcolor";
+      t.n = 7;
+      p->fgcolor = pik_round(pik_lookup_color(0, &t));
+    }
+    miss = 0;
+    p->bgcolor = pik_value_int(p,"bgcolor",7,&miss);
+    if( miss ){
+      PToken t;
+      t.z = "bgcolor";
+      t.n = 7;
+      p->bgcolor = pik_round(pik_lookup_color(0, &t));
+    }
+
+    /* Compute a bounding box over all objects so that we can know
+    ** how big to declare the SVG canvas */
+    pik_bbox_init(&p->bbox);
+    pik_bbox_add_elist(p, pList, wArrow);
+
+    /* Expand the bounding box slightly to account for line thickness
+    ** and the optional "margin = EXPR" setting. */
+    p->bbox.ne.x += margin + pik_value(p,"rightmargin",11,0);
+    p->bbox.ne.y += margin + pik_value(p,"topmargin",9,0);
+    p->bbox.sw.x -= margin + pik_value(p,"leftmargin",10,0);
+    p->bbox.sw.y -= margin + pik_value(p,"bottommargin",12,0);
+
+    /* Output the SVG */
+    pik_append(p, "<svg xmlns='http://www.w3.org/2000/svg'",-1);
+    if( p->zClass ){
+      pik_append(p, " class=\"", -1);
+      pik_append(p, p->zClass, -1);
+      pik_append(p, "\"", 1);
+    }
+    w = p->bbox.ne.x - p->bbox.sw.x;
+    h = p->bbox.ne.y - p->bbox.sw.y;
+    p->wSVG = pik_round(p->rScale*w);
+    p->hSVG = pik_round(p->rScale*h);
+    pikScale = pik_value(p,"scale",5,0);
+    if( pikScale>=0.001 && pikScale<=1000.0
+     && (pikScale<0.99 || pikScale>1.01)
+    ){
+      p->wSVG = pik_round(p->wSVG*pikScale);
+      p->hSVG = pik_round(p->hSVG*pikScale);
+      pik_append_num(p, " width=\"", p->wSVG);
+      pik_append_num(p, "\" height=\"", p->hSVG);
+      pik_append(p, "\"", 1);
+    }
+    pik_append_dis(p, " viewBox=\"0 0 ",w,"");
+    pik_append_dis(p, " ",h,"\">\n");
+    pik_elist_render(p, pList);
+    pik_append(p,"</svg>\n", -1);
+  }else{
+    p->wSVG = -1;
+    p->hSVG = -1;
+  }
+  pik_elist_free(p, pList);
+}
+
+
+
+/*
+** An array of this structure defines a list of keywords.
+*/
+typedef struct PikWord {
+  char *zWord;             /* Text of the keyword */
+  unsigned char nChar;     /* Length of keyword text in bytes */
+  unsigned char eType;     /* Token code */
+  unsigned char eCode;     /* Extra code for the token */
+  unsigned char eEdge;     /* CP_* code for corner/edge keywords */
+} PikWord;
+
+/*
+** Keywords
+*/
+static const PikWord pik_keywords[] = {
+  { "above",      5,   T_ABOVE,     0,         0        },
+  { "abs",        3,   T_FUNC1,     FN_ABS,    0        },
+  { "aligned",    7,   T_ALIGNED,   0,         0        },
+  { "and",        3,   T_AND,       0,         0        },
+  { "as",         2,   T_AS,        0,         0        },
+  { "assert",     6,   T_ASSERT,    0,         0        },
+  { "at",         2,   T_AT,        0,         0        },
+  { "behind",     6,   T_BEHIND,    0,         0        },
+  { "below",      5,   T_BELOW,     0,         0        },
+  { "between",    7,   T_BETWEEN,   0,         0        },
+  { "big",        3,   T_BIG,       0,         0        },
+  { "bold",       4,   T_BOLD,      0,         0        },
+  { "bot",        3,   T_EDGEPT,    0,         CP_S     },
+  { "bottom",     6,   T_BOTTOM,    0,         CP_S     },
+  { "c",          1,   T_EDGEPT,    0,         CP_C     },
+  { "ccw",        3,   T_CCW,       0,         0        },
+  { "center",     6,   T_CENTER,    0,         CP_C     },
+  { "chop",       4,   T_CHOP,      0,         0        },
+  { "close",      5,   T_CLOSE,     0,         0        },
+  { "color",      5,   T_COLOR,     0,         0        },
+  { "cos",        3,   T_FUNC1,     FN_COS,    0        },
+  { "cw",         2,   T_CW,        0,         0        },
+  { "dashed",     6,   T_DASHED,    0,         0        },
+  { "define",     6,   T_DEFINE,    0,         0        },
+  { "diameter",   8,   T_DIAMETER,  0,         0        },
+  { "dist",       4,   T_DIST,      0,         0        },
+  { "dotted",     6,   T_DOTTED,    0,         0        },
+  { "down",       4,   T_DOWN,      DIR_DOWN,  0        },
+  { "e",          1,   T_EDGEPT,    0,         CP_E     },
+  { "east",       4,   T_EDGEPT,    0,         CP_E     },
+  { "end",        3,   T_END,       0,         CP_END   },
+  { "even",       4,   T_EVEN,      0,         0        },
+  { "fill",       4,   T_FILL,      0,         0        },
+  { "first",      5,   T_NTH,       0,         0        },
+  { "fit",        3,   T_FIT,       0,         0        },
+  { "from",       4,   T_FROM,      0,         0        },
+  { "go",         2,   T_GO,        0,         0        },
+  { "heading",    7,   T_HEADING,   0,         0        },
+  { "height",     6,   T_HEIGHT,    0,         0        },
+  { "ht",         2,   T_HEIGHT,    0,         0        },
+  { "in",         2,   T_IN,        0,         0        },
+  { "int",        3,   T_FUNC1,     FN_INT,    0        },
+  { "invis",      5,   T_INVIS,     0,         0        },
+  { "invisible",  9,   T_INVIS,     0,         0        },
+  { "italic",     6,   T_ITALIC,    0,         0        },
+  { "last",       4,   T_LAST,      0,         0        },
+  { "left",       4,   T_LEFT,      DIR_LEFT,  CP_W     },
+  { "ljust",      5,   T_LJUST,     0,         0        },
+  { "max",        3,   T_FUNC2,     FN_MAX,    0        },
+  { "min",        3,   T_FUNC2,     FN_MIN,    0        },
+  { "n",          1,   T_EDGEPT,    0,         CP_N     },
+  { "ne",         2,   T_EDGEPT,    0,         CP_NE    },
+  { "north",      5,   T_EDGEPT,    0,         CP_N     },
+  { "nw",         2,   T_EDGEPT,    0,         CP_NW    },
+  { "of",         2,   T_OF,        0,         0        },
+  { "previous",   8,   T_LAST,      0,         0,       },
+  { "print",      5,   T_PRINT,     0,         0        },
+  { "rad",        3,   T_RADIUS,    0,         0        },
+  { "radius",     6,   T_RADIUS,    0,         0        },
+  { "right",      5,   T_RIGHT,     DIR_RIGHT, CP_E     },
+  { "rjust",      5,   T_RJUST,     0,         0        },
+  { "s",          1,   T_EDGEPT,    0,         CP_S     },
+  { "same",       4,   T_SAME,      0,         0        },
+  { "se",         2,   T_EDGEPT,    0,         CP_SE    },
+  { "sin",        3,   T_FUNC1,     FN_SIN,    0        },
+  { "small",      5,   T_SMALL,     0,         0        },
+  { "solid",      5,   T_SOLID,     0,         0        },
+  { "south",      5,   T_EDGEPT,    0,         CP_S     },
+  { "sqrt",       4,   T_FUNC1,     FN_SQRT,   0        },
+  { "start",      5,   T_START,     0,         CP_START },
+  { "sw",         2,   T_EDGEPT,    0,         CP_SW    },
+  { "t",          1,   T_TOP,       0,         CP_N     },
+  { "the",        3,   T_THE,       0,         0        },
+  { "then",       4,   T_THEN,      0,         0        },
+  { "thick",      5,   T_THICK,     0,         0        },
+  { "thickness",  9,   T_THICKNESS, 0,         0        },
+  { "thin",       4,   T_THIN,      0,         0        },
+  { "this",       4,   T_THIS,      0,         0        },
+  { "to",         2,   T_TO,        0,         0        },
+  { "top",        3,   T_TOP,       0,         CP_N     },
+  { "until",      5,   T_UNTIL,     0,         0        },
+  { "up",         2,   T_UP,        DIR_UP,    0        },
+  { "vertex",     6,   T_VERTEX,    0,         0        },
+  { "w",          1,   T_EDGEPT,    0,         CP_W     },
+  { "way",        3,   T_WAY,       0,         0        },
+  { "west",       4,   T_EDGEPT,    0,         CP_W     },
+  { "wid",        3,   T_WIDTH,     0,         0        },
+  { "width",      5,   T_WIDTH,     0,         0        },
+  { "with",       4,   T_WITH,      0,         0        },
+  { "x",          1,   T_X,         0,         0        },
+  { "y",          1,   T_Y,         0,         0        },
+};
+
+/*
+** Search a PikWordlist for the given keyword.  Return a pointer to the
+** keyword entry found.  Or return 0 if not found.
+*/
+static const PikWord *pik_find_word(
+  const char *zIn,              /* Word to search for */
+  int n,                        /* Length of zIn */
+  const PikWord *aList,         /* List to search */
+  int nList                     /* Number of entries in aList */
+){
+  int first = 0;
+  int last = nList-1;
+  while( first<=last ){
+    int mid = (first + last)/2;
+    int sz = aList[mid].nChar;
+    int c = strncmp(zIn, aList[mid].zWord, sz<n ? sz : n);
+    if( c==0 ){
+      c = n - sz;
+      if( c==0 ) return &aList[mid];
+    }
+    if( c<0 ){
+      last = mid-1;
+    }else{
+      first = mid+1;
+    }
+  }
+  return 0;
+}
+
+/*
+** Set a symbolic debugger breakpoint on this routine to receive a
+** breakpoint when the "#breakpoint" token is parsed.
+*/
+static void pik_breakpoint(const unsigned char *z){
+  /* Prevent C compilers from optimizing out this routine. */
+  if( z[2]=='X' ) exit(1);
+}
+
+
+/*
+** Return the length of next token.  The token starts on
+** the pToken->z character.  Fill in other fields of the
+** pToken object as appropriate.
+*/
+static int pik_token_length(PToken *pToken, int bAllowCodeBlock){
+  const unsigned char *z = (const unsigned char*)pToken->z;
+  int i;
+  unsigned char c, c2;
+  switch( z[0] ){
+    case '\\': {
+      pToken->eType = T_WHITESPACE;
+      for(i=1; z[i]=='\r' || z[i]==' ' || z[i]=='\t'; i++){}
+      if( z[i]=='\n'  ) return i+1;
+      pToken->eType = T_ERROR;
+      return 1;
+    }
+    case ';':
+    case '\n': {
+      pToken->eType = T_EOL;
+      return 1;
+    }
+    case '"': {
+      for(i=1; (c = z[i])!=0; i++){
+        if( c=='\\' ){ 
+          if( z[i+1]==0 ) break;
+          i++;
+          continue;
+        }
+        if( c=='"' ){
+          pToken->eType = T_STRING;
+          return i+1;
+        }
+      }
+      pToken->eType = T_ERROR;
+      return i;
+    }
+    case ' ':
+    case '\t':
+    case '\f':
+    case '\r': {
+      for(i=1; (c = z[i])==' ' || c=='\t' || c=='\r' || c=='\t'; i++){}
+      pToken->eType = T_WHITESPACE;
+      return i;
+    }
+    case '#': {
+      for(i=1; (c = z[i])!=0 && c!='\n'; i++){}
+      pToken->eType = T_WHITESPACE;
+      /* If the comment is "#breakpoint" then invoke the pik_breakpoint()
+      ** routine.  The pik_breakpoint() routie is a no-op that serves as
+      ** a convenient place to set a gdb breakpoint when debugging. */
+      if( strncmp((const char*)z,"#breakpoint",11)==0 ) pik_breakpoint(z);
+      return i;
+    }
+    case '/': {
+      if( z[1]=='*' ){
+        for(i=2; z[i]!=0 && (z[i]!='*' || z[i+1]!='/'); i++){}
+        if( z[i]=='*' ){
+          pToken->eType = T_WHITESPACE;
+          return i+2;
+        }else{
+          pToken->eType = T_ERROR;
+          return i;
+        }
+      }else if( z[1]=='/' ){
+        for(i=2; z[i]!=0 && z[i]!='\n'; i++){}
+        pToken->eType = T_WHITESPACE;
+        return i;
+      }else if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_SLASH;
+        return 2;
+      }else{
+        pToken->eType = T_SLASH;
+        return 1;
+      }
+    }
+    case '+': {
+      if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_PLUS;
+        return 2;
+      }
+      pToken->eType = T_PLUS;
+      return 1;
+    }
+    case '*': {
+      if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_STAR;
+        return 2;
+      }
+      pToken->eType = T_STAR;
+      return 1;
+    }
+    case '%': {   pToken->eType = T_PERCENT; return 1; }
+    case '(': {   pToken->eType = T_LP;      return 1; }
+    case ')': {   pToken->eType = T_RP;      return 1; }
+    case '[': {   pToken->eType = T_LB;      return 1; }
+    case ']': {   pToken->eType = T_RB;      return 1; }
+    case ',': {   pToken->eType = T_COMMA;   return 1; }
+    case ':': {   pToken->eType = T_COLON;   return 1; }
+    case '>': {   pToken->eType = T_GT;      return 1; }
+    case '=': {
+       if( z[1]=='=' ){
+         pToken->eType = T_EQ;
+         return 2;
+       }
+       pToken->eType = T_ASSIGN;
+       pToken->eCode = T_ASSIGN;
+       return 1;
+    }
+    case '-': {
+      if( z[1]=='>' ){
+        pToken->eType = T_RARROW;
+        return 2;
+      }else if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_MINUS;
+        return 2;
+      }else{
+        pToken->eType = T_MINUS;
+        return 1;
+      }
+    }
+    case '<': { 
+      if( z[1]=='-' ){
+         if( z[2]=='>' ){
+           pToken->eType = T_LRARROW;
+           return 3;
+         }else{
+           pToken->eType = T_LARROW;
+           return 2;
+         }
+      }else{
+        pToken->eType = T_LT;
+        return 1;
+      }
+    }
+    case 0xe2: {
+      if( z[1]==0x86 ){
+        if( z[2]==0x90 ){
+          pToken->eType = T_LARROW;   /* <- */
+          return 3;
+        }
+        if( z[2]==0x92 ){
+          pToken->eType = T_RARROW;   /* -> */
+          return 3;
+        }
+        if( z[2]==0x94 ){
+          pToken->eType = T_LRARROW;  /* <-> */
+          return 3;
+        }
+      }
+      pToken->eType = T_ERROR;
+      return 1;
+    }
+    case '{': {
+      int len, depth;
+      i = 1;
+      if( bAllowCodeBlock ){
+        depth = 1;
+        while( z[i] && depth>0 ){
+          PToken x;
+          x.z = (char*)(z+i);
+          len = pik_token_length(&x, 0);
+          if( len==1 ){
+            if( z[i]=='{' ) depth++;
+            if( z[i]=='}' ) depth--;
+          }
+          i += len;
+        }
+      }else{
+        depth = 0;
+      }
+      if( depth ){
+        pToken->eType = T_ERROR;
+        return 1;
+      }
+      pToken->eType = T_CODEBLOCK;
+      return i;
+    }
+    case '&': {
+      static const struct {
+         int nByte;            /* Number of bytes in zEntity */
+         int eCode;            /* Corresponding token code */
+         const char *zEntity;  /* Name of the HTML entity */
+      } aEntity[] = {
+                      /*   123456789 1234567 */
+         { 6,  T_RARROW,  "&rarr;"           },   /* Same as -> */
+         { 12, T_RARROW,  "&rightarrow;"     },   /* Same as -> */
+         { 6,  T_LARROW,  "&larr;"           },   /* Same as <- */
+         { 11, T_LARROW,  "&leftarrow;"      },   /* Same as <- */
+         { 16, T_LRARROW, "&leftrightarrow;" },   /* Same as <-> */
+      };
+      unsigned int i;
+      for(i=0; i<sizeof(aEntity)/sizeof(aEntity[0]); i++){
+        if( strncmp((const char*)z,aEntity[i].zEntity,aEntity[i].nByte)==0 ){
+          pToken->eType = aEntity[i].eCode;
+          return aEntity[i].nByte;
+        }
+      }
+      pToken->eType = T_ERROR;
+      return 1;
+    }
+    default: {
+      c = z[0];
+      if( c=='.' ){
+        unsigned char c1 = z[1];
+        if( islower(c1) ){
+          const PikWord *pFound;
+          for(i=2; (c = z[i])>='a' && c<='z'; i++){}
+          pFound = pik_find_word((const char*)z+1, i-1,
+                                    pik_keywords, count(pik_keywords));
+          if( pFound && (pFound->eEdge>0 ||
+                         pFound->eType==T_EDGEPT ||
+                         pFound->eType==T_START ||
+                         pFound->eType==T_END )
+          ){
+            /* Dot followed by something that is a 2-D place value */
+            pToken->eType = T_DOT_E;
+          }else if( pFound && (pFound->eType==T_X || pFound->eType==T_Y) ){
+            /* Dot followed by "x" or "y" */
+            pToken->eType = T_DOT_XY;
+          }else{
+            /* Any other "dot" */
+            pToken->eType = T_DOT_L;
+          }
+          return 1;
+        }else if( isdigit(c1) ){
+          i = 0;
+          /* no-op.  Fall through to number handling */
+        }else if( isupper(c1) ){
+          for(i=2; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+          pToken->eType = T_DOT_U;
+          return 1;
+        }else{
+          pToken->eType = T_ERROR;
+          return 1;
+        }
+      }
+      if( (c>='0' && c<='9') || c=='.' ){
+        int nDigit;
+        int isInt = 1;
+        if( c!='.' ){
+          nDigit = 1;
+          for(i=1; (c = z[i])>='0' && c<='9'; i++){ nDigit++; }
+          if( i==1 && (c=='x' || c=='X') ){
+            for(i=2; (c = z[i])!=0 && isxdigit(c); i++){}
+            pToken->eType = T_NUMBER;
+            return i;
+          }
+        }else{
+          isInt = 0;
+          nDigit = 0;
+          i = 0;
+        }
+        if( c=='.' ){
+          isInt = 0;
+          for(i++; (c = z[i])>='0' && c<='9'; i++){ nDigit++; }
+        }
+        if( nDigit==0 ){
+          pToken->eType = T_ERROR;
+          return i;
+        }
+        if( c=='e' || c=='E' ){
+          int iBefore = i;
+          i++;
+          c2 = z[i];
+          if( c2=='+' || c2=='-' ){
+            i++;
+            c2 = z[i];
+          }
+          if( c2<'0' || c>'9' ){
+            /* This is not an exp */
+            i = iBefore;
+          }else{
+            i++;
+            isInt = 0;
+            while( (c = z[i])>='0' && c<='9' ){ i++; }
+          }
+        }
+        c2 = c ? z[i+1] : 0;
+        if( isInt ){
+          if( (c=='t' && c2=='h')
+           || (c=='r' && c2=='d')
+           || (c=='n' && c2=='d')
+           || (c=='s' && c2=='t')
+          ){
+            pToken->eType = T_NTH;
+            return i+2;
+          }
+        }
+        if( (c=='i' && c2=='n')
+         || (c=='c' && c2=='m')
+         || (c=='m' && c2=='m')
+         || (c=='p' && c2=='t')
+         || (c=='p' && c2=='x')
+         || (c=='p' && c2=='c')
+        ){
+          i += 2;
+        }
+        pToken->eType = T_NUMBER;
+        return i;
+      }else if( islower(c) ){
+        const PikWord *pFound;
+        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+        pFound = pik_find_word((const char*)z, i,
+                               pik_keywords, count(pik_keywords));
+        if( pFound ){
+          pToken->eType = pFound->eType;
+          pToken->eCode = pFound->eCode;
+          pToken->eEdge = pFound->eEdge;
+          return i;
+        }
+        pToken->n = i;
+        if( pik_find_class(pToken)!=0 ){
+          pToken->eType = T_CLASSNAME;
+        }else{
+          pToken->eType = T_ID;
+        }
+        return i;
+      }else if( c>='A' && c<='Z' ){
+        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+        pToken->eType = T_PLACENAME;
+        return i;
+      }else if( c=='$' && z[1]>='1' && z[1]<='9' && !isdigit(z[2]) ){
+        pToken->eType = T_PARAMETER;
+        pToken->eCode = z[1] - '1';
+        return 2;
+      }else if( c=='_' || c=='$' || c=='@' ){
+        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+        pToken->eType = T_ID;
+        return i;
+      }else{
+        pToken->eType = T_ERROR;
+        return 1;
+      }
+    }
+  }
+}
+
+/*
+** Return a pointer to the next non-whitespace token after pThis.
+** This is used to help form error messages.
+*/
+static PToken pik_next_semantic_token(PToken *pThis){
+  PToken x;
+  int sz;
+  int i = pThis->n;
+  memset(&x, 0, sizeof(x));
+  x.z = pThis->z;
+  while(1){
+    x.z = pThis->z + i;
+    sz = pik_token_length(&x, 1);
+    if( x.eType!=T_WHITESPACE ){
+      x.n = sz;
+      return x;
+    }
+    i += sz;
+  }
+}
+
+/* Parser arguments to a macro invocation
+**
+**     (arg1, arg2, ...)
+**
+** Arguments are comma-separated, except that commas within string
+** literals or with (...), {...}, or [...] do not count.  The argument
+** list begins and ends with parentheses.  There can be at most 9
+** arguments.
+**
+** Return the number of bytes in the argument list.
+*/
+static unsigned int pik_parse_macro_args(
+  Pik *p,
+  const char *z,     /* Start of the argument list */
+  int n,             /* Available bytes */
+  PToken *args,      /* Fill in with the arguments */
+  PToken *pOuter     /* Arguments of the next outer context, or NULL */
+){
+  int nArg = 0;
+  int i, j, sz;
+  int iStart;
+  int depth = 0;
+  PToken x;
+  if( z[0]!='(' ) return 0;
+  args[0].z = z+1;
+  iStart = 1;
+  for(i=1; i<n && z[i]!=')'; i+=sz){
+    x.z = z+i;
+    sz = pik_token_length(&x, 0);
+    if( sz!=1 ) continue;
+    if( z[i]==',' && depth<=0 ){
+      args[nArg].n = i - iStart;
+      if( nArg==8 ){
+        x.z = z;
+        x.n = 1;
+        pik_error(p, &x, "too many macro arguments - max 9");
+        return 0;
+      }
+      nArg++;
+      args[nArg].z = z+i+1;
+      iStart = i+1;
+      depth = 0;
+    }else if( z[i]=='(' || z[i]=='{' || z[i]=='[' ){
+      depth++;
+    }else if( z[i]==')' || z[i]=='}' || z[i]==']' ){
+      depth--;
+    }
+  }
+  if( z[i]==')' ){
+    args[nArg].n = i - iStart;
+    /* Remove leading and trailing whitespace from each argument.
+    ** If what remains is one of $1, $2, ... $9 then transfer the
+    ** corresponding argument from the outer context */
+    for(j=0; j<=nArg; j++){
+      PToken *t = &args[j];
+      while( t->n>0 && isspace(t->z[0]) ){ t->n--; t->z++; }
+      while( t->n>0 && isspace(t->z[t->n-1]) ){ t->n--; }
+      if( t->n==2 && t->z[0]=='$' && t->z[1]>='1' && t->z[1]<='9' ){
+        if( pOuter ) *t = pOuter[t->z[1]-'1'];
+        else t->n = 0;
+      }
+    }
+    return i+1;
+  }
+  x.z = z;
+  x.n = 1;
+  pik_error(p, &x, "unterminated macro argument list");
+  return 0;
+}
+
+/*
+** Split up the content of a PToken into multiple tokens and
+** send each to the parser.
+*/
+void pik_tokenize(Pik *p, PToken *pIn, yyParser *pParser, PToken *aParam){
+  unsigned int i;
+  int sz = 0;
+  PToken token;
+  PMacro *pMac;
+  for(i=0; i<pIn->n && pIn->z[i] && p->nErr==0; i+=sz){
+    token.eCode = 0;
+    token.eEdge = 0;
+    token.z = pIn->z + i;
+    sz = pik_token_length(&token, 1);
+    if( token.eType==T_WHITESPACE ){
+      /* no-op */
+    }else if( sz>50000 ){
+      token.n = 1;
+      pik_error(p, &token, "token is too long - max length 50000 bytes");
+      break;
+    }else if( token.eType==T_ERROR ){
+      token.n = (unsigned short)(sz & 0xffff);
+      pik_error(p, &token, "unrecognized token");
+      break;
+    }else if( sz+i>pIn->n ){
+      token.n = pIn->n - i;
+      pik_error(p, &token, "syntax error");
+      break;
+    }else if( token.eType==T_PARAMETER ){
+      /* Substitute a parameter into the input stream */
+      if( aParam==0 || aParam[token.eCode].n==0 ){
+        continue;
+      }
+      token.n = (unsigned short)(sz & 0xffff);
+      if( p->nCtx>=count(p->aCtx) ){
+        pik_error(p, &token, "macros nested too deep");
+      }else{
+        p->aCtx[p->nCtx++] = token;
+        pik_tokenize(p, &aParam[token.eCode], pParser, 0);
+        p->nCtx--;
+      }
+    }else if( token.eType==T_ID
+               && (token.n = (unsigned short)(sz & 0xffff), 
+                   (pMac = pik_find_macro(p,&token))!=0)
+    ){
+      PToken args[9];
+      unsigned int j = i+sz;
+      if( pMac->inUse ){
+        pik_error(p, &pMac->macroName, "recursive macro definition");
+        break;
+      }
+      token.n = (short int)(sz & 0xffff);
+      if( p->nCtx>=count(p->aCtx) ){
+        pik_error(p, &token, "macros nested too deep");
+        break;
+      } 
+      pMac->inUse = 1;
+      memset(args, 0, sizeof(args));
+      p->aCtx[p->nCtx++] = token;
+      sz += pik_parse_macro_args(p, pIn->z+j, pIn->n-j, args, aParam);
+      pik_tokenize(p, &pMac->macroBody, pParser, args);
+      p->nCtx--;
+      pMac->inUse = 0;
+    }else{
+#if 0
+      printf("******** Token %s (%d): \"%.*s\" **************\n",
+             yyTokenName[token.eType], token.eType,
+             (int)(isspace(token.z[0]) ? 0 : sz), token.z);
+#endif
+      token.n = (unsigned short)(sz & 0xffff);
+      if( p->nToken++ > PIKCHR_TOKEN_LIMIT ){
+        pik_error(p, &token, "script is too complex");
+        break;
+      }
+      pik_parser(pParser, token.eType, token);
+    }
+  }
+}
+
+/*
+** Parse the PIKCHR script contained in zText[].  Return a rendering.  Or
+** if an error is encountered, return the error text.  The error message
+** is HTML formatted.  So regardless of what happens, the return text
+** is safe to be insertd into an HTML output stream.
+**
+** If pnWidth and pnHeight are not NULL, then this routine writes the
+** width and height of the <SVG> object into the integers that they
+** point to.  A value of -1 is written if an error is seen.
+**
+** If zClass is not NULL, then it is a class name to be included in
+** the <SVG> markup.
+**
+** The returned string is contained in memory obtained from malloc()
+** and should be released by the caller.
+*/
+char *pikchr(
+  const char *zText,     /* Input PIKCHR source text.  zero-terminated */
+  const char *zClass,    /* Add class="%s" to <svg> markup */
+  unsigned int mFlags,   /* Flags used to influence rendering behavior */
+  int *pnWidth,          /* Write width of <svg> here, if not NULL */
+  int *pnHeight          /* Write height here, if not NULL */
+){
+  Pik s;
+  yyParser sParse;
+
+  memset(&s, 0, sizeof(s));
+  s.sIn.z = zText;
+  s.sIn.n = (unsigned int)strlen(zText);
+  s.eDir = DIR_RIGHT;
+  s.zClass = zClass;
+  s.mFlags = mFlags;
+  pik_parserInit(&sParse, &s);
+#if 0
+  pik_parserTrace(stdout, "parser: ");
+#endif
+  pik_tokenize(&s, &s.sIn, &sParse, 0);
+  if( s.nErr==0 ){
+    PToken token;
+    memset(&token,0,sizeof(token));
+    token.z = zText + (s.sIn.n>0 ? s.sIn.n-1 : 0);
+    token.n = 1;
+    pik_parser(&sParse, 0, token);
+  }
+  pik_parserFinalize(&sParse);
+  if( s.zOut==0 && s.nErr==0 ){
+    pik_append(&s, "<!-- empty pikchr diagram -->\n", -1);
+  }
+  while( s.pVar ){
+    PVar *pNext = s.pVar->pNext;
+    free(s.pVar);
+    s.pVar = pNext;
+  }
+  while( s.pMacros ){
+    PMacro *pNext = s.pMacros->pNext;
+    free(s.pMacros);
+    s.pMacros = pNext;
+  }
+  if( pnWidth ) *pnWidth = s.nErr ? -1 : s.wSVG;
+  if( pnHeight ) *pnHeight = s.nErr ? -1 : s.hSVG;
+  if( s.zOut ){
+    s.zOut[s.nOut] = 0;
+    s.zOut = realloc(s.zOut, s.nOut+1);
+  }
+  return s.zOut;
+}
+
+#if defined(PIKCHR_FUZZ)
+#include <stdint.h>
+int LLVMFuzzerTestOneInput(const uint8_t *aData, size_t nByte){
+  int w,h;
+  char *zIn, *zOut;
+  unsigned int mFlags = nByte & 3;
+  zIn = malloc( nByte + 1 );
+  if( zIn==0 ) return 0;
+  memcpy(zIn, aData, nByte);
+  zIn[nByte] = 0;
+  zOut = pikchr(zIn, "pikchr", mFlags, &w, &h);
+  free(zIn);
+  free(zOut);
+  return 0;
+}
+#endif /* PIKCHR_FUZZ */
+
+#if defined(PIKCHR_SHELL)
+/* Print a usage comment for the shell and exit. */
+static void usage(const char *argv0){
+  fprintf(stderr, "usage: %s [OPTIONS] FILE ...\n", argv0);
+  fprintf(stderr,
+    "Convert Pikchr input files into SVG.  Filename \"-\" means stdin.\n"
+    "All output goes to stdout.\n"
+    "Options:\n"
+    "   --dont-stop      Process all files even if earlier files have errors\n"
+    "   --svg-only       Emit raw SVG without the HTML wrapper\n"
+  );
+  exit(1);
+}
+
+/* Send text to standard output, but escape HTML markup */
+static void print_escape_html(const char *z){
+  int j;
+  char c;
+  while( z[0]!=0 ){
+    for(j=0; (c = z[j])!=0 && c!='<' && c!='>' && c!='&'; j++){}
+    if( j ) printf("%.*s", j, z);
+    z += j+1;
+    j = -1;
+    if( c=='<' ){
+      printf("&lt;");
+    }else if( c=='>' ){
+      printf("&gt;");
+    }else if( c=='&' ){
+      printf("&amp;");
+    }else if( c==0 ){
+      break;
+    }
+  }
+}
+
+/* Read the content of file zFilename into memory obtained from malloc()
+** Return the memory.  If something goes wrong (ex: the file does not exist
+** or cannot be opened) put an error message on stderr and return NULL.
+**
+** If the filename is "-" read stdin.
+*/
+static char *readFile(const char *zFilename){
+  FILE *in;
+  size_t n;
+  size_t nUsed = 0;
+  size_t nAlloc = 0;
+  char *z = 0, *zNew = 0;
+  in = strcmp(zFilename,"-")==0 ? stdin : fopen(zFilename, "rb");
+  if( in==0 ){
+    fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
+    return 0;
+  }
+  while(1){
+    if( nUsed+2>=nAlloc ){
+      nAlloc = nAlloc*2 + 4000;
+      zNew = realloc(z, nAlloc);
+    }
+    if( zNew==0 ){
+      free(z);
+      fprintf(stderr, "out of memory trying to allocate %lld bytes\n",
+              (long long int)nAlloc);
+      exit(1);
+    }
+    z = zNew;
+    n = fread(z+nUsed, 1, nAlloc-nUsed-1, in);
+    if( n<=0 ){
+      break;
+    }
+    nUsed += n;
+  }
+  if( in!=stdin ) fclose(in);
+  z[nUsed] = 0;
+  return z;
+}
+
+
+/* Testing interface
+**
+** Generate HTML on standard output that displays both the original
+** input text and the rendered SVG for all files named on the command
+** line.
+*/
+int main(int argc, char **argv){
+  int i;
+  int bSvgOnly = 0;            /* Output SVG only.  No HTML wrapper */
+  int bDontStop = 0;           /* Continue in spite of errors */
+  int exitCode = 0;            /* What to return */
+  int mFlags = 0;              /* mFlags argument to pikchr() */
+  const char *zStyle = "";     /* Extra styling */
+  const char *zHtmlHdr = 
+    "<!DOCTYPE html>\n"
+    "<html lang=\"en-US\">\n"
+    "<head>\n<title>PIKCHR Test</title>\n"
+    "<style>\n"
+    "  .hidden {\n"
+    "     position: absolute !important;\n"
+    "     opacity: 0 !important;\n"
+    "     pointer-events: none !important;\n"
+    "     display: none !important;\n"
+    "  }\n"
+    "</style>\n"
+    "<script>\n"
+    "  function toggleHidden(id){\n"
+    "    for(var c of document.getElementById(id).children){\n"
+    "      c.classList.toggle('hidden');\n"
+    "    }\n"
+    "  }\n"
+    "</script>\n"
+    "<meta charset=\"utf-8\">\n"
+    "</head>\n"
+    "<body>\n"
+  ;
+  if( argc<2 ) usage(argv[0]);
+  for(i=1; i<argc; i++){
+    char *zIn;
+    char *zOut;
+    int w, h;
+
+    if( argv[i][0]=='-' && argv[i][1]!=0 ){
+      char *z = argv[i];
+      z++;
+      if( z[0]=='-' ) z++;
+      if( strcmp(z,"dont-stop")==0 ){
+        bDontStop = 1;
+      }else
+      if( strcmp(z,"dark-mode")==0 ){
+        zStyle = "color:white;background-color:black;";
+        mFlags |= PIKCHR_DARK_MODE;
+      }else
+      if( strcmp(z,"svg-only")==0 ){
+        if( zHtmlHdr==0 ){
+          fprintf(stderr, "the \"%s\" option must come first\n",argv[i]);
+          exit(1);
+        }
+        bSvgOnly = 1;
+        mFlags |= PIKCHR_PLAINTEXT_ERRORS;
+      }else
+      {
+        fprintf(stderr,"unknown option: \"%s\"\n", argv[i]);
+        usage(argv[0]);
+      }
+      continue;
+    }
+    zIn = readFile(argv[i]);
+    if( zIn==0 ) continue;
+    zOut = pikchr(zIn, "pikchr", mFlags, &w, &h);
+    if( w<0 ) exitCode = 1;
+    if( zOut==0 ){
+      fprintf(stderr, "pikchr() returns NULL.  Out of memory?\n");
+      if( !bDontStop ) exit(1);
+    }else if( bSvgOnly ){
+      printf("%s\n", zOut);
+    }else{
+      if( zHtmlHdr ){
+        printf("%s", zHtmlHdr);
+        zHtmlHdr = 0;
+      }
+      printf("<h1>File %s</h1>\n", argv[i]);
+      if( w<0 ){
+        printf("<p>ERROR</p>\n%s\n", zOut);
+      }else{
+        printf("<div id=\"svg-%d\" onclick=\"toggleHidden('svg-%d')\">\n",i,i);
+        printf("<div style='border:3px solid lightgray;max-width:%dpx;%s'>\n",
+               w,zStyle);
+        printf("%s</div>\n", zOut);
+        printf("<pre class='hidden'>");
+        print_escape_html(zIn);
+        printf("</pre>\n</div>\n");
+      }
+    }
+    free(zOut);
+    free(zIn);
+  }
+  if( !bSvgOnly ){
+    printf("</body></html>\n");
+  }
+  return exitCode ? EXIT_FAILURE : EXIT_SUCCESS; 
+}
+#endif /* PIKCHR_SHELL */
+
+#ifdef PIKCHR_TCL
+#include <tcl.h>
+/*
+** An interface to TCL
+**
+** TCL command:     pikchr $INPUTTEXT
+**
+** Returns a list of 3 elements which are the output text, the width, and
+** the height.
+**
+** Register the "pikchr" command by invoking Pikchr_Init(Tcl_Interp*).  Or
+** compile this source file as a shared library and load it using the
+** "load" command of Tcl.
+**
+** Compile this source-code file into a shared library using a command
+** similar to this:
+**
+**    gcc -c pikchr.so -DPIKCHR_TCL -shared pikchr.c
+*/
+static int pik_tcl_command(
+  ClientData clientData, /* Not Used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  int w, h;              /* Width and height of the pikchr */
+  const char *zIn;       /* Source text input */
+  char *zOut;            /* SVG output text */
+  Tcl_Obj *pRes;         /* The result TCL object */
+
+  (void)clientData;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PIKCHR_SOURCE_TEXT");
+    return TCL_ERROR;
+  }
+  zIn = Tcl_GetString(objv[1]);
+  w = h = 0;
+  zOut = pikchr(zIn, "pikchr", 0, &w, &h);
+  if( zOut==0 ){
+    return TCL_ERROR;  /* Out of memory */
+  }
+  pRes = Tcl_NewObj();
+  Tcl_ListObjAppendElement(0, pRes, Tcl_NewStringObj(zOut, -1));
+  free(zOut);
+  Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(w));
+  Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(h));
+  Tcl_SetObjResult(interp, pRes);
+  return TCL_OK;
+}
+
+#ifndef PACKAGE_NAME
+# define PACKAGE_NAME "pikchr"
+#endif
+#ifndef PACKAGE_VERSION
+# define PACKAGE_VERSION "1.0"
+#endif
+
+/* Invoke this routine to register the "pikchr" command with the interpreter
+** given in the argument */
+int Pikchr_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "pikchr", pik_tcl_command, 0, 0);
+  Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION);
+  return TCL_OK;
+}
+
+
+#endif /* PIKCHR_TCL */
+
+
+#line 8152 "pikchr.c"

+ 33 - 0
pikchr.mod/pikchr/pikchr.h

@@ -0,0 +1,33 @@
+/*
+** Interface definition for Pikchr.
+*/
+
+
+/* The main interface.  Invoke this routine to translate PIKCHR source
+** text into SVG. The SVG is returned in a buffer obtained from malloc().
+** The caller is responsible for freeing the buffer.
+**
+** If an error occurs, *pnWidth is filled with a negative number and
+** the return buffer contains error message text instead of SVG.  By
+** default, the error message is HTML encoded.  However, error messages
+** come out as plaintext if the PIKCHR_PLAINTEXT_ERRORS flag is included
+** as one of the bits in the mFlags parameter.
+*/
+char *pikchr(
+  const char *zText,     /* Input PIKCHR source text.  zero-terminated */
+  const char *zClass,    /* Add class="%s" to <svg> markup */
+  unsigned int mFlags,   /* Flags used to influence rendering behavior */
+  int *pnWidth,          /* OUT: Write width of <svg> here, if not NULL */
+  int *pnHeight          /* OUT: Write height here, if not NULL */
+);
+
+/* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
+** argument to pikchr() in order to cause error message text to come out
+** as text/plain instead of as text/html
+*/
+#define PIKCHR_PLAINTEXT_ERRORS 0x0001
+
+/* Include PIKCHR_DARK_MODE among the bits of mFlags on the 3rd
+** argument to pikchr() to render the image in dark mode.
+*/
+#define PIKCHR_DARK_MODE        0x0002

+ 33 - 0
pikchr.mod/pikchr/pikchr.h.in

@@ -0,0 +1,33 @@
+/*
+** Interface definition for Pikchr.
+*/
+
+
+/* The main interface.  Invoke this routine to translate PIKCHR source
+** text into SVG. The SVG is returned in a buffer obtained from malloc().
+** The caller is responsible for freeing the buffer.
+**
+** If an error occurs, *pnWidth is filled with a negative number and
+** the return buffer contains error message text instead of SVG.  By
+** default, the error message is HTML encoded.  However, error messages
+** come out as plaintext if the PIKCHR_PLAINTEXT_ERRORS flag is included
+** as one of the bits in the mFlags parameter.
+*/
+char *pikchr(
+  const char *zText,     /* Input PIKCHR source text.  zero-terminated */
+  const char *zClass,    /* Add class="%s" to <svg> markup */
+  unsigned int mFlags,   /* Flags used to influence rendering behavior */
+  int *pnWidth,          /* OUT: Write width of <svg> here, if not NULL */
+  int *pnHeight          /* OUT: Write height here, if not NULL */
+);
+
+/* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
+** argument to pikchr() in order to cause error message text to come out
+** as text/plain instead of as text/html
+*/
+#define PIKCHR_PLAINTEXT_ERRORS 0x0001
+
+/* Include PIKCHR_DARK_MODE among the bits of mFlags on the 3rd
+** argument to pikchr() to render the image in dark mode.
+*/
+#define PIKCHR_DARK_MODE        0x0002

+ 5525 - 0
pikchr.mod/pikchr/pikchr.y

@@ -0,0 +1,5525 @@
+%include {
+/*
+** Zero-Clause BSD license:
+**
+** Copyright (C) 2020-09-01 by D. Richard Hipp <[email protected]>
+**
+** Permission to use, copy, modify, and/or distribute this software for
+** any purpose with or without fee is hereby granted.
+**
+****************************************************************************
+**
+** This software translates a PIC-inspired diagram language into SVG.
+**
+** PIKCHR (pronounced like "picture") is *mostly* backwards compatible
+** with legacy PIC, though some features of legacy PIC are removed 
+** (for example, the "sh" command is removed for security) and
+** many enhancements are added.
+**
+** PIKCHR is designed for use in an internet facing web environment.
+** In particular, PIKCHR is designed to safely generate benign SVG from
+** source text that provided by a hostile agent. 
+**
+** This code was originally written by D. Richard Hipp using documentation
+** from prior PIC implementations but without reference to prior code.
+** All of the code in this project is original.
+**
+** This file implements a C-language subroutine that accepts a string
+** of PIKCHR language text and generates a second string of SVG output that
+** renders the drawing defined by the input.  Space to hold the returned
+** string is obtained from malloc() and should be freed by the caller.
+** NULL might be returned if there is a memory allocation error.
+**
+** If there are errors in the PIKCHR input, the output will consist of an
+** error message and the original PIKCHR input text (inside of <pre>...</pre>).
+**
+** The subroutine implemented by this file is intended to be stand-alone.
+** It uses no external routines other than routines commonly found in
+** the standard C library.
+**
+****************************************************************************
+** COMPILING:
+**
+** The original source text is a mixture of C99 and "Lemon"
+** (See https://sqlite.org/src/file/doc/lemon.html).  Lemon is an LALR(1)
+** parser generator program, similar to Yacc.  The grammar of the
+** input language is specified in Lemon.  C-code is attached.  Lemon
+** runs to generate a single output file ("pikchr.c") which is then
+** compiled to generate the Pikchr library.  This header comment is
+** preserved in the Lemon output, so you might be reading this in either
+** the generated "pikchr.c" file that is output by Lemon, or in the
+** "pikchr.y" source file that is input into Lemon.  If you make changes,
+** you should change the input source file "pikchr.y", not the
+** Lemon-generated output file.
+**
+** Basic compilation steps:
+**
+**      lemon pikchr.y
+**      cc pikchr.c -o pikchr.o
+**
+** Add -DPIKCHR_SHELL to add a main() routine that reads input files
+** and sends them through Pikchr, for testing.  Add -DPIKCHR_FUZZ for
+** -fsanitizer=fuzzer testing.
+** 
+****************************************************************************
+** IMPLEMENTATION NOTES (for people who want to understand the internal
+** operation of this software, perhaps to extend the code or to fix bugs):
+**
+** Each call to pikchr() uses a single instance of the Pik structure to
+** track its internal state.  The Pik structure lives for the duration
+** of the pikchr() call.
+**
+** The input is a sequence of objects or "statements".  Each statement is
+** parsed into a PObj object.  These are stored on an extensible array
+** called PList.  All parameters to each PObj are computed as the
+** object is parsed.  (Hence, the parameters to a PObj may only refer
+** to prior statements.) Once the PObj is completely assembled, it is
+** added to the end of a PList and never changes thereafter - except,
+** PObj objects that are part of a "[...]" block might have their
+** absolute position shifted when the outer [...] block is positioned.
+** But apart from this repositioning, PObj objects are unchanged once
+** they are added to the list. The order of statements on a PList does
+** not change.
+**
+** After all input has been parsed, the top-level PList is walked to
+** generate output.  Sub-lists resulting from [...] blocks are scanned
+** as they are encountered.  All input must be collected and parsed ahead
+** of output generation because the size and position of statements must be
+** known in order to compute a bounding box on the output.
+**
+** Each PObj is on a "layer".  (The common case is that all PObj's are
+** on a single layer, but multiple layers are possible.)  A separate pass
+** is made through the list for each layer.
+**
+** After all output is generated, the Pik object and all the PList
+** and PObj objects are deallocated and the generated output string is
+** returned.  Upon any error, the Pik.nErr flag is set, processing quickly
+** stops, and the stack unwinds.  No attempt is made to continue reading
+** input after an error.
+**
+** Most statements begin with a class name like "box" or "arrow" or "move".
+** There is a class named "text" which is used for statements that begin
+** with a string literal.  You can also specify the "text" class.
+** A Sublist ("[...]") is a single object that contains a pointer to
+** its substatements, all gathered onto a separate PList object.
+**
+** Variables go into PVar objects that form a linked list.
+**
+** Each PObj has zero or one names.  Input constructs that attempt
+** to assign a new name from an older name, for example:
+**
+**      Abc:  Abc + (0.5cm, 0)
+**
+** Statements like these generate a new "noop" object at the specified
+** place and with the given name. As place-names are searched by scanning
+** the list in reverse order, this has the effect of overriding the "Abc"
+** name when referenced by subsequent objects.
+*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <assert.h>
+#define count(X) (sizeof(X)/sizeof(X[0]))
+#ifndef M_PI
+# define M_PI 3.1415926535897932385
+#endif
+
+/* Limit the number of tokens in a single script to avoid run-away
+** macro expansion attacks.  See forum post
+**    https://pikchr.org/home/forumpost/ef8684c6955a411a
+*/
+#ifndef PIKCHR_TOKEN_LIMIT
+# define PIKCHR_TOKEN_LIMIT 100000
+#endif
+
+
+/* Tag intentionally unused parameters with this macro to prevent
+** compiler warnings with -Wextra */
+#define UNUSED_PARAMETER(X)  (void)(X)
+
+typedef struct Pik Pik;          /* Complete parsing context */
+typedef struct PToken PToken;    /* A single token */
+typedef struct PObj PObj;        /* A single diagram object */
+typedef struct PList PList;      /* A list of diagram objects */
+typedef struct PClass PClass;    /* Description of statements types */
+typedef double PNum;             /* Numeric value */
+typedef struct PRel PRel;        /* Absolute or percentage value */
+typedef struct PPoint PPoint;    /* A position in 2-D space */
+typedef struct PVar PVar;        /* script-defined variable */
+typedef struct PBox PBox;        /* A bounding box */
+typedef struct PMacro PMacro;    /* A "define" macro */
+
+/* Compass points */
+#define CP_N      1
+#define CP_NE     2
+#define CP_E      3
+#define CP_SE     4
+#define CP_S      5
+#define CP_SW     6
+#define CP_W      7
+#define CP_NW     8
+#define CP_C      9   /* .center or .c */
+#define CP_END   10   /* .end */
+#define CP_START 11   /* .start */
+
+/* Heading angles corresponding to compass points */
+static const PNum pik_hdg_angle[] = {
+/* none  */   0.0,
+  /* N  */    0.0,
+  /* NE */   45.0,
+  /* E  */   90.0,
+  /* SE */  135.0,
+  /* S  */  180.0,
+  /* SW */  225.0,
+  /* W  */  270.0,
+  /* NW */  315.0,
+  /* C  */    0.0,
+};
+
+/* Built-in functions */
+#define FN_ABS    0
+#define FN_COS    1
+#define FN_INT    2
+#define FN_MAX    3
+#define FN_MIN    4
+#define FN_SIN    5
+#define FN_SQRT   6
+
+/* Text position and style flags.  Stored in PToken.eCode so limited
+** to 15 bits. */
+#define TP_LJUST   0x0001  /* left justify......          */
+#define TP_RJUST   0x0002  /*            ...Right justify */
+#define TP_JMASK   0x0003  /* Mask for justification bits */
+#define TP_ABOVE2  0x0004  /* Position text way above PObj.ptAt */
+#define TP_ABOVE   0x0008  /* Position text above PObj.ptAt */
+#define TP_CENTER  0x0010  /* On the line */
+#define TP_BELOW   0x0020  /* Position text below PObj.ptAt */
+#define TP_BELOW2  0x0040  /* Position text way below PObj.ptAt */
+#define TP_VMASK   0x007c  /* Mask for text positioning flags */
+#define TP_BIG     0x0100  /* Larger font */
+#define TP_SMALL   0x0200  /* Smaller font */
+#define TP_XTRA    0x0400  /* Amplify TP_BIG or TP_SMALL */
+#define TP_SZMASK  0x0700  /* Font size mask */
+#define TP_ITALIC  0x1000  /* Italic font */
+#define TP_BOLD    0x2000  /* Bold font */
+#define TP_FMASK   0x3000  /* Mask for font style */
+#define TP_ALIGN   0x4000  /* Rotate to align with the line */
+
+/* An object to hold a position in 2-D space */
+struct PPoint {
+  PNum x, y;             /* X and Y coordinates */
+};
+static const PPoint cZeroPoint = {0.0,0.0};
+
+/* A bounding box */
+struct PBox {
+  PPoint sw, ne;         /* Lower-left and top-right corners */
+};
+
+/* An Absolute or a relative distance.  The absolute distance
+** is stored in rAbs and the relative distance is stored in rRel.
+** Usually, one or the other will be 0.0.  When using a PRel to
+** update an existing value, the computation is usually something
+** like this:
+**
+**          value = PRel.rAbs + value*PRel.rRel
+**
+*/
+struct PRel {
+  PNum rAbs;            /* Absolute value */
+  PNum rRel;            /* Value relative to current value */
+};
+
+/* A variable created by the ID = EXPR construct of the PIKCHR script 
+**
+** PIKCHR (and PIC) scripts do not use many varaibles, so it is reasonable
+** to store them all on a linked list.
+*/
+struct PVar {
+  const char *zName;       /* Name of the variable */
+  PNum val;                /* Value of the variable */
+  PVar *pNext;             /* Next variable in a list of them all */
+};
+
+/* A single token in the parser input stream
+*/
+struct PToken {
+  const char *z;             /* Pointer to the token text */
+  unsigned int n;            /* Length of the token in bytes */
+  short int eCode;           /* Auxiliary code */
+  unsigned char eType;       /* The numeric parser code */
+  unsigned char eEdge;       /* Corner value for corner keywords */
+};
+
+/* Return negative, zero, or positive if pToken is less than, equal to
+** or greater than the zero-terminated string z[]
+*/
+static int pik_token_eq(PToken *pToken, const char *z){
+  int c = strncmp(pToken->z,z,pToken->n);
+  if( c==0 && z[pToken->n]!=0 ) c = -1;
+  return c;
+}
+
+/* Extra token types not generated by LEMON but needed by the
+** tokenizer
+*/
+#define T_PARAMETER  253     /* $1, $2, ..., $9 */
+#define T_WHITESPACE 254     /* Whitespace of comments */
+#define T_ERROR      255     /* Any text that is not a valid token */
+
+/* Directions of movement */
+#define DIR_RIGHT     0
+#define DIR_DOWN      1
+#define DIR_LEFT      2
+#define DIR_UP        3
+#define ValidDir(X)     ((X)>=0 && (X)<=3)
+#define IsUpDown(X)     (((X)&1)==1)
+#define IsLeftRight(X)  (((X)&1)==0)
+
+/* Bitmask for the various attributes for PObj.  These bits are
+** collected in PObj.mProp and PObj.mCalc to check for constraint
+** errors. */
+#define A_WIDTH         0x0001
+#define A_HEIGHT        0x0002
+#define A_RADIUS        0x0004
+#define A_THICKNESS     0x0008
+#define A_DASHED        0x0010 /* Includes "dotted" */
+#define A_FILL          0x0020
+#define A_COLOR         0x0040
+#define A_ARROW         0x0080
+#define A_FROM          0x0100
+#define A_CW            0x0200
+#define A_AT            0x0400
+#define A_TO            0x0800 /* one or more movement attributes */
+#define A_FIT           0x1000
+
+
+/* A single graphics object */
+struct PObj {
+  const PClass *type;      /* Object type or class */
+  PToken errTok;           /* Reference token for error messages */
+  PPoint ptAt;             /* Reference point for the object */
+  PPoint ptEnter, ptExit;  /* Entry and exit points */
+  PList *pSublist;         /* Substructure for [...] objects */
+  char *zName;             /* Name assigned to this statement */
+  PNum w;                  /* "width" property */
+  PNum h;                  /* "height" property */
+  PNum rad;                /* "radius" property */
+  PNum sw;                 /* "thickness" property. (Mnemonic: "stroke width")*/
+  PNum dotted;             /* "dotted" property.   <=0.0 for off */
+  PNum dashed;             /* "dashed" property.   <=0.0 for off */
+  PNum fill;               /* "fill" property.  Negative for off */
+  PNum color;              /* "color" property */
+  PPoint with;             /* Position constraint from WITH clause */
+  char eWith;              /* Type of heading point on WITH clause */
+  char cw;                 /* True for clockwise arc */
+  char larrow;             /* Arrow at beginning (<- or <->) */
+  char rarrow;             /* Arrow at end  (-> or <->) */
+  char bClose;             /* True if "close" is seen */
+  char bChop;              /* True if "chop" is seen */
+  unsigned char nTxt;      /* Number of text values */
+  unsigned mProp;          /* Masks of properties set so far */
+  unsigned mCalc;          /* Values computed from other constraints */
+  PToken aTxt[5];          /* Text with .eCode holding TP flags */
+  int iLayer;              /* Rendering order */
+  int inDir, outDir;       /* Entry and exit directions */
+  int nPath;               /* Number of path points */
+  PPoint *aPath;           /* Array of path points */
+  PObj *pFrom, *pTo;       /* End-point objects of a path */
+  PBox bbox;               /* Bounding box */
+};
+
+/* A list of graphics objects */
+struct PList {
+  int n;          /* Number of statements in the list */
+  int nAlloc;     /* Allocated slots in a[] */
+  PObj **a;       /* Pointers to individual objects */
+};
+
+/* A macro definition */
+struct PMacro {
+  PMacro *pNext;       /* Next in the list */
+  PToken macroName;    /* Name of the macro */
+  PToken macroBody;    /* Body of the macro */
+  int inUse;           /* Do not allow recursion */
+};
+
+/* Each call to the pikchr() subroutine uses an instance of the following
+** object to pass around context to all of its subroutines.
+*/
+struct Pik {
+  unsigned nErr;           /* Number of errors seen */
+  unsigned nToken;         /* Number of tokens parsed */
+  PToken sIn;              /* Input Pikchr-language text */
+  char *zOut;              /* Result accumulates here */
+  unsigned int nOut;       /* Bytes written to zOut[] so far */
+  unsigned int nOutAlloc;  /* Space allocated to zOut[] */
+  unsigned char eDir;      /* Current direction */
+  unsigned int mFlags;     /* Flags passed to pikchr() */
+  PObj *cur;               /* Object under construction */
+  PObj *lastRef;           /* Last object references by name */
+  PList *list;             /* Object list under construction */
+  PMacro *pMacros;         /* List of all defined macros */
+  PVar *pVar;              /* Application-defined variables */
+  PBox bbox;               /* Bounding box around all statements */
+                           /* Cache of layout values.  <=0.0 for unknown... */
+  PNum rScale;                 /* Multiply to convert inches to pixels */
+  PNum fontScale;              /* Scale fonts by this percent */
+  PNum charWidth;              /* Character width */
+  PNum charHeight;             /* Character height */
+  PNum wArrow;                 /* Width of arrowhead at the fat end */
+  PNum hArrow;                 /* Ht of arrowhead - dist from tip to fat end */
+  char bLayoutVars;            /* True if cache is valid */
+  char thenFlag;           /* True if "then" seen */
+  char samePath;           /* aTPath copied by "same" */
+  const char *zClass;      /* Class name for the <svg> */
+  int wSVG, hSVG;          /* Width and height of the <svg> */
+  int fgcolor;             /* foreground color value, or -1 for none */
+  int bgcolor;             /* background color value, or -1 for none */
+  /* Paths for lines are constructed here first, then transferred into
+  ** the PObj object at the end: */
+  int nTPath;              /* Number of entries on aTPath[] */
+  int mTPath;              /* For last entry, 1: x set,  2: y set */
+  PPoint aTPath[1000];     /* Path under construction */
+  /* Error contexts */
+  unsigned int nCtx;       /* Number of error contexts */
+  PToken aCtx[10];         /* Nested error contexts */
+};
+
+/* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd
+** argument to pikchr() in order to cause error message text to come out
+** as text/plain instead of as text/html
+*/
+#define PIKCHR_PLAINTEXT_ERRORS 0x0001
+
+/* Include PIKCHR_DARK_MODE among the mFlag bits to invert colors.
+*/
+#define PIKCHR_DARK_MODE        0x0002
+
+/*
+** The behavior of an object class is defined by an instance of
+** this structure. This is the "virtual method" table.
+*/
+struct PClass {
+  const char *zName;                     /* Name of class */
+  char isLine;                           /* True if a line class */
+  char eJust;                            /* Use box-style text justification */
+  void (*xInit)(Pik*,PObj*);              /* Initializer */
+  void (*xNumProp)(Pik*,PObj*,PToken*);   /* Value change notification */
+  void (*xCheck)(Pik*,PObj*);             /* Checks to do after parsing */
+  PPoint (*xChop)(Pik*,PObj*,PPoint*);    /* Chopper */
+  PPoint (*xOffset)(Pik*,PObj*,int);      /* Offset from .c to edge point */
+  void (*xFit)(Pik*,PObj*,PNum w,PNum h); /* Size to fit text */
+  void (*xRender)(Pik*,PObj*);            /* Render */
+};
+
+
+/* Forward declarations */
+static void pik_append(Pik*, const char*,int);
+static void pik_append_text(Pik*,const char*,int,int);
+static void pik_append_num(Pik*,const char*,PNum);
+static void pik_append_point(Pik*,const char*,PPoint*);
+static void pik_append_x(Pik*,const char*,PNum,const char*);
+static void pik_append_y(Pik*,const char*,PNum,const char*);
+static void pik_append_xy(Pik*,const char*,PNum,PNum);
+static void pik_append_dis(Pik*,const char*,PNum,const char*);
+static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum);
+static void pik_append_clr(Pik*,const char*,PNum,const char*,int);
+static void pik_append_style(Pik*,PObj*,int);
+static void pik_append_txt(Pik*,PObj*, PBox*);
+static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*);
+static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum);
+static void pik_error(Pik*,PToken*,const char*);
+static void pik_elist_free(Pik*,PList*);
+static void pik_elem_free(Pik*,PObj*);
+static void pik_render(Pik*,PList*);
+static PList *pik_elist_append(Pik*,PList*,PObj*);
+static PObj *pik_elem_new(Pik*,PToken*,PToken*,PList*);
+static void pik_set_direction(Pik*,int);
+static void pik_elem_setname(Pik*,PObj*,PToken*);
+static int pik_round(PNum);
+static void pik_set_var(Pik*,PToken*,PNum,PToken*);
+static PNum pik_value(Pik*,const char*,int,int*);
+static int pik_value_int(Pik*,const char*,int,int*);
+static PNum pik_lookup_color(Pik*,PToken*);
+static PNum pik_get_var(Pik*,PToken*);
+static PNum pik_atof(PToken*);
+static void pik_after_adding_attributes(Pik*,PObj*);
+static void pik_elem_move(PObj*,PNum dx, PNum dy);
+static void pik_elist_move(PList*,PNum dx, PNum dy);
+static void pik_set_numprop(Pik*,PToken*,PRel*);
+static void pik_set_clrprop(Pik*,PToken*,PNum);
+static void pik_set_dashed(Pik*,PToken*,PNum*);
+static void pik_then(Pik*,PToken*,PObj*);
+static void pik_add_direction(Pik*,PToken*,PRel*);
+static void pik_move_hdg(Pik*,PRel*,PToken*,PNum,PToken*,PToken*);
+static void pik_evenwith(Pik*,PToken*,PPoint*);
+static void pik_set_from(Pik*,PObj*,PToken*,PPoint*);
+static void pik_add_to(Pik*,PObj*,PToken*,PPoint*);
+static void pik_close_path(Pik*,PToken*);
+static void pik_set_at(Pik*,PToken*,PPoint*,PToken*);
+static short int pik_nth_value(Pik*,PToken*);
+static PObj *pik_find_nth(Pik*,PObj*,PToken*);
+static PObj *pik_find_byname(Pik*,PObj*,PToken*);
+static PPoint pik_place_of_elem(Pik*,PObj*,PToken*);
+static int pik_bbox_isempty(PBox*);
+static int pik_bbox_contains_point(PBox*,PPoint*);
+static void pik_bbox_init(PBox*);
+static void pik_bbox_addbox(PBox*,PBox*);
+static void pik_bbox_add_xy(PBox*,PNum,PNum);
+static void pik_bbox_addellipse(PBox*,PNum x,PNum y,PNum rx,PNum ry);
+static void pik_add_txt(Pik*,PToken*,int);
+static int pik_text_length(const PToken *pToken);
+static void pik_size_to_fit(Pik*,PToken*,int);
+static int pik_text_position(int,PToken*);
+static PNum pik_property_of(PObj*,PToken*);
+static PNum pik_func(Pik*,PToken*,PNum,PNum);
+static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2);
+static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt);
+static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt);
+static void pik_same(Pik *p, PObj*, PToken*);
+static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj);
+static PToken pik_next_semantic_token(PToken *pThis);
+static void pik_compute_layout_settings(Pik*);
+static void pik_behind(Pik*,PObj*);
+static PObj *pik_assert(Pik*,PNum,PToken*,PNum);
+static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*);
+static PNum pik_dist(PPoint*,PPoint*);
+static void pik_add_macro(Pik*,PToken *pId,PToken *pCode);
+
+
+} // end %include
+
+%name pik_parser
+%token_prefix T_
+%token_type {PToken}
+%extra_context {Pik *p}
+
+%fallback ID EDGEPT.
+
+// precedence rules.
+%left OF.
+%left PLUS MINUS.
+%left STAR SLASH PERCENT.
+%right UMINUS.
+
+%type statement_list {PList*}
+%destructor statement_list {pik_elist_free(p,$$);}
+%type statement {PObj*}
+%destructor statement {pik_elem_free(p,$$);}
+%type unnamed_statement {PObj*}
+%destructor unnamed_statement {pik_elem_free(p,$$);}
+%type basetype {PObj*}
+%destructor basetype {pik_elem_free(p,$$);}
+%type expr {PNum}
+%type numproperty {PToken}
+%type edge {PToken}
+%type direction {PToken}
+%type dashproperty {PToken}
+%type colorproperty {PToken}
+%type locproperty {PToken}
+%type position {PPoint}
+%type place {PPoint}
+%type object {PObj*}
+%type objectname {PObj*}
+%type nth {PToken}
+%type textposition {short int}
+%type rvalue {PNum}
+%type lvalue {PToken}
+%type even {PToken}
+%type relexpr {PRel}
+%type optrelexpr {PRel}
+
+%syntax_error {
+  if( TOKEN.z && TOKEN.z[0] ){
+    pik_error(p, &TOKEN, "syntax error");
+  }else{
+    pik_error(p, 0, "syntax error");
+  }
+  UNUSED_PARAMETER(yymajor);
+}
+%stack_overflow {
+  pik_error(p, 0, "parser stack overflow");
+}
+
+document ::= statement_list(X).  {pik_render(p,X);}
+
+
+statement_list(A) ::= statement(X).   { A = pik_elist_append(p,0,X); }
+statement_list(A) ::= statement_list(B) EOL statement(X).
+                      { A = pik_elist_append(p,B,X); }
+
+
+statement(A) ::= .   { A = 0; }
+statement(A) ::= direction(D).  { pik_set_direction(p,D.eCode);  A=0; }
+statement(A) ::= lvalue(N) ASSIGN(OP) rvalue(X). {pik_set_var(p,&N,X,&OP); A=0;}
+statement(A) ::= PLACENAME(N) COLON unnamed_statement(X).
+               { A = X;  pik_elem_setname(p,X,&N); }
+statement(A) ::= PLACENAME(N) COLON position(P).
+               { A = pik_elem_new(p,0,0,0);
+                 if(A){ A->ptAt = P; pik_elem_setname(p,A,&N); }}
+statement(A) ::= unnamed_statement(X).  {A = X;}
+statement(A) ::= print prlist.  {pik_append(p,"<br>\n",5); A=0;}
+
+// assert() statements are undocumented and are intended for testing and
+// debugging use only.  If the equality comparison of the assert() fails
+// then an error message is generated.
+statement(A) ::= ASSERT LP expr(X) EQ(OP) expr(Y) RP. {A=pik_assert(p,X,&OP,Y);}
+statement(A) ::= ASSERT LP position(X) EQ(OP) position(Y) RP.  
+                                          {A=pik_position_assert(p,&X,&OP,&Y);}
+statement(A) ::= DEFINE ID(ID) CODEBLOCK(C).  {A=0; pik_add_macro(p,&ID,&C);}
+
+lvalue(A) ::= ID(A).
+lvalue(A) ::= FILL(A).
+lvalue(A) ::= COLOR(A).
+lvalue(A) ::= THICKNESS(A).
+
+// PLACENAME might actually be a color name (ex: DarkBlue).  But we
+// cannot make it part of expr due to parsing ambiguities.  The
+// rvalue non-terminal means "general expression or a colorname"
+rvalue(A) ::= expr(A).
+rvalue(A) ::= PLACENAME(C).  {A = pik_lookup_color(p,&C);}
+
+print ::= PRINT.
+prlist ::= pritem.
+prlist ::= prlist prsep pritem.
+pritem ::= FILL(X).        {pik_append_num(p,"",pik_value(p,X.z,X.n,0));}
+pritem ::= COLOR(X).       {pik_append_num(p,"",pik_value(p,X.z,X.n,0));}
+pritem ::= THICKNESS(X).   {pik_append_num(p,"",pik_value(p,X.z,X.n,0));}
+pritem ::= rvalue(X).      {pik_append_num(p,"",X);}
+pritem ::= STRING(S). {pik_append_text(p,S.z+1,S.n-2,0);}
+prsep  ::= COMMA. {pik_append(p, " ", 1);}
+
+unnamed_statement(A) ::= basetype(X) attribute_list.  
+                          {A = X; pik_after_adding_attributes(p,A);}
+
+basetype(A) ::= CLASSNAME(N).            {A = pik_elem_new(p,&N,0,0); }
+basetype(A) ::= STRING(N) textposition(P).
+                            {N.eCode = P; A = pik_elem_new(p,0,&N,0); }
+basetype(A) ::= LB savelist(L) statement_list(X) RB(E).
+      { p->list = L; A = pik_elem_new(p,0,0,X); if(A) A->errTok = E; }
+
+%type savelist {PList*}
+// No destructor required as this same PList is also held by
+// an "statement" non-terminal deeper on the stack.
+savelist(A) ::= .   {A = p->list; p->list = 0;}
+
+direction(A) ::= UP(A).
+direction(A) ::= DOWN(A).
+direction(A) ::= LEFT(A).
+direction(A) ::= RIGHT(A).
+
+relexpr(A) ::= expr(B).             {A.rAbs = B; A.rRel = 0;}
+relexpr(A) ::= expr(B) PERCENT.     {A.rAbs = 0; A.rRel = B/100;}
+optrelexpr(A) ::= relexpr(A).
+optrelexpr(A) ::= .                 {A.rAbs = 0; A.rRel = 1.0;}
+
+attribute_list ::= relexpr(X) alist.    {pik_add_direction(p,0,&X);}
+attribute_list ::= alist.
+alist ::=.
+alist ::= alist attribute.
+attribute ::= numproperty(P) relexpr(X).     { pik_set_numprop(p,&P,&X); }
+attribute ::= dashproperty(P) expr(X).       { pik_set_dashed(p,&P,&X); }
+attribute ::= dashproperty(P).               { pik_set_dashed(p,&P,0);  }
+attribute ::= colorproperty(P) rvalue(X).    { pik_set_clrprop(p,&P,X); }
+attribute ::= go direction(D) optrelexpr(X). { pik_add_direction(p,&D,&X);}
+attribute ::= go direction(D) even position(P). {pik_evenwith(p,&D,&P);}
+attribute ::= CLOSE(E).             { pik_close_path(p,&E); }
+attribute ::= CHOP.                 { p->cur->bChop = 1; }
+attribute ::= FROM(T) position(X).  { pik_set_from(p,p->cur,&T,&X); }
+attribute ::= TO(T) position(X).    { pik_add_to(p,p->cur,&T,&X); }
+attribute ::= THEN(T).              { pik_then(p, &T, p->cur); }
+attribute ::= THEN(E) optrelexpr(D) HEADING(H) expr(A).
+                                                {pik_move_hdg(p,&D,&H,A,0,&E);}
+attribute ::= THEN(E) optrelexpr(D) EDGEPT(C).  {pik_move_hdg(p,&D,0,0,&C,&E);}
+attribute ::= GO(E) optrelexpr(D) HEADING(H) expr(A).
+                                                {pik_move_hdg(p,&D,&H,A,0,&E);}
+attribute ::= GO(E) optrelexpr(D) EDGEPT(C).    {pik_move_hdg(p,&D,0,0,&C,&E);}
+attribute ::= boolproperty.
+attribute ::= AT(A) position(P).                    { pik_set_at(p,0,&P,&A); }
+attribute ::= WITH withclause.
+attribute ::= SAME(E).                          {pik_same(p,0,&E);}
+attribute ::= SAME(E) AS object(X).             {pik_same(p,X,&E);}
+attribute ::= STRING(T) textposition(P).        {pik_add_txt(p,&T,P);}
+attribute ::= FIT(E).                           {pik_size_to_fit(p,&E,3); }
+attribute ::= BEHIND object(X).                 {pik_behind(p,X);}
+
+go ::= GO.
+go ::= .
+
+even ::= UNTIL EVEN WITH.
+even ::= EVEN WITH.
+
+withclause ::=  DOT_E edge(E) AT(A) position(P).{ pik_set_at(p,&E,&P,&A); }
+withclause ::=  edge(E) AT(A) position(P).      { pik_set_at(p,&E,&P,&A); }
+
+// Properties that require an argument
+numproperty(A) ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS(P).  {A = P;}
+
+// Properties with optional arguments
+dashproperty(A) ::= DOTTED(A).
+dashproperty(A) ::= DASHED(A).
+
+// Color properties
+colorproperty(A) ::= FILL(A).
+colorproperty(A) ::= COLOR(A).
+
+// Properties with no argument
+boolproperty ::= CW.          {p->cur->cw = 1;}
+boolproperty ::= CCW.         {p->cur->cw = 0;}
+boolproperty ::= LARROW.      {p->cur->larrow=1; p->cur->rarrow=0; }
+boolproperty ::= RARROW.      {p->cur->larrow=0; p->cur->rarrow=1; }
+boolproperty ::= LRARROW.     {p->cur->larrow=1; p->cur->rarrow=1; }
+boolproperty ::= INVIS.       {p->cur->sw = 0.0;}
+boolproperty ::= THICK.       {p->cur->sw *= 1.5;}
+boolproperty ::= THIN.        {p->cur->sw *= 0.67;}
+boolproperty ::= SOLID.       {p->cur->sw = pik_value(p,"thickness",9,0);
+                               p->cur->dotted = p->cur->dashed = 0.0;}
+
+textposition(A) ::= .   {A = 0;}
+textposition(A) ::= textposition(B) 
+   CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL(F).
+                        {A = (short int)pik_text_position(B,&F);}
+
+
+position(A) ::= expr(X) COMMA expr(Y).                {A.x=X; A.y=Y;}
+position(A) ::= place(A).
+position(A) ::= place(B) PLUS expr(X) COMMA expr(Y).  {A.x=B.x+X; A.y=B.y+Y;}
+position(A) ::= place(B) MINUS expr(X) COMMA expr(Y). {A.x=B.x-X; A.y=B.y-Y;}
+position(A) ::= place(B) PLUS LP expr(X) COMMA expr(Y) RP.
+                                                      {A.x=B.x+X; A.y=B.y+Y;}
+position(A) ::= place(B) MINUS LP expr(X) COMMA expr(Y) RP.
+                                                      {A.x=B.x-X; A.y=B.y-Y;}
+position(A) ::= LP position(X) COMMA position(Y) RP.  {A.x=X.x; A.y=Y.y;}
+position(A) ::= LP position(X) RP.                    {A=X;}
+position(A) ::= expr(X) between position(P1) AND position(P2).
+                                       {A = pik_position_between(X,P1,P2);}
+position(A) ::= expr(X) LT position(P1) COMMA position(P2) GT.
+                                       {A = pik_position_between(X,P1,P2);}
+position(A) ::= expr(X) ABOVE position(B).    {A=B; A.y += X;}
+position(A) ::= expr(X) BELOW position(B).    {A=B; A.y -= X;}
+position(A) ::= expr(X) LEFT OF position(B).  {A=B; A.x -= X;}
+position(A) ::= expr(X) RIGHT OF position(B). {A=B; A.x += X;}
+position(A) ::= expr(D) ON HEADING EDGEPT(E) OF position(P).
+                                        {A = pik_position_at_hdg(D,&E,P);}
+position(A) ::= expr(D) HEADING EDGEPT(E) OF position(P).
+                                        {A = pik_position_at_hdg(D,&E,P);}
+position(A) ::= expr(D) EDGEPT(E) OF position(P).
+                                        {A = pik_position_at_hdg(D,&E,P);}
+position(A) ::= expr(D) ON HEADING expr(G) FROM position(P).
+                                        {A = pik_position_at_angle(D,G,P);}
+position(A) ::= expr(D) HEADING expr(G) FROM position(P).
+                                        {A = pik_position_at_angle(D,G,P);}
+
+between ::= WAY BETWEEN.
+between ::= BETWEEN.
+between ::= OF THE WAY BETWEEN.
+
+// place2 is the same as place, but excludes the forms like
+// "RIGHT of object" to avoid a parsing ambiguity with "place .x"
+// and "place .y" expressions
+%type place2 {PPoint}
+
+place(A) ::= place2(A).
+place(A) ::= edge(X) OF object(O).           {A = pik_place_of_elem(p,O,&X);}
+place2(A) ::= object(O).                     {A = pik_place_of_elem(p,O,0);}
+place2(A) ::= object(O) DOT_E edge(X).       {A = pik_place_of_elem(p,O,&X);}
+place2(A) ::= NTH(N) VERTEX(E) OF object(X). {A = pik_nth_vertex(p,&N,&E,X);}
+
+edge(A) ::= CENTER(A).
+edge(A) ::= EDGEPT(A).
+edge(A) ::= TOP(A).
+edge(A) ::= BOTTOM(A).
+edge(A) ::= START(A).
+edge(A) ::= END(A).
+edge(A) ::= RIGHT(A).
+edge(A) ::= LEFT(A).
+
+object(A) ::= objectname(A).
+object(A) ::= nth(N).                     {A = pik_find_nth(p,0,&N);}
+object(A) ::= nth(N) OF|IN object(B).     {A = pik_find_nth(p,B,&N);}
+
+objectname(A) ::= THIS.                   {A = p->cur;}
+objectname(A) ::= PLACENAME(N).           {A = pik_find_byname(p,0,&N);}
+objectname(A) ::= objectname(B) DOT_U PLACENAME(N).
+                                          {A = pik_find_byname(p,B,&N);}
+
+nth(A) ::= NTH(N) CLASSNAME(ID).      {A=ID; A.eCode = pik_nth_value(p,&N); }
+nth(A) ::= NTH(N) LAST CLASSNAME(ID). {A=ID; A.eCode = -pik_nth_value(p,&N); }
+nth(A) ::= LAST CLASSNAME(ID).        {A=ID; A.eCode = -1;}
+nth(A) ::= LAST(ID).                  {A=ID; A.eCode = -1;}
+nth(A) ::= NTH(N) LB(ID) RB.          {A=ID; A.eCode = pik_nth_value(p,&N);}
+nth(A) ::= NTH(N) LAST LB(ID) RB.     {A=ID; A.eCode = -pik_nth_value(p,&N);}
+nth(A) ::= LAST LB(ID) RB.            {A=ID; A.eCode = -1; }
+
+expr(A) ::= expr(X) PLUS expr(Y).                 {A=X+Y;}
+expr(A) ::= expr(X) MINUS expr(Y).                {A=X-Y;}
+expr(A) ::= expr(X) STAR expr(Y).                 {A=X*Y;}
+expr(A) ::= expr(X) SLASH(E) expr(Y).             {
+  if( Y==0.0 ){ pik_error(p, &E, "division by zero"); A = 0.0; }
+  else{ A = X/Y; }
+}
+expr(A) ::= MINUS expr(X). [UMINUS]               {A=-X;}
+expr(A) ::= PLUS expr(X). [UMINUS]                {A=X;}
+expr(A) ::= LP expr(X) RP.                        {A=X;}
+expr(A) ::= LP FILL|COLOR|THICKNESS(X) RP.        {A=pik_get_var(p,&X);}
+expr(A) ::= NUMBER(N).                            {A=pik_atof(&N);}
+expr(A) ::= ID(N).                                {A=pik_get_var(p,&N);}
+expr(A) ::= FUNC1(F) LP expr(X) RP.               {A = pik_func(p,&F,X,0.0);}
+expr(A) ::= FUNC2(F) LP expr(X) COMMA expr(Y) RP. {A = pik_func(p,&F,X,Y);}
+expr(A) ::= DIST LP position(X) COMMA position(Y) RP. {A = pik_dist(&X,&Y);}
+expr(A) ::= place2(B) DOT_XY X.                   {A = B.x;}
+expr(A) ::= place2(B) DOT_XY Y.                   {A = B.y;}
+expr(A) ::= object(B) DOT_L numproperty(P).       {A=pik_property_of(B,&P);}
+expr(A) ::= object(B) DOT_L dashproperty(P).      {A=pik_property_of(B,&P);}
+expr(A) ::= object(B) DOT_L colorproperty(P).     {A=pik_property_of(B,&P);}
+
+
+%code {
+
+
+/* Chart of the 148 official CSS color names with their
+** corresponding RGB values thru Color Module Level 4:
+** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
+**
+** Two new names "None" and "Off" are added with a value
+** of -1.
+*/
+static const struct {
+  const char *zName;  /* Name of the color */
+  int val;            /* RGB value */
+} aColor[] = {
+  { "AliceBlue",                   0xf0f8ff },
+  { "AntiqueWhite",                0xfaebd7 },
+  { "Aqua",                        0x00ffff },
+  { "Aquamarine",                  0x7fffd4 },
+  { "Azure",                       0xf0ffff },
+  { "Beige",                       0xf5f5dc },
+  { "Bisque",                      0xffe4c4 },
+  { "Black",                       0x000000 },
+  { "BlanchedAlmond",              0xffebcd },
+  { "Blue",                        0x0000ff },
+  { "BlueViolet",                  0x8a2be2 },
+  { "Brown",                       0xa52a2a },
+  { "BurlyWood",                   0xdeb887 },
+  { "CadetBlue",                   0x5f9ea0 },
+  { "Chartreuse",                  0x7fff00 },
+  { "Chocolate",                   0xd2691e },
+  { "Coral",                       0xff7f50 },
+  { "CornflowerBlue",              0x6495ed },
+  { "Cornsilk",                    0xfff8dc },
+  { "Crimson",                     0xdc143c },
+  { "Cyan",                        0x00ffff },
+  { "DarkBlue",                    0x00008b },
+  { "DarkCyan",                    0x008b8b },
+  { "DarkGoldenrod",               0xb8860b },
+  { "DarkGray",                    0xa9a9a9 },
+  { "DarkGreen",                   0x006400 },
+  { "DarkGrey",                    0xa9a9a9 },
+  { "DarkKhaki",                   0xbdb76b },
+  { "DarkMagenta",                 0x8b008b },
+  { "DarkOliveGreen",              0x556b2f },
+  { "DarkOrange",                  0xff8c00 },
+  { "DarkOrchid",                  0x9932cc },
+  { "DarkRed",                     0x8b0000 },
+  { "DarkSalmon",                  0xe9967a },
+  { "DarkSeaGreen",                0x8fbc8f },
+  { "DarkSlateBlue",               0x483d8b },
+  { "DarkSlateGray",               0x2f4f4f },
+  { "DarkSlateGrey",               0x2f4f4f },
+  { "DarkTurquoise",               0x00ced1 },
+  { "DarkViolet",                  0x9400d3 },
+  { "DeepPink",                    0xff1493 },
+  { "DeepSkyBlue",                 0x00bfff },
+  { "DimGray",                     0x696969 },
+  { "DimGrey",                     0x696969 },
+  { "DodgerBlue",                  0x1e90ff },
+  { "Firebrick",                   0xb22222 },
+  { "FloralWhite",                 0xfffaf0 },
+  { "ForestGreen",                 0x228b22 },
+  { "Fuchsia",                     0xff00ff },
+  { "Gainsboro",                   0xdcdcdc },
+  { "GhostWhite",                  0xf8f8ff },
+  { "Gold",                        0xffd700 },
+  { "Goldenrod",                   0xdaa520 },
+  { "Gray",                        0x808080 },
+  { "Green",                       0x008000 },
+  { "GreenYellow",                 0xadff2f },
+  { "Grey",                        0x808080 },
+  { "Honeydew",                    0xf0fff0 },
+  { "HotPink",                     0xff69b4 },
+  { "IndianRed",                   0xcd5c5c },
+  { "Indigo",                      0x4b0082 },
+  { "Ivory",                       0xfffff0 },
+  { "Khaki",                       0xf0e68c },
+  { "Lavender",                    0xe6e6fa },
+  { "LavenderBlush",               0xfff0f5 },
+  { "LawnGreen",                   0x7cfc00 },
+  { "LemonChiffon",                0xfffacd },
+  { "LightBlue",                   0xadd8e6 },
+  { "LightCoral",                  0xf08080 },
+  { "LightCyan",                   0xe0ffff },
+  { "LightGoldenrodYellow",        0xfafad2 },
+  { "LightGray",                   0xd3d3d3 },
+  { "LightGreen",                  0x90ee90 },
+  { "LightGrey",                   0xd3d3d3 },
+  { "LightPink",                   0xffb6c1 },
+  { "LightSalmon",                 0xffa07a },
+  { "LightSeaGreen",               0x20b2aa },
+  { "LightSkyBlue",                0x87cefa },
+  { "LightSlateGray",              0x778899 },
+  { "LightSlateGrey",              0x778899 },
+  { "LightSteelBlue",              0xb0c4de },
+  { "LightYellow",                 0xffffe0 },
+  { "Lime",                        0x00ff00 },
+  { "LimeGreen",                   0x32cd32 },
+  { "Linen",                       0xfaf0e6 },
+  { "Magenta",                     0xff00ff },
+  { "Maroon",                      0x800000 },
+  { "MediumAquamarine",            0x66cdaa },
+  { "MediumBlue",                  0x0000cd },
+  { "MediumOrchid",                0xba55d3 },
+  { "MediumPurple",                0x9370db },
+  { "MediumSeaGreen",              0x3cb371 },
+  { "MediumSlateBlue",             0x7b68ee },
+  { "MediumSpringGreen",           0x00fa9a },
+  { "MediumTurquoise",             0x48d1cc },
+  { "MediumVioletRed",             0xc71585 },
+  { "MidnightBlue",                0x191970 },
+  { "MintCream",                   0xf5fffa },
+  { "MistyRose",                   0xffe4e1 },
+  { "Moccasin",                    0xffe4b5 },
+  { "NavajoWhite",                 0xffdead },
+  { "Navy",                        0x000080 },
+  { "None",                              -1 },  /* Non-standard addition */
+  { "Off",                               -1 },  /* Non-standard addition */
+  { "OldLace",                     0xfdf5e6 },
+  { "Olive",                       0x808000 },
+  { "OliveDrab",                   0x6b8e23 },
+  { "Orange",                      0xffa500 },
+  { "OrangeRed",                   0xff4500 },
+  { "Orchid",                      0xda70d6 },
+  { "PaleGoldenrod",               0xeee8aa },
+  { "PaleGreen",                   0x98fb98 },
+  { "PaleTurquoise",               0xafeeee },
+  { "PaleVioletRed",               0xdb7093 },
+  { "PapayaWhip",                  0xffefd5 },
+  { "PeachPuff",                   0xffdab9 },
+  { "Peru",                        0xcd853f },
+  { "Pink",                        0xffc0cb },
+  { "Plum",                        0xdda0dd },
+  { "PowderBlue",                  0xb0e0e6 },
+  { "Purple",                      0x800080 },
+  { "RebeccaPurple",               0x663399 },
+  { "Red",                         0xff0000 },
+  { "RosyBrown",                   0xbc8f8f },
+  { "RoyalBlue",                   0x4169e1 },
+  { "SaddleBrown",                 0x8b4513 },
+  { "Salmon",                      0xfa8072 },
+  { "SandyBrown",                  0xf4a460 },
+  { "SeaGreen",                    0x2e8b57 },
+  { "Seashell",                    0xfff5ee },
+  { "Sienna",                      0xa0522d },
+  { "Silver",                      0xc0c0c0 },
+  { "SkyBlue",                     0x87ceeb },
+  { "SlateBlue",                   0x6a5acd },
+  { "SlateGray",                   0x708090 },
+  { "SlateGrey",                   0x708090 },
+  { "Snow",                        0xfffafa },
+  { "SpringGreen",                 0x00ff7f },
+  { "SteelBlue",                   0x4682b4 },
+  { "Tan",                         0xd2b48c },
+  { "Teal",                        0x008080 },
+  { "Thistle",                     0xd8bfd8 },
+  { "Tomato",                      0xff6347 },
+  { "Turquoise",                   0x40e0d0 },
+  { "Violet",                      0xee82ee },
+  { "Wheat",                       0xf5deb3 },
+  { "White",                       0xffffff },
+  { "WhiteSmoke",                  0xf5f5f5 },
+  { "Yellow",                      0xffff00 },
+  { "YellowGreen",                 0x9acd32 },
+};
+
+/* Built-in variable names.
+**
+** This array is constant.  When a script changes the value of one of
+** these built-ins, a new PVar record is added at the head of
+** the Pik.pVar list, which is searched first.  Thus the new PVar entry
+** will override this default value.
+**
+** Units are in inches, except for "color" and "fill" which are 
+** interpreted as 24-bit RGB values.
+**
+** Binary search used.  Must be kept in sorted order.
+*/
+static const struct { const char *zName; PNum val; } aBuiltin[] = {
+  { "arcrad",      0.25  },
+  { "arrowhead",   2.0   },
+  { "arrowht",     0.08  },
+  { "arrowwid",    0.06  },
+  { "boxht",       0.5   },
+  { "boxrad",      0.0   },
+  { "boxwid",      0.75  },
+  { "charht",      0.14  },
+  { "charwid",     0.08  },
+  { "circlerad",   0.25  },
+  { "color",       0.0   },
+  { "cylht",       0.5   },
+  { "cylrad",      0.075 },
+  { "cylwid",      0.75  },
+  { "dashwid",     0.05  },
+  { "dotrad",      0.015 },
+  { "ellipseht",   0.5   },
+  { "ellipsewid",  0.75  },
+  { "fileht",      0.75  },
+  { "filerad",     0.15  },
+  { "filewid",     0.5   },
+  { "fill",        -1.0  },
+  { "lineht",      0.5   },
+  { "linewid",     0.5   },
+  { "movewid",     0.5   },
+  { "ovalht",      0.5   },
+  { "ovalwid",     1.0   },
+  { "scale",       1.0   },
+  { "textht",      0.5   },
+  { "textwid",     0.75  },
+  { "thickness",   0.015 },
+};
+
+
+/* Methods for the "arc" class */
+static void arcInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "arcrad",6,0);
+  pObj->h = pObj->w;
+}
+/* Hack: Arcs are here rendered as quadratic Bezier curves rather
+** than true arcs.  Multiple reasons: (1) the legacy-PIC parameters
+** that control arcs are obscure and I could not figure out what they
+** mean based on available documentation.  (2) Arcs are rarely used,
+** and so do not seem that important.
+*/
+static PPoint arcControlPoint(int cw, PPoint f, PPoint t, PNum rScale){
+  PPoint m;
+  PNum dx, dy;
+  m.x = 0.5*(f.x+t.x);
+  m.y = 0.5*(f.y+t.y);
+  dx = t.x - f.x;
+  dy = t.y - f.y;
+  if( cw ){
+    m.x -= 0.5*rScale*dy;
+    m.y += 0.5*rScale*dx;
+  }else{
+    m.x += 0.5*rScale*dy;
+    m.y -= 0.5*rScale*dx;
+  }
+  return m;
+}
+static void arcCheck(Pik *p, PObj *pObj){
+  PPoint m;
+  if( p->nTPath>2 ){
+    pik_error(p, &pObj->errTok, "arc geometry error");
+    return;
+  }
+  m = arcControlPoint(pObj->cw, p->aTPath[0], p->aTPath[1], 0.5);
+  pik_bbox_add_xy(&pObj->bbox, m.x, m.y);
+}
+static void arcRender(Pik *p, PObj *pObj){
+  PPoint f, m, t;
+  if( pObj->nPath<2 ) return;
+  if( pObj->sw<=0.0 ) return;
+  f = pObj->aPath[0];
+  t = pObj->aPath[1];
+  m = arcControlPoint(pObj->cw,f,t,1.0);
+  if( pObj->larrow ){
+    pik_draw_arrowhead(p,&m,&f,pObj);
+  }
+  if( pObj->rarrow ){
+    pik_draw_arrowhead(p,&m,&t,pObj);
+  }
+  pik_append_xy(p,"<path d=\"M", f.x, f.y);
+  pik_append_xy(p,"Q", m.x, m.y);
+  pik_append_xy(p," ", t.x, t.y);
+  pik_append(p,"\" ",2);
+  pik_append_style(p,pObj,0);
+  pik_append(p,"\" />\n", -1);
+
+  pik_append_txt(p, pObj, 0);
+}
+
+
+/* Methods for the "arrow" class */
+static void arrowInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "linewid",7,0);
+  pObj->h = pik_value(p, "lineht",6,0);
+  pObj->rad = pik_value(p, "linerad",7,0);
+  pObj->rarrow = 1;
+}
+
+/* Methods for the "box" class */
+static void boxInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "boxwid",6,0);
+  pObj->h = pik_value(p, "boxht",5,0);
+  pObj->rad = pik_value(p, "boxrad",6,0);
+}
+/* Return offset from the center of the box to the compass point 
+** given by parameter cp */
+static PPoint boxOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PNum rx;
+  if( rad<=0.0 ){
+    rx = 0.0;
+  }else{
+    if( rad>w2 ) rad = w2;
+    if( rad>h2 ) rad = h2;
+    rx = 0.29289321881345252392*rad;
+  }
+  switch( cp ){
+    case CP_C:                                   break;
+    case CP_N:   pt.x = 0.0;      pt.y = h2;     break;
+    case CP_NE:  pt.x = w2-rx;    pt.y = h2-rx;  break;
+    case CP_E:   pt.x = w2;       pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2-rx;    pt.y = rx-h2;  break;
+    case CP_S:   pt.x = 0.0;      pt.y = -h2;    break;
+    case CP_SW:  pt.x = rx-w2;    pt.y = rx-h2;  break;
+    case CP_W:   pt.x = -w2;      pt.y = 0.0;    break;
+    case CP_NW:  pt.x = rx-w2;    pt.y = h2-rx;  break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+static PPoint boxChop(Pik *p, PObj *pObj, PPoint *pPt){
+  PNum dx, dy;
+  int cp = CP_C;
+  PPoint chop = pObj->ptAt;
+  if( pObj->w<=0.0 ) return chop;
+  if( pObj->h<=0.0 ) return chop;
+  dx = (pPt->x - pObj->ptAt.x)*pObj->h/pObj->w;
+  dy = (pPt->y - pObj->ptAt.y);
+  if( dx>0.0 ){
+    if( dy>=2.414*dx ){
+      cp = CP_N;
+    }else if( dy>=0.414*dx ){
+      cp = CP_NE;
+    }else if( dy>=-0.414*dx ){
+      cp = CP_E;
+    }else if( dy>-2.414*dx ){
+      cp = CP_SE;
+    }else{
+      cp = CP_S;
+    }
+  }else{
+    if( dy>=-2.414*dx ){
+      cp = CP_N;
+    }else if( dy>=-0.414*dx ){
+      cp = CP_NW;
+    }else if( dy>=0.414*dx ){
+      cp = CP_W;
+    }else if( dy>2.414*dx ){
+      cp = CP_SW;
+    }else{
+      cp = CP_S;
+    }
+  }
+  chop = pObj->type->xOffset(p,pObj,cp);
+  chop.x += pObj->ptAt.x;
+  chop.y += pObj->ptAt.y;
+  return chop;
+}
+static void boxFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h;
+  UNUSED_PARAMETER(p);
+}
+static void boxRender(Pik *p, PObj *pObj){
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    if( rad<=0.0 ){
+      pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y-h2);
+      pik_append_xy(p,"L", pt.x+w2,pt.y-h2);
+      pik_append_xy(p,"L", pt.x+w2,pt.y+h2);
+      pik_append_xy(p,"L", pt.x-w2,pt.y+h2);
+      pik_append(p,"Z\" ",-1);
+    }else{
+      /*
+      **         ----       - y3
+      **        /    \
+      **       /      \     _ y2
+      **      |        |    
+      **      |        |    _ y1
+      **       \      /
+      **        \    /
+      **         ----       _ y0
+      **
+      **      '  '  '  '
+      **     x0 x1 x2 x3
+      */
+      PNum x0,x1,x2,x3,y0,y1,y2,y3;
+      if( rad>w2 ) rad = w2;
+      if( rad>h2 ) rad = h2;
+      x0 = pt.x - w2;
+      x1 = x0 + rad;
+      x3 = pt.x + w2;
+      x2 = x3 - rad;
+      y0 = pt.y - h2;
+      y1 = y0 + rad;
+      y3 = pt.y + h2;
+      y2 = y3 - rad;
+      pik_append_xy(p,"<path d=\"M", x1, y0);
+      if( x2>x1 ) pik_append_xy(p, "L", x2, y0);
+      pik_append_arc(p, rad, rad, x3, y1);
+      if( y2>y1 ) pik_append_xy(p, "L", x3, y2);
+      pik_append_arc(p, rad, rad, x2, y3);
+      if( x2>x1 ) pik_append_xy(p, "L", x1, y3);
+      pik_append_arc(p, rad, rad, x0, y2);
+      if( y2>y1 ) pik_append_xy(p, "L", x0, y1);
+      pik_append_arc(p, rad, rad, x1, y0);
+      pik_append(p,"Z\" ",-1);
+    }
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "circle" class */
+static void circleInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "circlerad",9,0)*2;
+  pObj->h = pObj->w;
+  pObj->rad = 0.5*pObj->w;
+}
+static void circleNumProp(Pik *p, PObj *pObj, PToken *pId){
+  /* For a circle, the width must equal the height and both must
+  ** be twice the radius.  Enforce those constraints. */
+  switch( pId->eType ){
+    case T_DIAMETER:
+    case T_RADIUS:
+      pObj->w = pObj->h = 2.0*pObj->rad;
+      break;
+    case T_WIDTH:
+      pObj->h = pObj->w;
+      pObj->rad = 0.5*pObj->w;
+      break;
+    case T_HEIGHT:
+      pObj->w = pObj->h;
+      pObj->rad = 0.5*pObj->w;
+      break;
+  }
+  UNUSED_PARAMETER(p);
+}
+static PPoint circleChop(Pik *p, PObj *pObj, PPoint *pPt){
+  PPoint chop;
+  PNum dx = pPt->x - pObj->ptAt.x;
+  PNum dy = pPt->y - pObj->ptAt.y;
+  PNum dist = hypot(dx,dy);
+  if( dist<pObj->rad || dist<=0 ) return pObj->ptAt;
+  chop.x = pObj->ptAt.x + dx*pObj->rad/dist;
+  chop.y = pObj->ptAt.y + dy*pObj->rad/dist;
+  UNUSED_PARAMETER(p);
+  return chop;
+}
+static void circleFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  PNum mx = 0.0;
+  if( w>0 ) mx = w;
+  if( h>mx ) mx = h;
+  if( w*h>0 && (w*w + h*h) > mx*mx ){
+    mx = hypot(w,h);
+  }
+  if( mx>0.0 ){
+    pObj->rad = 0.5*mx;
+    pObj->w = pObj->h = mx;
+  }
+  UNUSED_PARAMETER(p);
+}
+
+static void circleRender(Pik *p, PObj *pObj){
+  PNum r = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    pik_append_x(p,"<circle cx=\"", pt.x, "\"");
+    pik_append_y(p," cy=\"", pt.y, "\"");
+    pik_append_dis(p," r=\"", r, "\" ");
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "cylinder" class */
+static void cylinderInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "cylwid",6,0);
+  pObj->h = pik_value(p, "cylht",5,0);
+  pObj->rad = pik_value(p, "cylrad",6,0); /* Minor radius of ellipses */
+}
+static void cylinderFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h + 0.25*pObj->rad + pObj->sw;
+  UNUSED_PARAMETER(p);
+}
+static void cylinderRender(Pik *p, PObj *pObj){
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    if( rad>h2 ){
+      rad = h2;
+    }else if( rad<0 ){
+      rad = 0;
+    }
+    pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y+h2-rad);
+    pik_append_xy(p,"L", pt.x-w2,pt.y-h2+rad);
+    pik_append_arc(p,w2,rad,pt.x+w2,pt.y-h2+rad);
+    pik_append_xy(p,"L", pt.x+w2,pt.y+h2-rad);
+    pik_append_arc(p,w2,rad,pt.x-w2,pt.y+h2-rad);
+    pik_append_arc(p,w2,rad,pt.x+w2,pt.y+h2-rad);
+    pik_append(p,"\" ",-1);
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+static PPoint cylinderOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w2 = pObj->w*0.5;
+  PNum h1 = pObj->h*0.5;
+  PNum h2 = h1 - pObj->rad;
+  switch( cp ){
+    case CP_C:                                break;
+    case CP_N:   pt.x = 0.0;   pt.y = h1;     break;
+    case CP_NE:  pt.x = w2;    pt.y = h2;     break;
+    case CP_E:   pt.x = w2;    pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2;    pt.y = -h2;    break;
+    case CP_S:   pt.x = 0.0;   pt.y = -h1;    break;
+    case CP_SW:  pt.x = -w2;   pt.y = -h2;    break;
+    case CP_W:   pt.x = -w2;   pt.y = 0.0;    break;
+    case CP_NW:  pt.x = -w2;   pt.y = h2;     break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+
+/* Methods for the "dot" class */
+static void dotInit(Pik *p, PObj *pObj){
+  pObj->rad = pik_value(p, "dotrad",6,0);
+  pObj->h = pObj->w = pObj->rad*6;
+  pObj->fill = pObj->color;
+}
+static void dotNumProp(Pik *p, PObj *pObj, PToken *pId){
+  switch( pId->eType ){
+    case T_COLOR:
+      pObj->fill = pObj->color;
+      break;
+    case T_FILL:
+      pObj->color = pObj->fill;
+      break;
+  }
+  UNUSED_PARAMETER(p);
+}
+static void dotCheck(Pik *p, PObj *pObj){
+  pObj->w = pObj->h = 0;
+  pik_bbox_addellipse(&pObj->bbox, pObj->ptAt.x, pObj->ptAt.y,
+                       pObj->rad, pObj->rad);
+  UNUSED_PARAMETER(p);
+}
+static PPoint dotOffset(Pik *p, PObj *pObj, int cp){
+  UNUSED_PARAMETER(p);
+  UNUSED_PARAMETER(pObj);
+  UNUSED_PARAMETER(cp);
+  return cZeroPoint;
+}
+static void dotRender(Pik *p, PObj *pObj){
+  PNum r = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    pik_append_x(p,"<circle cx=\"", pt.x, "\"");
+    pik_append_y(p," cy=\"", pt.y, "\"");
+    pik_append_dis(p," r=\"", r, "\"");
+    pik_append_style(p,pObj,2);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+
+
+/* Methods for the "ellipse" class */
+static void ellipseInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "ellipsewid",10,0);
+  pObj->h = pik_value(p, "ellipseht",9,0);
+}
+static PPoint ellipseChop(Pik *p, PObj *pObj, PPoint *pPt){
+  PPoint chop;
+  PNum s, dq, dist;
+  PNum dx = pPt->x - pObj->ptAt.x;
+  PNum dy = pPt->y - pObj->ptAt.y;
+  if( pObj->w<=0.0 ) return pObj->ptAt;
+  if( pObj->h<=0.0 ) return pObj->ptAt;
+  s = pObj->h/pObj->w;
+  dq = dx*s;
+  dist = hypot(dq,dy);
+  if( dist<pObj->h ) return pObj->ptAt;
+  chop.x = pObj->ptAt.x + 0.5*dq*pObj->h/(dist*s);
+  chop.y = pObj->ptAt.y + 0.5*dy*pObj->h/dist;
+  UNUSED_PARAMETER(p);
+  return chop;
+}
+static PPoint ellipseOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w = pObj->w*0.5;
+  PNum w2 = w*0.70710678118654747608;
+  PNum h = pObj->h*0.5;
+  PNum h2 = h*0.70710678118654747608;
+  switch( cp ){
+    case CP_C:                                break;
+    case CP_N:   pt.x = 0.0;   pt.y = h;      break;
+    case CP_NE:  pt.x = w2;    pt.y = h2;     break;
+    case CP_E:   pt.x = w;     pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2;    pt.y = -h2;    break;
+    case CP_S:   pt.x = 0.0;   pt.y = -h;     break;
+    case CP_SW:  pt.x = -w2;   pt.y = -h2;    break;
+    case CP_W:   pt.x = -w;    pt.y = 0.0;    break;
+    case CP_NW:  pt.x = -w2;   pt.y = h2;     break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+static void ellipseRender(Pik *p, PObj *pObj){
+  PNum w = pObj->w;
+  PNum h = pObj->h;
+  PPoint pt = pObj->ptAt;
+  if( pObj->sw>0.0 ){
+    pik_append_x(p,"<ellipse cx=\"", pt.x, "\"");
+    pik_append_y(p," cy=\"", pt.y, "\"");
+    pik_append_dis(p," rx=\"", w/2.0, "\"");
+    pik_append_dis(p," ry=\"", h/2.0, "\" ");
+    pik_append_style(p,pObj,3);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "file" object */
+static void fileInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "filewid",7,0);
+  pObj->h = pik_value(p, "fileht",6,0);
+  pObj->rad = pik_value(p, "filerad",7,0);
+}
+/* Return offset from the center of the file to the compass point 
+** given by parameter cp */
+static PPoint fileOffset(Pik *p, PObj *pObj, int cp){
+  PPoint pt = cZeroPoint;
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rx = pObj->rad;
+  PNum mn = w2<h2 ? w2 : h2;
+  if( rx>mn ) rx = mn;
+  if( rx<mn*0.25 ) rx = mn*0.25;
+  pt.x = pt.y = 0.0;
+  rx *= 0.5;
+  switch( cp ){
+    case CP_C:                                   break;
+    case CP_N:   pt.x = 0.0;      pt.y = h2;     break;
+    case CP_NE:  pt.x = w2-rx;    pt.y = h2-rx;  break;
+    case CP_E:   pt.x = w2;       pt.y = 0.0;    break;
+    case CP_SE:  pt.x = w2;       pt.y = -h2;    break;
+    case CP_S:   pt.x = 0.0;      pt.y = -h2;    break;
+    case CP_SW:  pt.x = -w2;      pt.y = -h2;    break;
+    case CP_W:   pt.x = -w2;      pt.y = 0.0;    break;
+    case CP_NW:  pt.x = -w2;      pt.y = h2;     break;
+    default:     assert(0);
+  }
+  UNUSED_PARAMETER(p);
+  return pt;
+}
+static void fileFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h + 2*pObj->rad;
+  UNUSED_PARAMETER(p);
+}
+static void fileRender(Pik *p, PObj *pObj){
+  PNum w2 = 0.5*pObj->w;
+  PNum h2 = 0.5*pObj->h;
+  PNum rad = pObj->rad;
+  PPoint pt = pObj->ptAt;
+  PNum mn = w2<h2 ? w2 : h2;
+  if( rad>mn ) rad = mn;
+  if( rad<mn*0.25 ) rad = mn*0.25;
+  if( pObj->sw>0.0 ){
+    pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y-h2);
+    pik_append_xy(p,"L", pt.x+w2,pt.y-h2);
+    pik_append_xy(p,"L", pt.x+w2,pt.y+(h2-rad));
+    pik_append_xy(p,"L", pt.x+(w2-rad),pt.y+h2);
+    pik_append_xy(p,"L", pt.x-w2,pt.y+h2);
+    pik_append(p,"Z\" ",-1);
+    pik_append_style(p,pObj,1);
+    pik_append(p,"\" />\n",-1);
+    pik_append_xy(p,"<path d=\"M", pt.x+(w2-rad), pt.y+h2);
+    pik_append_xy(p,"L", pt.x+(w2-rad),pt.y+(h2-rad));
+    pik_append_xy(p,"L", pt.x+w2, pt.y+(h2-rad));
+    pik_append(p,"\" ",-1);
+    pik_append_style(p,pObj,0);
+    pik_append(p,"\" />\n",-1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+
+/* Methods for the "line" class */
+static void lineInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "linewid",7,0);
+  pObj->h = pik_value(p, "lineht",6,0);
+  pObj->rad = pik_value(p, "linerad",7,0);
+}
+static PPoint lineOffset(Pik *p, PObj *pObj, int cp){
+#if 0
+  /* In legacy PIC, the .center of an unclosed line is half way between
+  ** its .start and .end. */
+  if( cp==CP_C && !pObj->bClose ){
+    PPoint out;
+    out.x = 0.5*(pObj->ptEnter.x + pObj->ptExit.x) - pObj->ptAt.x;
+    out.y = 0.5*(pObj->ptEnter.x + pObj->ptExit.y) - pObj->ptAt.y;
+    return out;
+  }
+#endif
+  return boxOffset(p,pObj,cp);
+}
+static void lineRender(Pik *p, PObj *pObj){
+  int i;
+  if( pObj->sw>0.0 ){
+    const char *z = "<path d=\"M";
+    int n = pObj->nPath;
+    if( pObj->larrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj);
+    }
+    if( pObj->rarrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj);
+    }
+    for(i=0; i<pObj->nPath; i++){
+      pik_append_xy(p,z,pObj->aPath[i].x,pObj->aPath[i].y);
+      z = "L";
+    }
+    if( pObj->bClose ){
+      pik_append(p,"Z",1);
+    }else{
+      pObj->fill = -1.0;
+    }
+    pik_append(p,"\" ",-1);
+    pik_append_style(p,pObj,pObj->bClose?3:0);
+    pik_append(p,"\" />\n", -1);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+/* Methods for the "move" class */
+static void moveInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "movewid",7,0);
+  pObj->h = pObj->w;
+  pObj->fill = -1.0;
+  pObj->color = -1.0;
+  pObj->sw = -1.0;
+}
+static void moveRender(Pik *p, PObj *pObj){
+  /* No-op */
+  UNUSED_PARAMETER(p);
+  UNUSED_PARAMETER(pObj);
+}
+
+/* Methods for the "oval" class */
+static void ovalInit(Pik *p, PObj *pObj){
+  pObj->h = pik_value(p, "ovalht",6,0);
+  pObj->w = pik_value(p, "ovalwid",7,0);
+  pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
+}
+static void ovalNumProp(Pik *p, PObj *pObj, PToken *pId){
+  UNUSED_PARAMETER(p);
+  UNUSED_PARAMETER(pId);
+  /* Always adjust the radius to be half of the smaller of
+  ** the width and height. */
+  pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
+}
+static void ovalFit(Pik *p, PObj *pObj, PNum w, PNum h){
+  UNUSED_PARAMETER(p);
+  if( w>0 ) pObj->w = w;
+  if( h>0 ) pObj->h = h;
+  if( pObj->w<pObj->h ) pObj->w = pObj->h;
+  pObj->rad = 0.5*(pObj->h<pObj->w?pObj->h:pObj->w);
+}
+
+
+
+/* Methods for the "spline" class */
+static void splineInit(Pik *p, PObj *pObj){
+  pObj->w = pik_value(p, "linewid",7,0);
+  pObj->h = pik_value(p, "lineht",6,0);
+  pObj->rad = 1000;
+}
+/* Return a point along the path from "f" to "t" that is r units
+** prior to reaching "t", except if the path is less than 2*r total,
+** return the midpoint.
+*/
+static PPoint radiusMidpoint(PPoint f, PPoint t, PNum r, int *pbMid){
+  PNum dx = t.x - f.x;
+  PNum dy = t.y - f.y;
+  PNum dist = hypot(dx,dy);
+  PPoint m;
+  if( dist<=0.0 ) return t;
+  dx /= dist;
+  dy /= dist;
+  if( r > 0.5*dist ){
+    r = 0.5*dist;
+    *pbMid = 1;
+  }else{
+    *pbMid = 0;
+  }
+  m.x = t.x - r*dx;
+  m.y = t.y - r*dy;
+  return m;
+}
+static void radiusPath(Pik *p, PObj *pObj, PNum r){
+  int i;
+  int n = pObj->nPath;
+  const PPoint *a = pObj->aPath;
+  PPoint m;
+  PPoint an = a[n-1];
+  int isMid = 0;
+  int iLast = pObj->bClose ? n : n-1;
+
+  pik_append_xy(p,"<path d=\"M", a[0].x, a[0].y);
+  m = radiusMidpoint(a[0], a[1], r, &isMid);
+  pik_append_xy(p," L ",m.x,m.y);
+  for(i=1; i<iLast; i++){
+    an = i<n-1 ? a[i+1] : a[0];
+    m = radiusMidpoint(an,a[i],r, &isMid);
+    pik_append_xy(p," Q ",a[i].x,a[i].y);
+    pik_append_xy(p," ",m.x,m.y);
+    if( !isMid ){
+      m = radiusMidpoint(a[i],an,r, &isMid);
+      pik_append_xy(p," L ",m.x,m.y);
+    }
+  }
+  pik_append_xy(p," L ",an.x,an.y);
+  if( pObj->bClose ){
+    pik_append(p,"Z",1);
+  }else{
+    pObj->fill = -1.0;
+  }
+  pik_append(p,"\" ",-1);
+  pik_append_style(p,pObj,pObj->bClose?3:0);
+  pik_append(p,"\" />\n", -1);
+}
+static void splineRender(Pik *p, PObj *pObj){
+  if( pObj->sw>0.0 ){
+    int n = pObj->nPath;
+    PNum r = pObj->rad;
+    if( n<3 || r<=0.0 ){
+      lineRender(p,pObj);
+      return;
+    }
+    if( pObj->larrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj);
+    }
+    if( pObj->rarrow ){
+      pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj);
+    }
+    radiusPath(p,pObj,pObj->rad);
+  }
+  pik_append_txt(p, pObj, 0);
+}
+
+
+/* Methods for the "text" class */
+static void textInit(Pik *p, PObj *pObj){
+  pik_value(p, "textwid",7,0);
+  pik_value(p, "textht",6,0);
+  pObj->sw = 0.0;
+}
+static PPoint textOffset(Pik *p, PObj *pObj, int cp){
+  /* Automatically slim-down the width and height of text
+  ** statements so that the bounding box tightly encloses the text,
+  ** then get boxOffset() to do the offset computation.
+  */
+  pik_size_to_fit(p, &pObj->errTok,3);
+  return boxOffset(p, pObj, cp);
+}
+
+/* Methods for the "sublist" class */
+static void sublistInit(Pik *p, PObj *pObj){
+  PList *pList = pObj->pSublist;
+  int i;
+  UNUSED_PARAMETER(p);
+  pik_bbox_init(&pObj->bbox);
+  for(i=0; i<pList->n; i++){
+    pik_bbox_addbox(&pObj->bbox, &pList->a[i]->bbox);
+  }
+  pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x;
+  pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y;
+  pObj->ptAt.x = 0.5*(pObj->bbox.ne.x + pObj->bbox.sw.x);
+  pObj->ptAt.y = 0.5*(pObj->bbox.ne.y + pObj->bbox.sw.y);
+  pObj->mCalc |= A_WIDTH|A_HEIGHT|A_RADIUS;
+}
+
+
+/*
+** The following array holds all the different kinds of objects.
+** The special [] object is separate.
+*/
+static const PClass aClass[] = {
+   {  /* name */          "arc",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         arcInit,
+      /* xNumProp */      0,
+      /* xCheck */        arcCheck,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       arcRender
+   },
+   {  /* name */          "arrow",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         arrowInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       lineOffset,
+      /* xFit */          0,
+      /* xRender */       splineRender 
+   },
+   {  /* name */          "box",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         boxInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       boxOffset,
+      /* xFit */          boxFit,
+      /* xRender */       boxRender 
+   },
+   {  /* name */          "circle",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         circleInit,
+      /* xNumProp */      circleNumProp,
+      /* xCheck */        0,
+      /* xChop */         circleChop,
+      /* xOffset */       ellipseOffset,
+      /* xFit */          circleFit,
+      /* xRender */       circleRender 
+   },
+   {  /* name */          "cylinder",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         cylinderInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       cylinderOffset,
+      /* xFit */          cylinderFit,
+      /* xRender */       cylinderRender
+   },
+   {  /* name */          "dot",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         dotInit,
+      /* xNumProp */      dotNumProp,
+      /* xCheck */        dotCheck,
+      /* xChop */         circleChop,
+      /* xOffset */       dotOffset,
+      /* xFit */          0,
+      /* xRender */       dotRender 
+   },
+   {  /* name */          "ellipse",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         ellipseInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         ellipseChop,
+      /* xOffset */       ellipseOffset,
+      /* xFit */          boxFit,
+      /* xRender */       ellipseRender
+   },
+   {  /* name */          "file",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         fileInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       fileOffset,
+      /* xFit */          fileFit,
+      /* xRender */       fileRender 
+   },
+   {  /* name */          "line",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         lineInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       lineOffset,
+      /* xFit */          0,
+      /* xRender */       splineRender
+   },
+   {  /* name */          "move",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         moveInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       moveRender
+   },
+   {  /* name */          "oval",
+      /* isline */        0,
+      /* eJust */         1,
+      /* xInit */         ovalInit,
+      /* xNumProp */      ovalNumProp,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       boxOffset,
+      /* xFit */          ovalFit,
+      /* xRender */       boxRender
+   },
+   {  /* name */          "spline",
+      /* isline */        1,
+      /* eJust */         0,
+      /* xInit */         splineInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       lineOffset,
+      /* xFit */          0,
+      /* xRender */       splineRender
+   },
+   {  /* name */          "text",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         textInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         boxChop,
+      /* xOffset */       textOffset,
+      /* xFit */          boxFit,
+      /* xRender */       boxRender 
+   },
+};
+static const PClass sublistClass = 
+   {  /* name */          "[]",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         sublistInit,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       0 
+   };
+static const PClass noopClass = 
+   {  /* name */          "noop",
+      /* isline */        0,
+      /* eJust */         0,
+      /* xInit */         0,
+      /* xNumProp */      0,
+      /* xCheck */        0,
+      /* xChop */         0,
+      /* xOffset */       boxOffset,
+      /* xFit */          0,
+      /* xRender */       0
+   };
+
+
+/*
+** Reduce the length of the line segment by amt (if possible) by
+** modifying the location of *t.
+*/
+static void pik_chop(PPoint *f, PPoint *t, PNum amt){
+  PNum dx = t->x - f->x;
+  PNum dy = t->y - f->y;
+  PNum dist = hypot(dx,dy);
+  PNum r;
+  if( dist<=amt ){
+    *t = *f;
+    return;
+  }
+  r = 1.0 - amt/dist;
+  t->x = f->x + r*dx;
+  t->y = f->y + r*dy;
+}
+
+/*
+** Draw an arrowhead on the end of the line segment from pFrom to pTo.
+** Also, shorten the line segment (by changing the value of pTo) so that
+** the shaft of the arrow does not extend into the arrowhead.
+*/
+static void pik_draw_arrowhead(Pik *p, PPoint *f, PPoint *t, PObj *pObj){
+  PNum dx = t->x - f->x;
+  PNum dy = t->y - f->y;
+  PNum dist = hypot(dx,dy);
+  PNum h = p->hArrow * pObj->sw;
+  PNum w = p->wArrow * pObj->sw;
+  PNum e1, ddx, ddy;
+  PNum bx, by;
+  if( pObj->color<0.0 ) return;
+  if( pObj->sw<=0.0 ) return;
+  if( dist<=0.0 ) return;  /* Unable */
+  dx /= dist;
+  dy /= dist;
+  e1 = dist - h;
+  if( e1<0.0 ){
+    e1 = 0.0;
+    h = dist;
+  }
+  ddx = -w*dy;
+  ddy = w*dx;
+  bx = f->x + e1*dx;
+  by = f->y + e1*dy;
+  pik_append_xy(p,"<polygon points=\"", t->x, t->y);
+  pik_append_xy(p," ",bx-ddx, by-ddy);
+  pik_append_xy(p," ",bx+ddx, by+ddy);
+  pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n",0);
+  pik_chop(f,t,h/2);
+}
+
+/*
+** Compute the relative offset to an edge location from the reference for a
+** an statement.
+*/
+static PPoint pik_elem_offset(Pik *p, PObj *pObj, int cp){
+  return pObj->type->xOffset(p, pObj, cp);
+}
+
+
+/*
+** Append raw text to zOut
+*/
+static void pik_append(Pik *p, const char *zText, int n){
+  if( n<0 ) n = (int)strlen(zText);
+  if( p->nOut+n>=p->nOutAlloc ){
+    int nNew = (p->nOut+n)*2 + 1;
+    char *z = realloc(p->zOut, nNew);
+    if( z==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }
+    p->zOut = z;
+    p->nOutAlloc = nNew;
+  }
+  memcpy(p->zOut+p->nOut, zText, n);
+  p->nOut += n;
+  p->zOut[p->nOut] = 0;
+}
+
+/*
+** Given a string and its length, returns true if the string begins
+** with a construct which syntactically matches an HTML entity escape
+** sequence (without checking for whether it's a known entity). Always
+** returns false if zText[0] is false or n<4. Entities match the
+** equivalent of the regexes `&#[0-9]{2,};` and
+** `&[a-zA-Z][a-zA-Z0-9]+;`.
+*/
+static int pik_isentity(char const * zText, int n){
+  int i = 0;
+  if( n<4 || '&'!=zText[0] ) return 0;
+  n--;
+  zText++;
+  if( '#'==zText[0] ){
+    zText++;
+    n--;
+    for(i=0; i<n; i++){
+      if( i>1 && ';'==zText[i] ) return 1;
+      else if( zText[i]<'0' || zText[i]>'9' ) return 0;
+      /* Note that &#nn; values nn<32d are not legal entities. */
+    }
+  }else{
+    for(i=0; i<n; i++){
+      if( i>1 && ';'==zText[i] ) return 1;
+      else if( i>0 && zText[i]>='0' && zText[i]<='9' ){
+          continue;
+      }else if( zText[i]<'A' || zText[i]>'z'
+               || (zText[i]>'Z' && zText[i]<'a') ) return 0;
+    }
+  }
+  return 0;
+}
+
+/*
+** Append text to zOut with HTML characters escaped.
+**
+**   *  The space character is changed into non-breaking space (U+00a0)
+**      if mFlags has the 0x01 bit set. This is needed when outputting
+**      text to preserve leading and trailing whitespace.  Turns out we
+**      cannot use &nbsp; as that is an HTML-ism and is not valid in XML.
+**
+**   *  The "&" character is changed into "&amp;" if mFlags has the
+**      0x02 bit set.  This is needed when generating error message text.
+**
+**   *  Except for the above, only "<" and ">" are escaped.
+*/
+static void pik_append_text(Pik *p, const char *zText, int n, int mFlags){
+  int i;
+  char c = 0;
+  int bQSpace = mFlags & 1;
+  int bQAmp = mFlags & 2;
+  if( n<0 ) n = (int)strlen(zText);
+  while( n>0 ){
+    for(i=0; i<n; i++){
+      c = zText[i];
+      if( c=='<' || c=='>' ) break;
+      if( c==' ' && bQSpace ) break;
+      if( c=='&' && bQAmp ) break;
+    }
+    if( i ) pik_append(p, zText, i);
+    if( i==n ) break;
+    switch( c ){
+      case '<': {  pik_append(p, "&lt;", 4);  break;  }
+      case '>': {  pik_append(p, "&gt;", 4);  break;  }
+      case ' ': {  pik_append(p, "\302\240;", 2);  break;  }
+      case '&':
+        if( pik_isentity(zText+i, n-i) ){ pik_append(p, "&", 1); }
+        else { pik_append(p, "&amp;", 5); }
+    }
+    i++;
+    n -= i;
+    zText += i;
+    i = 0;
+  }
+}
+
+/*
+** Append error message text.  This is either a raw append, or an append
+** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag
+** is set.
+*/
+static void pik_append_errtxt(Pik *p, const char *zText, int n){
+  if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){
+    pik_append(p, zText, n);
+  }else{
+    pik_append_text(p, zText, n, 0);
+  }
+}
+
+/* Append a PNum value
+*/
+static void pik_append_num(Pik *p, const char *z,PNum v){
+  char buf[100];
+  snprintf(buf, sizeof(buf)-1, "%.10g", (double)v);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, z, -1);
+  pik_append(p, buf, -1);
+}
+
+/* Append a PPoint value  (Used for debugging only)
+*/
+static void pik_append_point(Pik *p, const char *z, PPoint *pPt){
+  char buf[100];
+  snprintf(buf, sizeof(buf)-1, "%.10g,%.10g", 
+          (double)pPt->x, (double)pPt->y);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, z, -1);
+  pik_append(p, buf, -1);
+}
+
+/*
+** Invert the RGB color so that it is appropriate for dark mode.
+** Variable x hold the initial color.  The color is intended for use
+** as a background color if isBg is true, and as a foreground color
+** if isBg is false.
+*/
+static int pik_color_to_dark_mode(int x, int isBg){
+  int r, g, b;
+  int mn, mx;
+  x = 0xffffff - x;
+  r = (x>>16) & 0xff;
+  g = (x>>8) & 0xff;
+  b = x & 0xff;
+  mx = r;
+  if( g>mx ) mx = g;
+  if( b>mx ) mx = b;
+  mn = r;
+  if( g<mn ) mn = g;
+  if( b<mn ) mn = b;
+  r = mn + (mx-r);
+  g = mn + (mx-g);
+  b = mn + (mx-b);
+  if( isBg ){
+    if( mx>127 ){
+      r = (127*r)/mx;
+      g = (127*g)/mx;
+      b = (127*b)/mx;
+    }
+  }else{
+    if( mn<128 && mx>mn ){
+      r = 127 + ((r-mn)*128)/(mx-mn);
+      g = 127 + ((g-mn)*128)/(mx-mn);
+      b = 127 + ((b-mn)*128)/(mx-mn);
+    }
+  }
+  return r*0x10000 + g*0x100 + b;
+}
+
+/* Append a PNum value surrounded by text.  Do coordinate transformations
+** on the value.
+*/
+static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){
+  char buf[200];
+  v -= p->bbox.sw.x;
+  snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+static void pik_append_y(Pik *p, const char *z1, PNum v, const char *z2){
+  char buf[200];
+  v = p->bbox.ne.y - v;
+  snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+static void pik_append_xy(Pik *p, const char *z1, PNum x, PNum y){
+  char buf[200];
+  x = x - p->bbox.sw.x;
+  y = p->bbox.ne.y - y;
+  snprintf(buf, sizeof(buf)-1, "%s%g,%g", z1, p->rScale*x, p->rScale*y);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+static void pik_append_dis(Pik *p, const char *z1, PNum v, const char *z2){
+  char buf[200];
+  snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+
+/* Append a color specification to the output.
+**
+** In PIKCHR_DARK_MODE, the color is inverted.  The "bg" flags indicates that
+** the color is intended for use as a background color if true, or as a
+** foreground color if false.  The distinction only matters for color
+** inversions in PIKCHR_DARK_MODE.
+*/
+static void pik_append_clr(Pik *p,const char *z1,PNum v,const char *z2,int bg){
+  char buf[200];
+  int x = pik_round(v);
+  int r, g, b;
+  if( x==0 && p->fgcolor>0 && !bg ){
+    x = p->fgcolor;
+  }else if( bg && x>=0xffffff && p->bgcolor>0 ){
+    x = p->bgcolor;
+  }else if( p->mFlags & PIKCHR_DARK_MODE ){
+    x = pik_color_to_dark_mode(x,bg);
+  }
+  r = (x>>16) & 0xff;
+  g = (x>>8) & 0xff;
+  b = x & 0xff;
+  snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+
+/* Append an SVG path A record:
+**
+**    A r1 r2 0 0 0 x y
+*/
+static void pik_append_arc(Pik *p, PNum r1, PNum r2, PNum x, PNum y){
+  char buf[200];
+  x = x - p->bbox.sw.x;
+  y = p->bbox.ne.y - y;
+  snprintf(buf, sizeof(buf)-1, "A%g %g 0 0 0 %g %g", 
+     p->rScale*r1, p->rScale*r2,
+     p->rScale*x, p->rScale*y);
+  buf[sizeof(buf)-1] = 0;
+  pik_append(p, buf, -1);
+}
+
+/* Append a style="..." text.  But, leave the quote unterminated, in case
+** the caller wants to add some more.
+**
+** eFill is non-zero to fill in the background, or 0 if no fill should
+** occur.  Non-zero values of eFill determine the "bg" flag to pik_append_clr()
+** for cases when pObj->fill==pObj->color
+**
+**     1        fill is background, and color is foreground.
+**     2        fill and color are both foreground.  (Used by "dot" objects)
+**     3        fill and color are both background.  (Used by most other objs)
+*/
+static void pik_append_style(Pik *p, PObj *pObj, int eFill){
+  int clrIsBg = 0;
+  pik_append(p, " style=\"", -1);
+  if( pObj->fill>=0 && eFill ){
+    int fillIsBg = 1;
+    if( pObj->fill==pObj->color ){
+      if( eFill==2 ) fillIsBg = 0;
+      if( eFill==3 ) clrIsBg = 1;
+    }
+    pik_append_clr(p, "fill:", pObj->fill, ";", fillIsBg);
+  }else{
+    pik_append(p,"fill:none;",-1);
+  }
+  if( pObj->sw>0.0 && pObj->color>=0.0 ){
+    PNum sw = pObj->sw;
+    pik_append_dis(p, "stroke-width:", sw, ";");
+    if( pObj->nPath>2 && pObj->rad<=pObj->sw ){
+      pik_append(p, "stroke-linejoin:round;", -1);
+    }
+    pik_append_clr(p, "stroke:",pObj->color,";",clrIsBg);
+    if( pObj->dotted>0.0 ){
+      PNum v = pObj->dotted;
+      if( sw<2.1/p->rScale ) sw = 2.1/p->rScale;
+      pik_append_dis(p,"stroke-dasharray:",sw,"");
+      pik_append_dis(p,",",v,";");
+    }else if( pObj->dashed>0.0 ){
+      PNum v = pObj->dashed;
+      pik_append_dis(p,"stroke-dasharray:",v,"");
+      pik_append_dis(p,",",v,";");
+    }
+  }
+}
+
+/*
+** Compute the vertical locations for all text items in the
+** object pObj.  In other words, set every pObj->aTxt[*].eCode
+** value to contain exactly one of: TP_ABOVE2, TP_ABOVE, TP_CENTER,
+** TP_BELOW, or TP_BELOW2 is set.
+*/
+static void pik_txt_vertical_layout(PObj *pObj){
+  int n, i;
+  PToken *aTxt;
+  n = pObj->nTxt;
+  if( n==0 ) return;
+  aTxt = pObj->aTxt;
+  if( n==1 ){
+    if( (aTxt[0].eCode & TP_VMASK)==0 ){
+      aTxt[0].eCode |= TP_CENTER;
+    }
+  }else{
+    int allSlots = 0;
+    int aFree[5];
+    int iSlot;
+    int j, mJust;
+    /* If there is more than one TP_ABOVE, change the first to TP_ABOVE2. */
+    for(j=mJust=0, i=n-1; i>=0; i--){
+      if( aTxt[i].eCode & TP_ABOVE ){
+        if( j==0 ){
+          j++;
+          mJust = aTxt[i].eCode & TP_JMASK;
+        }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){
+          j++;
+        }else{
+          aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_ABOVE2;
+          break;
+        }
+      }
+    }
+    /* If there is more than one TP_BELOW, change the last to TP_BELOW2 */
+    for(j=mJust=0, i=0; i<n; i++){
+      if( aTxt[i].eCode & TP_BELOW ){
+        if( j==0 ){
+          j++;
+          mJust = aTxt[i].eCode & TP_JMASK;
+        }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){
+          j++;
+        }else{
+          aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_BELOW2;
+          break;
+        }
+      }
+    }
+    /* Compute a mask of all slots used */
+    for(i=0; i<n; i++) allSlots |= aTxt[i].eCode & TP_VMASK;
+    /* Set of an array of available slots */
+    if( n==2
+     && ((aTxt[0].eCode|aTxt[1].eCode)&TP_JMASK)==(TP_LJUST|TP_RJUST)
+    ){
+      /* Special case of two texts that have opposite justification:
+      ** Allow them both to float to center. */
+      iSlot = 2;
+      aFree[0] = aFree[1] = TP_CENTER;
+    }else{
+      /* Set up the arrow so that available slots are filled from top to
+      ** bottom */
+      iSlot = 0;
+      if( n>=4 && (allSlots & TP_ABOVE2)==0 ) aFree[iSlot++] = TP_ABOVE2;
+      if( (allSlots & TP_ABOVE)==0 ) aFree[iSlot++] = TP_ABOVE;
+      if( (n&1)!=0 ) aFree[iSlot++] = TP_CENTER;
+      if( (allSlots & TP_BELOW)==0 ) aFree[iSlot++] = TP_BELOW;
+      if( n>=4 && (allSlots & TP_BELOW2)==0 ) aFree[iSlot++] = TP_BELOW2;
+    }
+    /* Set the VMASK for all unassigned texts */
+    for(i=iSlot=0; i<n; i++){
+      if( (aTxt[i].eCode & TP_VMASK)==0 ){
+        aTxt[i].eCode |= aFree[iSlot++];
+      }
+    }
+  }
+}
+
+/* Return the font scaling factor associated with the input text attribute.
+*/
+static PNum pik_font_scale(PToken *t){
+  PNum scale = 1.0;
+  if( t->eCode & TP_BIG    ) scale *= 1.25;
+  if( t->eCode & TP_SMALL  ) scale *= 0.8;
+  if( t->eCode & TP_XTRA   ) scale *= scale;
+  return scale;
+}
+
+/* Append multiple <text> SVG elements for the text fields of the PObj.
+** Parameters:
+**
+**    p          The Pik object into which we are rendering
+**
+**    pObj       Object containing the text to be rendered
+**
+**    pBox       If not NULL, do no rendering at all.  Instead
+**               expand the box object so that it will include all
+**               of the text.
+*/
+static void pik_append_txt(Pik *p, PObj *pObj, PBox *pBox){
+  PNum jw;          /* Justification margin relative to center */
+  PNum ha2 = 0.0;   /* Height of the top row of text */
+  PNum ha1 = 0.0;   /* Height of the second "above" row */
+  PNum hc = 0.0;    /* Height of the center row */
+  PNum hb1 = 0.0;   /* Height of the first "below" row of text */
+  PNum hb2 = 0.0;   /* Height of the second "below" row */
+  PNum yBase = 0.0;
+  int n, i, nz;
+  PNum x, y, orig_y, s;
+  const char *z;
+  PToken *aTxt;
+  unsigned allMask = 0;
+
+  if( p->nErr ) return;
+  if( pObj->nTxt==0 ) return;
+  aTxt = pObj->aTxt;
+  n = pObj->nTxt;
+  pik_txt_vertical_layout(pObj);
+  x = pObj->ptAt.x;
+  for(i=0; i<n; i++) allMask |= pObj->aTxt[i].eCode;
+  if( pObj->type->isLine ){
+    hc = pObj->sw*1.5;
+  }else if( pObj->rad>0.0 && pObj->type->xInit==cylinderInit ){
+    yBase = -0.75*pObj->rad;
+  }
+  if( allMask & TP_CENTER ){
+    for(i=0; i<n; i++){
+      if( pObj->aTxt[i].eCode & TP_CENTER ){
+        s = pik_font_scale(pObj->aTxt+i);
+        if( hc<s*p->charHeight ) hc = s*p->charHeight;
+      }
+    }
+  }
+  if( allMask & TP_ABOVE ){
+    for(i=0; i<n; i++){
+      if( pObj->aTxt[i].eCode & TP_ABOVE ){
+        s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+        if( ha1<s ) ha1 = s;
+      }
+    }
+    if( allMask & TP_ABOVE2 ){
+      for(i=0; i<n; i++){
+        if( pObj->aTxt[i].eCode & TP_ABOVE2 ){
+          s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+          if( ha2<s ) ha2 = s;
+        }
+      }
+    }
+  }
+  if( allMask & TP_BELOW ){
+    for(i=0; i<n; i++){
+      if( pObj->aTxt[i].eCode & TP_BELOW ){
+        s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+        if( hb1<s ) hb1 = s;
+      }
+    }
+    if( allMask & TP_BELOW2 ){
+      for(i=0; i<n; i++){
+        if( pObj->aTxt[i].eCode & TP_BELOW2 ){
+          s = pik_font_scale(pObj->aTxt+i)*p->charHeight;
+          if( hb2<s ) hb2 = s;
+        }
+      }
+    }
+  }
+  if( pObj->type->eJust==1 ){
+    jw = 0.5*(pObj->w - 0.5*(p->charWidth + pObj->sw));
+  }else{
+    jw = 0.0;
+  }
+  for(i=0; i<n; i++){
+    PToken *t = &aTxt[i];
+    PNum xtraFontScale = pik_font_scale(t);
+    PNum nx = 0;
+    orig_y = pObj->ptAt.y;
+    y = yBase;
+    if( t->eCode & TP_ABOVE2 ) y += 0.5*hc + ha1 + 0.5*ha2;
+    if( t->eCode & TP_ABOVE  ) y += 0.5*hc + 0.5*ha1;
+    if( t->eCode & TP_BELOW  ) y -= 0.5*hc + 0.5*hb1;
+    if( t->eCode & TP_BELOW2 ) y -= 0.5*hc + hb1 + 0.5*hb2;
+    if( t->eCode & TP_LJUST  ) nx -= jw;
+    if( t->eCode & TP_RJUST  ) nx += jw;
+
+    if( pBox!=0 ){
+      /* If pBox is not NULL, do not draw any <text>.  Instead, just expand
+      ** pBox to include the text */
+      PNum cw = pik_text_length(t)*p->charWidth*xtraFontScale*0.01;
+      PNum ch = p->charHeight*0.5*xtraFontScale;
+      PNum x0, y0, x1, y1;  /* Boundary of text relative to pObj->ptAt */
+      if( t->eCode & TP_BOLD ) cw *= 1.1;
+      if( t->eCode & TP_RJUST ){
+        x0 = nx;
+        y0 = y-ch;
+        x1 = nx-cw;
+        y1 = y+ch;
+      }else if( t->eCode & TP_LJUST ){
+        x0 = nx;
+        y0 = y-ch;
+        x1 = nx+cw;
+        y1 = y+ch;
+      }else{
+        x0 = nx+cw/2;
+        y0 = y+ch;
+        x1 = nx-cw/2;
+        y1 = y-ch;
+      }
+      if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){
+        int nn = pObj->nPath;
+        PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x;
+        PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y;
+        if( dx!=0 || dy!=0 ){
+          PNum dist = hypot(dx,dy);
+          PNum tt;
+          dx /= dist;
+          dy /= dist;
+          tt = dx*x0 - dy*y0;
+          y0 = dy*x0 - dx*y0;
+          x0 = tt;
+          tt = dx*x1 - dy*y1;
+          y1 = dy*x1 - dx*y1;
+          x1 = tt;
+        }
+      }
+      pik_bbox_add_xy(pBox, x+x0, orig_y+y0);
+      pik_bbox_add_xy(pBox, x+x1, orig_y+y1);
+      continue;
+    }
+    nx += x;
+    y += orig_y;
+
+    pik_append_x(p, "<text x=\"", nx, "\"");
+    pik_append_y(p, " y=\"", y, "\"");
+    if( t->eCode & TP_RJUST ){
+      pik_append(p, " text-anchor=\"end\"", -1);
+    }else if( t->eCode & TP_LJUST ){
+      pik_append(p, " text-anchor=\"start\"", -1);
+    }else{
+      pik_append(p, " text-anchor=\"middle\"", -1);
+    }
+    if( t->eCode & TP_ITALIC ){
+      pik_append(p, " font-style=\"italic\"", -1);
+    }
+    if( t->eCode & TP_BOLD ){
+      pik_append(p, " font-weight=\"bold\"", -1);
+    }
+    if( pObj->color>=0.0 ){
+      pik_append_clr(p, " fill=\"", pObj->color, "\"",0);
+    }
+    xtraFontScale *= p->fontScale;
+    if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){
+      pik_append_num(p, " font-size=\"", xtraFontScale*100.0);
+      pik_append(p, "%\"", 2);
+    }
+    if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){
+      int nn = pObj->nPath;
+      PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x;
+      PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y;
+      if( dx!=0 || dy!=0 ){
+        PNum ang = atan2(dy,dx)*-180/M_PI;
+        pik_append_num(p, " transform=\"rotate(", ang);
+        pik_append_xy(p, " ", x, orig_y);
+        pik_append(p,")\"",2);
+      }
+    }
+    pik_append(p," dominant-baseline=\"central\">",-1);
+    if( t->n>=2 && t->z[0]=='"' ){
+      z = t->z+1;
+      nz = t->n-2;
+    }else{
+      z = t->z;
+      nz = t->n;
+    }
+    while( nz>0 ){
+      int j;
+      for(j=0; j<nz && z[j]!='\\'; j++){}
+      if( j ) pik_append_text(p, z, j, 0x3);
+      if( j<nz && (j+1==nz || z[j+1]=='\\') ){
+        pik_append(p, "&#92;", -1);
+        j++;
+      }
+      nz -= j+1;
+      z += j+1;
+    }
+    pik_append(p, "</text>\n", -1);
+  }
+}
+
+/*
+** Append text (that will go inside of a <pre>...</pre>) that
+** shows the context of an error token.
+*/
+static void pik_error_context(Pik *p, PToken *pErr, int nContext){
+  int iErrPt;           /* Index of first byte of error from start of input */
+  int iErrCol;          /* Column of the error token on its line */
+  int iStart;           /* Start position of the error context */
+  int iEnd;             /* End position of the error context */
+  int iLineno;          /* Line number of the error */
+  int iFirstLineno;     /* Line number of start of error context */
+  int i;                /* Loop counter */
+  int iBump = 0;        /* Bump the location of the error cursor */
+  char zLineno[20];     /* Buffer in which to generate line numbers */
+
+  iErrPt = (int)(pErr->z - p->sIn.z);
+  if( iErrPt>=(int)p->sIn.n ){
+    iErrPt = p->sIn.n-1;
+    iBump = 1;
+  }else{
+    while( iErrPt>0 && (p->sIn.z[iErrPt]=='\n' || p->sIn.z[iErrPt]=='\r') ){
+      iErrPt--;
+      iBump = 1;
+    }
+  }
+  iLineno = 1;
+  for(i=0; i<iErrPt; i++){
+    if( p->sIn.z[i]=='\n' ){
+      iLineno++;
+    }
+  }
+  iStart = 0;
+  iFirstLineno = 1;
+  while( iFirstLineno+nContext<iLineno ){
+    while( p->sIn.z[iStart]!='\n' ){ iStart++; }
+    iStart++;
+    iFirstLineno++;
+  }
+  for(iEnd=iErrPt; p->sIn.z[iEnd]!=0 && p->sIn.z[iEnd]!='\n'; iEnd++){}
+  i = iStart;
+  while( iFirstLineno<=iLineno ){
+    snprintf(zLineno,sizeof(zLineno)-1,"/* %4d */  ", iFirstLineno++);
+    zLineno[sizeof(zLineno)-1] = 0;
+    pik_append(p, zLineno, -1);
+    for(i=iStart; p->sIn.z[i]!=0 && p->sIn.z[i]!='\n'; i++){}
+    pik_append_errtxt(p, p->sIn.z+iStart, i-iStart);
+    iStart = i+1;
+    pik_append(p, "\n", 1);
+  }
+  for(iErrCol=0, i=iErrPt; i>0 && p->sIn.z[i]!='\n'; iErrCol++, i--){}
+  for(i=0; i<iErrCol+11+iBump; i++){ pik_append(p, " ", 1); }
+  for(i=0; i<(int)pErr->n; i++) pik_append(p, "^", 1);
+  pik_append(p, "\n", 1);
+}
+
+
+/*
+** Generate an error message for the output.  pErr is the token at which
+** the error should point.  zMsg is the text of the error message. If
+** either pErr or zMsg is NULL, generate an out-of-memory error message.
+**
+** This routine is a no-op if there has already been an error reported.
+*/
+static void pik_error(Pik *p, PToken *pErr, const char *zMsg){
+  int i;
+  if( p==0 ) return;
+  if( p->nErr ) return;
+  p->nErr++;
+  if( zMsg==0 ){
+    if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){
+      pik_append(p, "\nOut of memory\n", -1);
+    }else{
+      pik_append(p, "\n<div><p>Out of memory</p></div>\n", -1);
+    }
+    return;
+  }
+  if( pErr==0 ){
+    pik_append(p, "\n", 1);
+    pik_append_errtxt(p, zMsg, -1);
+    return;
+  }
+  if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
+    pik_append(p, "<div><pre>\n", -1);
+  }
+  pik_error_context(p, pErr, 5);
+  pik_append(p, "ERROR: ", -1);
+  pik_append_errtxt(p, zMsg, -1);
+  pik_append(p, "\n", 1);
+  for(i=p->nCtx-1; i>=0; i--){
+    pik_append(p, "Called from:\n", -1);
+    pik_error_context(p, &p->aCtx[i], 0);
+  }
+  if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
+    pik_append(p, "</pre></div>\n", -1);
+  }
+}
+
+/*
+** Process an "assert( e1 == e2 )" statement.  Always return NULL.
+*/
+static PObj *pik_assert(Pik *p, PNum e1, PToken *pEq, PNum e2){
+  char zE1[100], zE2[100], zMsg[300];
+
+  /* Convert the numbers to strings using %g for comparison.  This
+  ** limits the precision of the comparison to account for rounding error. */
+  snprintf(zE1, sizeof(zE1), "%g", e1); zE1[sizeof(zE1)-1] = 0;
+  snprintf(zE2, sizeof(zE2), "%g", e2); zE1[sizeof(zE2)-1] = 0;
+  if( strcmp(zE1,zE2)!=0 ){
+    snprintf(zMsg, sizeof(zMsg), "%.50s != %.50s", zE1, zE2);
+    pik_error(p, pEq, zMsg);
+  }
+  return 0;
+}
+
+/*
+** Process an "assert( place1 == place2 )" statement.  Always return NULL.
+*/
+static PObj *pik_position_assert(Pik *p, PPoint *e1, PToken *pEq, PPoint *e2){
+  char zE1[100], zE2[100], zMsg[210];
+
+  /* Convert the numbers to strings using %g for comparison.  This
+  ** limits the precision of the comparison to account for rounding error. */
+  snprintf(zE1, sizeof(zE1), "(%g,%g)", e1->x, e1->y); zE1[sizeof(zE1)-1] = 0;
+  snprintf(zE2, sizeof(zE2), "(%g,%g)", e2->x, e2->y); zE1[sizeof(zE2)-1] = 0;
+  if( strcmp(zE1,zE2)!=0 ){
+    snprintf(zMsg, sizeof(zMsg), "%s != %s", zE1, zE2);
+    pik_error(p, pEq, zMsg);
+  }
+  return 0;
+}
+
+/* Free a complete list of objects */
+static void pik_elist_free(Pik *p, PList *pList){
+  int i;
+  if( pList==0 ) return;
+  for(i=0; i<pList->n; i++){
+    pik_elem_free(p, pList->a[i]);
+  }
+  free(pList->a);
+  free(pList);
+  return;
+}
+
+/* Free a single object, and its substructure */
+static void pik_elem_free(Pik *p, PObj *pObj){
+  if( pObj==0 ) return;
+  free(pObj->zName);
+  pik_elist_free(p, pObj->pSublist);
+  free(pObj->aPath);
+  free(pObj);
+}
+
+/* Convert a numeric literal into a number.  Return that number.
+** There is no error handling because the tokenizer has already
+** assured us that the numeric literal is valid.
+**
+** Allowed number forms:
+**
+**   (1)    Floating point literal
+**   (2)    Same as (1) but followed by a unit: "cm", "mm", "in",
+**          "px", "pt", or "pc".
+**   (3)    Hex integers: 0x000000
+**
+** This routine returns the result in inches.  If a different unit
+** is specified, the conversion happens automatically.
+*/
+PNum pik_atof(PToken *num){
+  char *endptr;
+  PNum ans;
+  if( num->n>=3 && num->z[0]=='0' && (num->z[1]=='x'||num->z[1]=='X') ){
+    return (PNum)strtol(num->z+2, 0, 16);
+  }
+  ans = strtod(num->z, &endptr);
+  if( (int)(endptr - num->z)==(int)num->n-2 ){
+    char c1 = endptr[0];
+    char c2 = endptr[1];
+    if( c1=='c' && c2=='m' ){
+      ans /= 2.54;
+    }else if( c1=='m' && c2=='m' ){
+      ans /= 25.4;
+    }else if( c1=='p' && c2=='x' ){
+      ans /= 96;
+    }else if( c1=='p' && c2=='t' ){
+      ans /= 72;
+    }else if( c1=='p' && c2=='c' ){
+      ans /= 6;
+    }
+  }
+  return ans;
+}
+
+/*
+** Compute the distance between two points
+*/
+static PNum pik_dist(PPoint *pA, PPoint *pB){
+  PNum dx, dy;
+  dx = pB->x - pA->x;
+  dy = pB->y - pA->y;
+  return hypot(dx,dy);
+}
+
+/* Return true if a bounding box is empty.
+*/
+static int pik_bbox_isempty(PBox *p){
+  return p->sw.x>p->ne.x;
+}
+
+/* Return true if point pPt is contained within the bounding box pBox
+*/
+static int pik_bbox_contains_point(PBox *pBox, PPoint *pPt){
+  if( pik_bbox_isempty(pBox) ) return 0;
+  if( pPt->x < pBox->sw.x ) return 0;
+  if( pPt->x > pBox->ne.x ) return 0;
+  if( pPt->y < pBox->sw.y ) return 0;
+  if( pPt->y > pBox->ne.y ) return 0;
+  return 1;
+}
+
+/* Initialize a bounding box to an empty container
+*/
+static void pik_bbox_init(PBox *p){
+  p->sw.x = 1.0;
+  p->sw.y = 1.0;
+  p->ne.x = 0.0;
+  p->ne.y = 0.0;
+}
+
+/* Enlarge the PBox of the first argument so that it fully
+** covers the second PBox
+*/
+static void pik_bbox_addbox(PBox *pA, PBox *pB){
+  if( pik_bbox_isempty(pA) ){
+    *pA = *pB;
+  }
+  if( pik_bbox_isempty(pB) ) return;
+  if( pA->sw.x>pB->sw.x ) pA->sw.x = pB->sw.x;
+  if( pA->sw.y>pB->sw.y ) pA->sw.y = pB->sw.y;
+  if( pA->ne.x<pB->ne.x ) pA->ne.x = pB->ne.x;
+  if( pA->ne.y<pB->ne.y ) pA->ne.y = pB->ne.y;
+}
+
+/* Enlarge the PBox of the first argument, if necessary, so that
+** it contains the point described by the 2nd and 3rd arguments.
+*/
+static void pik_bbox_add_xy(PBox *pA, PNum x, PNum y){
+  if( pik_bbox_isempty(pA) ){
+    pA->ne.x = x;
+    pA->ne.y = y;
+    pA->sw.x = x;
+    pA->sw.y = y;
+    return;
+  }
+  if( pA->sw.x>x ) pA->sw.x = x;
+  if( pA->sw.y>y ) pA->sw.y = y;
+  if( pA->ne.x<x ) pA->ne.x = x;
+  if( pA->ne.y<y ) pA->ne.y = y;
+}
+
+/* Enlarge the PBox so that it is able to contain an ellipse
+** centered at x,y and with radiuses rx and ry.
+*/
+static void pik_bbox_addellipse(PBox *pA, PNum x, PNum y, PNum rx, PNum ry){
+  if( pik_bbox_isempty(pA) ){
+    pA->ne.x = x+rx;
+    pA->ne.y = y+ry;
+    pA->sw.x = x-rx;
+    pA->sw.y = y-ry;
+    return;
+  }
+  if( pA->sw.x>x-rx ) pA->sw.x = x-rx;
+  if( pA->sw.y>y-ry ) pA->sw.y = y-ry;
+  if( pA->ne.x<x+rx ) pA->ne.x = x+rx;
+  if( pA->ne.y<y+ry ) pA->ne.y = y+ry;
+}
+
+
+
+/* Append a new object onto the end of an object list.  The
+** object list is created if it does not already exist.  Return
+** the new object list.
+*/
+static PList *pik_elist_append(Pik *p, PList *pList, PObj *pObj){
+  if( pObj==0 ) return pList;
+  if( pList==0 ){
+    pList = malloc(sizeof(*pList));
+    if( pList==0 ){
+      pik_error(p, 0, 0);
+      pik_elem_free(p, pObj);
+      return 0;
+    }
+    memset(pList, 0, sizeof(*pList));
+  }
+  if( pList->n>=pList->nAlloc ){
+    int nNew = (pList->n+5)*2;
+    PObj **pNew = realloc(pList->a, sizeof(PObj*)*nNew);
+    if( pNew==0 ){
+      pik_error(p, 0, 0);
+      pik_elem_free(p, pObj);
+      return pList;
+    }
+    pList->nAlloc = nNew;
+    pList->a = pNew;
+  }
+  pList->a[pList->n++] = pObj;
+  p->list = pList;
+  return pList;
+}
+
+/* Convert an object class name into a PClass pointer
+*/
+static const PClass *pik_find_class(PToken *pId){
+  int first = 0;
+  int last = count(aClass) - 1;
+  do{
+    int mid = (first+last)/2;
+    int c = strncmp(aClass[mid].zName, pId->z, pId->n);
+    if( c==0 ){
+      c = aClass[mid].zName[pId->n]!=0;
+      if( c==0 ) return &aClass[mid];
+    }
+    if( c<0 ){
+      first = mid + 1;
+    }else{
+      last = mid - 1;
+    }
+  }while( first<=last );
+  return 0;
+}
+
+/* Allocate and return a new PObj object.
+**
+** If pId!=0 then pId is an identifier that defines the object class.
+** If pStr!=0 then it is a STRING literal that defines a text object.
+** If pSublist!=0 then this is a [...] object. If all three parameters
+** are NULL then this is a no-op object used to define a PLACENAME.
+*/
+static PObj *pik_elem_new(Pik *p, PToken *pId, PToken *pStr,PList *pSublist){
+  PObj *pNew;
+  int miss = 0;
+
+  if( p->nErr ) return 0;
+  pNew = malloc( sizeof(*pNew) );
+  if( pNew==0 ){
+    pik_error(p,0,0);
+    pik_elist_free(p, pSublist);
+    return 0;
+  }
+  memset(pNew, 0, sizeof(*pNew));
+  p->cur = pNew;
+  p->nTPath = 1;
+  p->thenFlag = 0;
+  if( p->list==0 || p->list->n==0 ){
+    pNew->ptAt.x = pNew->ptAt.y = 0.0;
+    pNew->eWith = CP_C;
+  }else{
+    PObj *pPrior = p->list->a[p->list->n-1];
+    pNew->ptAt = pPrior->ptExit;
+    switch( p->eDir ){
+      default:         pNew->eWith = CP_W;   break;
+      case DIR_LEFT:   pNew->eWith = CP_E;   break;
+      case DIR_UP:     pNew->eWith = CP_S;   break;
+      case DIR_DOWN:   pNew->eWith = CP_N;   break;
+    }
+  }
+  p->aTPath[0] = pNew->ptAt;
+  pNew->with = pNew->ptAt;
+  pNew->outDir = pNew->inDir = p->eDir;
+  pNew->iLayer = pik_value_int(p, "layer", 5, &miss);
+  if( miss ) pNew->iLayer = 1000;
+  if( pNew->iLayer<0 ) pNew->iLayer = 0;
+  if( pSublist ){
+    pNew->type = &sublistClass;
+    pNew->pSublist = pSublist;
+    sublistClass.xInit(p,pNew);
+    return pNew;
+  }
+  if( pStr ){
+    PToken n;
+    n.z = "text";
+    n.n = 4;
+    pNew->type = pik_find_class(&n);
+    assert( pNew->type!=0 );
+    pNew->errTok = *pStr;
+    pNew->type->xInit(p, pNew);
+    pik_add_txt(p, pStr, pStr->eCode);
+    return pNew;
+  }
+  if( pId ){
+    const PClass *pClass;
+    pNew->errTok = *pId;
+    pClass = pik_find_class(pId);
+    if( pClass ){
+      pNew->type = pClass;
+      pNew->sw = pik_value(p, "thickness",9,0);
+      pNew->fill = pik_value(p, "fill",4,0);
+      pNew->color = pik_value(p, "color",5,0);
+      pClass->xInit(p, pNew);
+      return pNew;
+    }
+    pik_error(p, pId, "unknown object type");
+    pik_elem_free(p, pNew);
+    return 0;
+  }
+  pNew->type = &noopClass;
+  pNew->ptExit = pNew->ptEnter = pNew->ptAt;
+  return pNew;
+}
+
+/*
+** If the ID token in the argument is the name of a macro, return
+** the PMacro object for that macro
+*/
+static PMacro *pik_find_macro(Pik *p, PToken *pId){
+  PMacro *pMac;
+  for(pMac = p->pMacros; pMac; pMac=pMac->pNext){
+    if( pMac->macroName.n==pId->n
+     && strncmp(pMac->macroName.z,pId->z,pId->n)==0
+    ){
+      return pMac;
+    }
+  }
+  return 0;
+}
+
+/* Add a new macro
+*/
+static void pik_add_macro(
+  Pik *p,          /* Current Pikchr diagram */
+  PToken *pId,     /* The ID token that defines the macro name */
+  PToken *pCode    /* Macro body inside of {...} */
+){
+  PMacro *pNew = pik_find_macro(p, pId);
+  if( pNew==0 ){
+    pNew = malloc( sizeof(*pNew) );
+    if( pNew==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }
+    pNew->pNext = p->pMacros;
+    p->pMacros = pNew;
+    pNew->macroName = *pId;
+  }
+  pNew->macroBody.z = pCode->z+1;
+  pNew->macroBody.n = pCode->n-2;
+  pNew->inUse = 0;
+}
+
+
+/*
+** Set the output direction and exit point for an object
+*/
+static void pik_elem_set_exit(PObj *pObj, int eDir){
+  assert( ValidDir(eDir) );
+  pObj->outDir = eDir;
+  if( !pObj->type->isLine || pObj->bClose ){
+    pObj->ptExit = pObj->ptAt;
+    switch( pObj->outDir ){
+      default:         pObj->ptExit.x += pObj->w*0.5;  break;
+      case DIR_LEFT:   pObj->ptExit.x -= pObj->w*0.5;  break;
+      case DIR_UP:     pObj->ptExit.y += pObj->h*0.5;  break;
+      case DIR_DOWN:   pObj->ptExit.y -= pObj->h*0.5;  break;
+    }
+  }
+}
+
+/* Change the layout direction.
+*/
+static void pik_set_direction(Pik *p, int eDir){
+  assert( ValidDir(eDir) );
+  p->eDir = (unsigned char)eDir;
+
+  /* It seems to make sense to reach back into the last object and
+  ** change its exit point (its ".end") to correspond to the new
+  ** direction.  Things just seem to work better this way.  However,
+  ** legacy PIC does *not* do this.
+  **
+  ** The difference can be seen in a script like this:
+  **
+  **      arrow; circle; down; arrow
+  **
+  ** You can make pikchr render the above exactly like PIC
+  ** by deleting the following three lines.  But I (drh) think
+  ** it works better with those lines in place.
+  */
+  if( p->list && p->list->n ){
+    pik_elem_set_exit(p->list->a[p->list->n-1], eDir);
+  }
+}
+
+/* Move all coordinates contained within an object (and within its
+** substructure) by dx, dy
+*/
+static void pik_elem_move(PObj *pObj, PNum dx, PNum dy){
+  int i;
+  pObj->ptAt.x += dx;
+  pObj->ptAt.y += dy;
+  pObj->ptEnter.x += dx;
+  pObj->ptEnter.y += dy;
+  pObj->ptExit.x += dx;
+  pObj->ptExit.y += dy;
+  pObj->bbox.ne.x += dx;
+  pObj->bbox.ne.y += dy;
+  pObj->bbox.sw.x += dx;
+  pObj->bbox.sw.y += dy;
+  for(i=0; i<pObj->nPath; i++){
+    pObj->aPath[i].x += dx;
+    pObj->aPath[i].y += dy;
+  }
+  if( pObj->pSublist ){
+    pik_elist_move(pObj->pSublist, dx, dy);
+  }
+}
+static void pik_elist_move(PList *pList, PNum dx, PNum dy){
+  int i;
+  for(i=0; i<pList->n; i++){
+    pik_elem_move(pList->a[i], dx, dy);
+  }
+}
+
+/*
+** Check to see if it is ok to set the value of paraemeter mThis.
+** Return 0 if it is ok. If it not ok, generate an appropriate
+** error message and return non-zero.
+**
+** Flags are set in pObj so that the same object or conflicting
+** objects may not be set again.
+**
+** To be ok, bit mThis must be clear and no more than one of
+** the bits identified by mBlockers may be set.
+*/
+static int pik_param_ok(
+  Pik *p,             /* For storing the error message (if any) */
+  PObj *pObj,       /* The object under construction */
+  PToken *pId,        /* Make the error point to this token */
+  int mThis           /* Value we are trying to set */
+){
+  if( pObj->mProp & mThis ){
+    pik_error(p, pId, "value is already set");
+    return 1;
+  }
+  if( pObj->mCalc & mThis ){
+    pik_error(p, pId, "value already fixed by prior constraints");
+    return 1;
+  }
+  pObj->mProp |= mThis;
+  return 0;
+}
+
+
+/*
+** Set a numeric property like "width 7" or "radius 200%".
+**
+** The rAbs term is an absolute value to add in.  rRel is
+** a relative value by which to change the current value.
+*/
+void pik_set_numprop(Pik *p, PToken *pId, PRel *pVal){
+  PObj *pObj = p->cur;
+  switch( pId->eType ){
+    case T_HEIGHT:
+      if( pik_param_ok(p, pObj, pId, A_HEIGHT) ) return;
+      pObj->h = pObj->h*pVal->rRel + pVal->rAbs;
+      break;
+    case T_WIDTH:
+      if( pik_param_ok(p, pObj, pId, A_WIDTH) ) return;
+      pObj->w = pObj->w*pVal->rRel + pVal->rAbs;
+      break;
+    case T_RADIUS:
+      if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return;
+      pObj->rad = pObj->rad*pVal->rRel + pVal->rAbs;
+      break;
+    case T_DIAMETER:
+      if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return;
+      pObj->rad = pObj->rad*pVal->rRel + 0.5*pVal->rAbs; /* diam it 2x rad */
+      break;
+    case T_THICKNESS:
+      if( pik_param_ok(p, pObj, pId, A_THICKNESS) ) return;
+      pObj->sw = pObj->sw*pVal->rRel + pVal->rAbs;
+      break;
+  }
+  if( pObj->type->xNumProp ){
+    pObj->type->xNumProp(p, pObj, pId);
+  }
+  return;
+}
+
+/*
+** Set a color property.  The argument is an RGB value.
+*/
+void pik_set_clrprop(Pik *p, PToken *pId, PNum rClr){
+  PObj *pObj = p->cur;
+  switch( pId->eType ){
+    case T_FILL:
+      if( pik_param_ok(p, pObj, pId, A_FILL) ) return;
+      pObj->fill = rClr;
+      break;
+    case T_COLOR:
+      if( pik_param_ok(p, pObj, pId, A_COLOR) ) return;
+      pObj->color = rClr;
+      break;
+  }
+  if( pObj->type->xNumProp ){
+    pObj->type->xNumProp(p, pObj, pId);
+  }
+  return;
+}
+
+/*
+** Set a "dashed" property like "dash 0.05"
+**
+** Use the value supplied by pVal if available.  If pVal==0, use
+** a default.
+*/
+void pik_set_dashed(Pik *p, PToken *pId, PNum *pVal){
+  PObj *pObj = p->cur;
+  PNum v;
+  switch( pId->eType ){
+    case T_DOTTED:  {
+      v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal;
+      pObj->dotted = v;
+      pObj->dashed = 0.0;
+      break;
+    }
+    case T_DASHED:  {
+      v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal;
+      pObj->dashed = v;
+      pObj->dotted = 0.0;
+      break;
+    }
+  }
+}
+
+/*
+** If the current path information came from a "same" or "same as"
+** reset it.
+*/
+static void pik_reset_samepath(Pik *p){
+  if( p->samePath ){
+    p->samePath = 0;
+    p->nTPath = 1;
+  }
+}
+
+
+/* Add a new term to the path for a line-oriented object by transferring
+** the information in the ptTo field over onto the path and into ptFrom
+** resetting the ptTo.
+*/
+static void pik_then(Pik *p, PToken *pToken, PObj *pObj){
+  int n;
+  if( !pObj->type->isLine ){
+    pik_error(p, pToken, "use with line-oriented objects only");
+    return;
+  }
+  n = p->nTPath - 1;
+  if( n<1 && (pObj->mProp & A_FROM)==0 ){
+    pik_error(p, pToken, "no prior path points");
+    return;
+  }
+  p->thenFlag = 1;
+}
+
+/* Advance to the next entry in p->aTPath.  Return its index.
+*/
+static int pik_next_rpath(Pik *p, PToken *pErr){
+  int n = p->nTPath - 1;
+  if( n+1>=(int)count(p->aTPath) ){
+    pik_error(0, pErr, "too many path elements");
+    return n;
+  }
+  n++;
+  p->nTPath++;
+  p->aTPath[n] = p->aTPath[n-1];
+  p->mTPath = 0;
+  return n;
+}
+
+/* Add a direction term to an object.  "up 0.5", or "left 3", or "down"
+** or "down 50%".
+*/
+static void pik_add_direction(Pik *p, PToken *pDir, PRel *pVal){
+  PObj *pObj = p->cur;
+  int n;
+  int dir;
+  if( !pObj->type->isLine ){
+    if( pDir ){
+      pik_error(p, pDir, "use with line-oriented objects only");
+    }else{
+      PToken x = pik_next_semantic_token(&pObj->errTok);
+      pik_error(p, &x, "syntax error");
+    }
+    return;
+  }
+  pik_reset_samepath(p);
+  n = p->nTPath - 1;
+  if( p->thenFlag || p->mTPath==3 || n==0 ){
+    n = pik_next_rpath(p, pDir);
+    p->thenFlag = 0;
+  }
+  dir = pDir ? pDir->eCode : p->eDir;
+  switch( dir ){
+    case DIR_UP:
+       if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].y += pVal->rAbs + pObj->h*pVal->rRel;
+       p->mTPath |= 2;
+       break;
+    case DIR_DOWN:
+       if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].y -= pVal->rAbs + pObj->h*pVal->rRel;
+       p->mTPath |= 2;
+       break;
+    case DIR_RIGHT:
+       if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].x += pVal->rAbs + pObj->w*pVal->rRel;
+       p->mTPath |= 1;
+       break;
+    case DIR_LEFT:
+       if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].x -= pVal->rAbs + pObj->w*pVal->rRel;
+       p->mTPath |= 1;
+       break;
+  }
+  pObj->outDir = dir;
+}
+
+/* Process a movement attribute of one of these forms:
+**
+**         pDist   pHdgKW  rHdg    pEdgept
+**     GO distance HEADING angle
+**     GO distance               compasspoint
+*/
+static void pik_move_hdg(
+  Pik *p,              /* The Pikchr context */
+  PRel *pDist,         /* Distance to move */
+  PToken *pHeading,    /* "heading" keyword if present */
+  PNum rHdg,           /* Angle argument to "heading" keyword */
+  PToken *pEdgept,     /* EDGEPT keyword "ne", "sw", etc... */
+  PToken *pErr         /* Token to use for error messages */
+){
+  PObj *pObj = p->cur;
+  int n;
+  PNum rDist = pDist->rAbs + pik_value(p,"linewid",7,0)*pDist->rRel;
+  if( !pObj->type->isLine ){
+    pik_error(p, pErr, "use with line-oriented objects only");
+    return;
+  }
+  pik_reset_samepath(p);
+  do{
+    n = pik_next_rpath(p, pErr);
+  }while( n<1 );
+  if( pHeading ){
+    rHdg = fmod(rHdg,360.0);
+  }else if( pEdgept->eEdge==CP_C ){
+    pik_error(p, pEdgept, "syntax error");
+    return;
+  }else{
+    rHdg = pik_hdg_angle[pEdgept->eEdge];
+  }
+  if( rHdg<=45.0 ){
+    pObj->outDir = DIR_UP;
+  }else if( rHdg<=135.0 ){
+    pObj->outDir = DIR_RIGHT;
+  }else if( rHdg<=225.0 ){
+    pObj->outDir = DIR_DOWN;
+  }else if( rHdg<=315.0 ){
+    pObj->outDir = DIR_LEFT;
+  }else{
+    pObj->outDir = DIR_UP;
+  }
+  rHdg *= 0.017453292519943295769;  /* degrees to radians */
+  p->aTPath[n].x += rDist*sin(rHdg);
+  p->aTPath[n].y += rDist*cos(rHdg);
+  p->mTPath = 2;
+}
+
+
+/* Process a movement attribute of the form "right until even with ..."
+**
+** pDir is the first keyword, "right" or "left" or "up" or "down".
+** The movement is in that direction until its closest approach to
+** the point specified by pPoint.
+*/
+static void pik_evenwith(Pik *p, PToken *pDir, PPoint *pPlace){
+  PObj *pObj = p->cur;
+  int n;
+  if( !pObj->type->isLine ){
+    pik_error(p, pDir, "use with line-oriented objects only");
+    return;
+  }
+  pik_reset_samepath(p);
+  n = p->nTPath - 1;
+  if( p->thenFlag || p->mTPath==3 || n==0 ){
+    n = pik_next_rpath(p, pDir);
+    p->thenFlag = 0;
+  }
+  switch( pDir->eCode ){
+    case DIR_DOWN:
+    case DIR_UP:
+       if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].y = pPlace->y;
+       p->mTPath |= 2;
+       break;
+    case DIR_RIGHT:
+    case DIR_LEFT:
+       if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir);
+       p->aTPath[n].x = pPlace->x;
+       p->mTPath |= 1;
+       break;
+  }
+  pObj->outDir = pDir->eCode;
+}
+
+/* If the last referenced object is centered at point pPt then return
+** a pointer to that object.  If there is no prior object reference,
+** or if the points are not the same, return NULL.
+**
+** This is a side-channel hack used to find the objects at which a
+** line begins and ends.  For example, in
+**
+**        arrow from OBJ1 to OBJ2 chop
+**
+** The arrow object is normally just handed the coordinates of the
+** centers for OBJ1 and OBJ2.  But we also want to know the specific
+** object named in case there are multiple objects centered at the
+** same point.
+**
+** See forum post 1d46e3a0bc
+*/
+static PObj *pik_last_ref_object(Pik *p, PPoint *pPt){
+  PObj *pRes = 0;
+  if( p->lastRef==0 ) return 0;
+  if( p->lastRef->ptAt.x==pPt->x
+   && p->lastRef->ptAt.y==pPt->y
+  ){
+    pRes = p->lastRef;
+  }
+  p->lastRef = 0;
+  return pRes;
+}
+
+/* Set the "from" of an object
+*/
+static void pik_set_from(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){
+  if( !pObj->type->isLine ){
+    pik_error(p, pTk, "use \"at\" to position this object");
+    return;
+  }
+  if( pObj->mProp & A_FROM ){
+    pik_error(p, pTk, "line start location already fixed");
+    return;
+  }
+  if( pObj->bClose ){
+    pik_error(p, pTk, "polygon is closed");
+    return;
+  }
+  if( p->nTPath>1 ){
+    PNum dx = pPt->x - p->aTPath[0].x;
+    PNum dy = pPt->y - p->aTPath[0].y;
+    int i;
+    for(i=1; i<p->nTPath; i++){
+      p->aTPath[i].x += dx;
+      p->aTPath[i].y += dy;
+    }
+  }
+  p->aTPath[0] = *pPt;
+  p->mTPath = 3;
+  pObj->mProp |= A_FROM;
+  pObj->pFrom = pik_last_ref_object(p, pPt);
+}
+
+/* Set the "to" of an object
+*/
+static void pik_add_to(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){
+  int n = p->nTPath-1;
+  if( !pObj->type->isLine ){
+    pik_error(p, pTk, "use \"at\" to position this object");
+    return;
+  }
+  if( pObj->bClose ){
+    pik_error(p, pTk, "polygon is closed");
+    return;
+  }
+  pik_reset_samepath(p);
+  if( n==0 || p->mTPath==3 || p->thenFlag ){
+    n = pik_next_rpath(p, pTk);
+  }
+  p->aTPath[n] = *pPt;
+  p->mTPath = 3;
+  pObj->pTo = pik_last_ref_object(p, pPt);
+}
+
+static void pik_close_path(Pik *p, PToken *pErr){
+  PObj *pObj = p->cur;
+  if( p->nTPath<3 ){
+    pik_error(p, pErr,
+      "need at least 3 vertexes in order to close the polygon");
+    return;
+  }
+  if( pObj->bClose ){
+    pik_error(p, pErr, "polygon already closed");
+    return;
+  }
+  pObj->bClose = 1;
+}
+
+/* Lower the layer of the current object so that it is behind the
+** given object.
+*/
+static void pik_behind(Pik *p, PObj *pOther){
+  PObj *pObj = p->cur;
+  if( p->nErr==0 && pObj->iLayer>=pOther->iLayer ){
+    pObj->iLayer = pOther->iLayer - 1;
+  }
+}
+
+
+/* Set the "at" of an object
+*/
+static void pik_set_at(Pik *p, PToken *pEdge, PPoint *pAt, PToken *pErrTok){
+  PObj *pObj;
+  static unsigned char eDirToCp[] = { CP_E, CP_S, CP_W, CP_N };
+  if( p->nErr ) return;
+  pObj = p->cur;
+
+  if( pObj->type->isLine ){
+    pik_error(p, pErrTok, "use \"from\" and \"to\" to position this object");
+    return;
+  }
+  if( pObj->mProp & A_AT ){
+    pik_error(p, pErrTok, "location fixed by prior \"at\"");
+    return;
+  }
+  pObj->mProp |= A_AT;
+  pObj->eWith = pEdge ? pEdge->eEdge : CP_C;
+  if( pObj->eWith>=CP_END ){
+    int dir = pObj->eWith==CP_END ? pObj->outDir : pObj->inDir;
+    pObj->eWith = eDirToCp[dir];
+  }
+  pObj->with = *pAt;
+}
+
+/*
+** Try to add a text attribute to an object
+*/
+static void pik_add_txt(Pik *p, PToken *pTxt, int iPos){
+  PObj *pObj = p->cur;
+  PToken *pT;
+  if( pObj->nTxt >= count(pObj->aTxt) ){
+    pik_error(p, pTxt, "too many text terms");
+    return;
+  }
+  pT = &pObj->aTxt[pObj->nTxt++];
+  *pT = *pTxt;
+  pT->eCode = (short)iPos;
+}
+
+/* Merge "text-position" flags
+*/
+static int pik_text_position(int iPrev, PToken *pFlag){
+  int iRes = iPrev;
+  switch( pFlag->eType ){
+    case T_LJUST:    iRes = (iRes&~TP_JMASK) | TP_LJUST;  break;
+    case T_RJUST:    iRes = (iRes&~TP_JMASK) | TP_RJUST;  break;
+    case T_ABOVE:    iRes = (iRes&~TP_VMASK) | TP_ABOVE;  break;
+    case T_CENTER:   iRes = (iRes&~TP_VMASK) | TP_CENTER; break;
+    case T_BELOW:    iRes = (iRes&~TP_VMASK) | TP_BELOW;  break;
+    case T_ITALIC:   iRes |= TP_ITALIC;                   break; 
+    case T_BOLD:     iRes |= TP_BOLD;                     break; 
+    case T_ALIGNED:  iRes |= TP_ALIGN;                    break;
+    case T_BIG:      if( iRes & TP_BIG ) iRes |= TP_XTRA;
+                     else iRes = (iRes &~TP_SZMASK)|TP_BIG;   break;
+    case T_SMALL:    if( iRes & TP_SMALL ) iRes |= TP_XTRA;
+                     else iRes = (iRes &~TP_SZMASK)|TP_SMALL; break;
+  }
+  return iRes;
+}
+
+/*
+** Table of scale-factor estimates for variable-width characters.
+** Actual character widths vary by font.  These numbers are only
+** guesses.  And this table only provides data for ASCII.
+**
+** 100 means normal width.
+*/
+static const unsigned char awChar[] = {
+  /* Skip initial 32 control characters */
+  /* ' ' */  45,
+  /* '!' */  55,
+  /* '"' */  62,
+  /* '#' */  115,
+  /* '$' */  90,
+  /* '%' */  132,
+  /* '&' */  125,
+  /* '\''*/  40,
+
+  /* '(' */  55,
+  /* ')' */  55,
+  /* '*' */  71,
+  /* '+' */  115,
+  /* ',' */  45,
+  /* '-' */  48,
+  /* '.' */  45,
+  /* '/' */  50,
+
+  /* '0' */  91,
+  /* '1' */  91,
+  /* '2' */  91,
+  /* '3' */  91,
+  /* '4' */  91,
+  /* '5' */  91,
+  /* '6' */  91,
+  /* '7' */  91,
+
+  /* '8' */  91,
+  /* '9' */  91,
+  /* ':' */  50,
+  /* ';' */  50,
+  /* '<' */ 120,
+  /* '=' */ 120,
+  /* '>' */ 120,
+  /* '?' */  78,
+
+  /* '@' */ 142,
+  /* 'A' */ 102,
+  /* 'B' */ 105,
+  /* 'C' */ 110,
+  /* 'D' */ 115,
+  /* 'E' */ 105,
+  /* 'F' */  98,
+  /* 'G' */ 105,
+
+  /* 'H' */ 125,
+  /* 'I' */  58,
+  /* 'J' */  58,
+  /* 'K' */ 107,
+  /* 'L' */  95,
+  /* 'M' */ 145,
+  /* 'N' */ 125,
+  /* 'O' */ 115,
+
+  /* 'P' */  95,
+  /* 'Q' */ 115,
+  /* 'R' */ 107,
+  /* 'S' */  95,
+  /* 'T' */  97,
+  /* 'U' */ 118,
+  /* 'V' */ 102,
+  /* 'W' */ 150,
+
+  /* 'X' */ 100,
+  /* 'Y' */  93,
+  /* 'Z' */ 100,
+  /* '[' */  58,
+  /* '\\'*/  50,
+  /* ']' */  58,
+  /* '^' */ 119,
+  /* '_' */  72,
+
+  /* '`' */  72,
+  /* 'a' */  86,
+  /* 'b' */  92,
+  /* 'c' */  80,
+  /* 'd' */  92,
+  /* 'e' */  85,
+  /* 'f' */  52,
+  /* 'g' */  92,
+
+  /* 'h' */  92,
+  /* 'i' */  47,
+  /* 'j' */  47,
+  /* 'k' */  88,
+  /* 'l' */  48,
+  /* 'm' */ 135,
+  /* 'n' */  92,
+  /* 'o' */  86,
+
+  /* 'p' */  92,
+  /* 'q' */  92,
+  /* 'r' */  69,
+  /* 's' */  75,
+  /* 't' */  58,
+  /* 'u' */  92,
+  /* 'v' */  80,
+  /* 'w' */ 121,
+
+  /* 'x' */  81,
+  /* 'y' */  80,
+  /* 'z' */  76,
+  /* '{' */  91,
+  /* '|'*/   49,
+  /* '}' */  91,
+  /* '~' */ 118,
+};
+
+/* Return an estimate of the width of the displayed characters
+** in a character string.  The returned value is 100 times the
+** average character width.
+**
+** Omit "\" used to escape characters.  And count entities like
+** "&lt;" as a single character.  Multi-byte UTF8 characters count
+** as a single character.
+**
+** Attempt to scale the answer by the actual characters seen.  Wide
+** characters count more than narrow characters.  But the widths are
+** only guesses.
+*/
+static int pik_text_length(const PToken *pToken){
+  int n = pToken->n;
+  const char *z = pToken->z;
+  int cnt, j;
+  for(j=1, cnt=0; j<n-1; j++){
+    char c = z[j];
+    if( c=='\\' && z[j+1]!='&' ){
+      c = z[++j];
+    }else if( c=='&' ){
+      int k;
+      for(k=j+1; k<j+7 && z[k]!=0 && z[k]!=';'; k++){}
+      if( z[k]==';' ) j = k;
+      cnt += 150;
+      continue;
+    }
+    if( (c & 0xc0)==0xc0 ){
+      while( j+1<n-1 && (z[j+1]&0xc0)==0x80 ){ j++; }
+      cnt += 100;
+      continue;
+    }
+    if( c>=0x20 && c<=0x7e ){
+      cnt += awChar[c-0x20];
+    }else{
+      cnt += 100;
+    }
+  }
+  return cnt;
+}
+
+/* Adjust the width, height, and/or radius of the object so that
+** it fits around the text that has been added so far.
+**
+**    (1) Only text specified prior to this attribute is considered.
+**    (2) The text size is estimated based on the charht and charwid
+**        variable settings.
+**    (3) The fitted attributes can be changed again after this
+**        attribute, for example using "width 110%" if this auto-fit
+**        underestimates the text size.
+**    (4) Previously set attributes will not be altered.  In other words,
+**        "width 1in fit" might cause the height to change, but the
+**        width is now set.
+**    (5) This only works for attributes that have an xFit method.
+**
+** The eWhich parameter is:
+**
+**    1:   Fit horizontally only
+**    2:   Fit vertically only
+**    3:   Fit both ways
+*/
+static void pik_size_to_fit(Pik *p, PToken *pFit, int eWhich){
+  PObj *pObj;
+  PNum w, h;
+  PBox bbox;
+  if( p->nErr ) return;
+  pObj = p->cur;
+
+  if( pObj->nTxt==0 ){
+    pik_error(0, pFit, "no text to fit to");
+    return;
+  }
+  if( pObj->type->xFit==0 ) return;
+  pik_bbox_init(&bbox);
+  pik_compute_layout_settings(p);
+  pik_append_txt(p, pObj, &bbox);
+  w = (eWhich & 1)!=0 ? (bbox.ne.x - bbox.sw.x) + p->charWidth : 0;
+  if( eWhich & 2 ){
+    PNum h1, h2;
+    h1 = (bbox.ne.y - pObj->ptAt.y);
+    h2 = (pObj->ptAt.y - bbox.sw.y);
+    h = 2.0*( h1<h2 ? h2 : h1 ) + 0.5*p->charHeight;
+  }else{
+    h = 0;
+  }
+  pObj->type->xFit(p, pObj, w, h);
+  pObj->mProp |= A_FIT;
+}
+
+/* Set a local variable name to "val".
+**
+** The name might be a built-in variable or a color name.  In either case,
+** a new application-defined variable is set.  Since app-defined variables
+** are searched first, this will override any built-in variables.
+*/
+static void pik_set_var(Pik *p, PToken *pId, PNum val, PToken *pOp){
+  PVar *pVar = p->pVar;
+  while( pVar ){
+    if( pik_token_eq(pId,pVar->zName)==0 ) break;
+    pVar = pVar->pNext;
+  }
+  if( pVar==0 ){
+    char *z;
+    pVar = malloc( pId->n+1 + sizeof(*pVar) );
+    if( pVar==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }
+    pVar->zName = z = (char*)&pVar[1];
+    memcpy(z, pId->z, pId->n);
+    z[pId->n] = 0;
+    pVar->pNext = p->pVar;
+    pVar->val = pik_value(p, pId->z, pId->n, 0);
+    p->pVar = pVar;
+  }
+  switch( pOp->eCode ){
+    case T_PLUS:  pVar->val += val; break;
+    case T_STAR:  pVar->val *= val; break;
+    case T_MINUS: pVar->val -= val; break;
+    case T_SLASH:
+      if( val==0.0 ){
+        pik_error(p, pOp, "division by zero");
+      }else{
+        pVar->val /= val;
+      }
+      break;
+    default:      pVar->val = val; break;
+  }
+  p->bLayoutVars = 0;  /* Clear the layout setting cache */
+}
+
+/*
+** Round a PNum into the nearest integer
+*/
+static int pik_round(PNum v){
+  if( isnan(v) ) return 0;
+  if( v < -2147483647 ) return (-2147483647-1);
+  if( v >= 2147483647 ) return 2147483647;
+  return (int)v;
+}
+
+/*
+** Search for the variable named z[0..n-1] in:
+**
+**   * Application defined variables
+**   * Built-in variables
+**
+** Return the value of the variable if found.  If not found
+** return 0.0.  Also if pMiss is not NULL, then set it to 1
+** if not found.
+**
+** This routine is a subroutine to pik_get_var().  But it is also
+** used by object implementations to look up (possibly overwritten)
+** values for built-in variables like "boxwid".
+*/
+static PNum pik_value(Pik *p, const char *z, int n, int *pMiss){
+  PVar *pVar;
+  int first, last, mid, c;
+  for(pVar=p->pVar; pVar; pVar=pVar->pNext){
+    if( strncmp(pVar->zName,z,n)==0 && pVar->zName[n]==0 ){
+      return pVar->val;
+    }
+  }
+  first = 0;
+  last = count(aBuiltin)-1;
+  while( first<=last ){
+    mid = (first+last)/2;
+    c = strncmp(z,aBuiltin[mid].zName,n);
+    if( c==0 && aBuiltin[mid].zName[n] ) c = 1;
+    if( c==0 ) return aBuiltin[mid].val;
+    if( c>0 ){
+      first = mid+1;
+    }else{
+      last = mid-1;
+    }
+  }
+  if( pMiss ) *pMiss = 1;
+  return 0.0;
+}
+static int pik_value_int(Pik *p, const char *z, int n, int *pMiss){
+  return pik_round(pik_value(p,z,n,pMiss));
+}
+
+/*
+** Look up a color-name.  Unlike other names in this program, the
+** color-names are not case sensitive.  So "DarkBlue" and "darkblue"
+** and "DARKBLUE" all find the same value (139).
+**
+** If not found, return -99.0.  Also post an error if p!=NULL.
+**
+** Special color names "None" and "Off" return -1.0 without causing
+** an error.
+*/
+static PNum pik_lookup_color(Pik *p, PToken *pId){
+  int first, last, mid, c = 0;
+  first = 0;
+  last = count(aColor)-1;
+  while( first<=last ){
+    const char *zClr;
+    int c1, c2;
+    unsigned int i;
+    mid = (first+last)/2;
+    zClr = aColor[mid].zName;
+    for(i=0; i<pId->n; i++){
+      c1 = zClr[i]&0x7f;
+      if( isupper(c1) ) c1 = tolower(c1);
+      c2 = pId->z[i]&0x7f;
+      if( isupper(c2) ) c2 = tolower(c2);
+      c = c2 - c1;
+      if( c ) break;
+    }
+    if( c==0 && aColor[mid].zName[pId->n] ) c = -1;
+    if( c==0 ) return (double)aColor[mid].val;
+    if( c>0 ){
+      first = mid+1;
+    }else{
+      last = mid-1;
+    }
+  }
+  if( p ) pik_error(p, pId, "not a known color name");
+  return -99.0;
+}
+
+/* Get the value of a variable.
+**
+** Search in order:
+**
+**    *  Application defined variables
+**    *  Built-in variables
+**    *  Color names
+**
+** If no such variable is found, throw an error.
+*/
+static PNum pik_get_var(Pik *p, PToken *pId){
+  int miss = 0;
+  PNum v = pik_value(p, pId->z, pId->n, &miss);
+  if( miss==0 ) return v;
+  v = pik_lookup_color(0, pId);
+  if( v>-90.0 ) return v;
+  pik_error(p,pId,"no such variable");
+  return 0.0;
+}
+
+/* Convert a T_NTH token (ex: "2nd", "5th"} into a numeric value and
+** return that value.  Throw an error if the value is too big.
+*/
+static short int pik_nth_value(Pik *p, PToken *pNth){
+  int i = atoi(pNth->z);
+  if( i>1000 ){
+    pik_error(p, pNth, "value too big - max '1000th'");
+    i = 1;
+  }
+  if( i==0 && pik_token_eq(pNth,"first")==0 ) i = 1;
+  return (short int)i;
+}
+
+/* Search for the NTH object.
+**
+** If pBasis is not NULL then it should be a [] object.  Use the
+** sublist of that [] object for the search.  If pBasis is not a []
+** object, then throw an error.
+**
+** The pNth token describes the N-th search.  The pNth->eCode value
+** is one more than the number of items to skip.  It is negative
+** to search backwards.  If pNth->eType==T_ID, then it is the name
+** of a class to search for.  If pNth->eType==T_LB, then
+** search for a [] object.  If pNth->eType==T_LAST, then search for
+** any type.
+**
+** Raise an error if the item is not found.
+*/
+static PObj *pik_find_nth(Pik *p, PObj *pBasis, PToken *pNth){
+  PList *pList;
+  int i, n;
+  const PClass *pClass;
+  if( pBasis==0 ){
+    pList = p->list;
+  }else{
+    pList = pBasis->pSublist;
+  }
+  if( pList==0 ){
+    pik_error(p, pNth, "no such object");
+    return 0;
+  }
+  if( pNth->eType==T_LAST ){
+    pClass = 0;
+  }else if( pNth->eType==T_LB ){
+    pClass = &sublistClass;
+  }else{
+    pClass = pik_find_class(pNth);
+    if( pClass==0 ){
+      pik_error(0, pNth, "no such object type");
+      return 0;
+    }
+  }
+  n = pNth->eCode;
+  if( n<0 ){
+    for(i=pList->n-1; i>=0; i--){
+      PObj *pObj = pList->a[i];
+      if( pClass && pObj->type!=pClass ) continue;
+      n++;
+      if( n==0 ){ return pObj; }
+    }
+  }else{
+    for(i=0; i<pList->n; i++){
+      PObj *pObj = pList->a[i];
+      if( pClass && pObj->type!=pClass ) continue;
+      n--;
+      if( n==0 ){ return pObj; }
+    }
+  }
+  pik_error(p, pNth, "no such object");
+  return 0;
+}
+
+/* Search for an object by name.
+**
+** Search in pBasis->pSublist if pBasis is not NULL.  If pBasis is NULL
+** then search in p->list.
+*/
+static PObj *pik_find_byname(Pik *p, PObj *pBasis, PToken *pName){
+  PList *pList;
+  int i, j;
+  if( pBasis==0 ){
+    pList = p->list;
+  }else{
+    pList = pBasis->pSublist;
+  }
+  if( pList==0 ){
+    pik_error(p, pName, "no such object");
+    return 0;
+  }
+  /* First look explicitly tagged objects */
+  for(i=pList->n-1; i>=0; i--){
+    PObj *pObj = pList->a[i];
+    if( pObj->zName && pik_token_eq(pName,pObj->zName)==0 ){
+      p->lastRef = pObj;
+      return pObj;
+    }
+  }
+  /* If not found, do a second pass looking for any object containing
+  ** text which exactly matches pName */
+  for(i=pList->n-1; i>=0; i--){
+    PObj *pObj = pList->a[i];
+    for(j=0; j<pObj->nTxt; j++){
+      if( pObj->aTxt[j].n==pName->n+2
+       && memcmp(pObj->aTxt[j].z+1,pName->z,pName->n)==0 ){
+        p->lastRef = pObj;
+        return pObj;
+      }
+    }
+  }
+  pik_error(p, pName, "no such object");
+  return 0;
+}
+
+/* Change most of the settings for the current object to be the
+** same as the pOther object, or the most recent object of the same
+** type if pOther is NULL.
+*/
+static void pik_same(Pik *p, PObj *pOther, PToken *pErrTok){
+  PObj *pObj = p->cur;
+  if( p->nErr ) return;
+  if( pOther==0 ){
+    int i;
+    for(i=(p->list ? p->list->n : 0)-1; i>=0; i--){
+      pOther = p->list->a[i];
+      if( pOther->type==pObj->type ) break;
+    }
+    if( i<0 ){
+      pik_error(p, pErrTok, "no prior objects of the same type");
+      return;
+    }
+  }
+  if( pOther->nPath && pObj->type->isLine ){
+    PNum dx, dy;
+    int i;
+    dx = p->aTPath[0].x - pOther->aPath[0].x;
+    dy = p->aTPath[0].y - pOther->aPath[0].y;
+    for(i=1; i<pOther->nPath; i++){
+      p->aTPath[i].x = pOther->aPath[i].x + dx;
+      p->aTPath[i].y = pOther->aPath[i].y + dy;
+    }
+    p->nTPath = pOther->nPath;
+    p->mTPath = 3;
+    p->samePath = 1;
+  }
+  if( !pObj->type->isLine ){
+    pObj->w = pOther->w;
+    pObj->h = pOther->h;
+  }
+  pObj->rad = pOther->rad;
+  pObj->sw = pOther->sw;
+  pObj->dashed = pOther->dashed;
+  pObj->dotted = pOther->dotted;
+  pObj->fill = pOther->fill;
+  pObj->color = pOther->color;
+  pObj->cw = pOther->cw;
+  pObj->larrow = pOther->larrow;
+  pObj->rarrow = pOther->rarrow;
+  pObj->bClose = pOther->bClose;
+  pObj->bChop = pOther->bChop;
+  pObj->inDir = pOther->inDir;
+  pObj->outDir = pOther->outDir;
+  pObj->iLayer = pOther->iLayer;
+}
+
+
+/* Return a "Place" associated with object pObj.  If pEdge is NULL
+** return the center of the object.  Otherwise, return the corner
+** described by pEdge.
+*/
+static PPoint pik_place_of_elem(Pik *p, PObj *pObj, PToken *pEdge){
+  PPoint pt = cZeroPoint;
+  const PClass *pClass;
+  if( pObj==0 ) return pt;
+  if( pEdge==0 ){
+    return pObj->ptAt;
+  }
+  pClass = pObj->type;
+  if( pEdge->eType==T_EDGEPT || (pEdge->eEdge>0 && pEdge->eEdge<CP_END) ){
+    pt = pClass->xOffset(p, pObj, pEdge->eEdge);
+    pt.x += pObj->ptAt.x;
+    pt.y += pObj->ptAt.y;
+    return pt;
+  }
+  if( pEdge->eType==T_START ){
+    return pObj->ptEnter;
+  }else{
+    return pObj->ptExit;
+  }
+}
+
+/* Do a linear interpolation of two positions.
+*/
+static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2){
+  PPoint out;
+  out.x = p2.x*x + p1.x*(1.0 - x);
+  out.y = p2.y*x + p1.y*(1.0 - x);
+  return out;
+}
+
+/* Compute the position that is dist away from pt at an heading angle of r
+**
+** The angle is a compass heading in degrees.  North is 0 (or 360).
+** East is 90.  South is 180.  West is 270.  And so forth.
+*/
+static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt){
+  r *= 0.017453292519943295769;  /* degrees to radians */
+  pt.x += dist*sin(r);
+  pt.y += dist*cos(r);
+  return pt;
+}
+
+/* Compute the position that is dist away at a compass point
+*/
+static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt){
+  return pik_position_at_angle(dist, pik_hdg_angle[pD->eEdge], pt);
+}
+
+/* Return the coordinates for the n-th vertex of a line.
+*/
+static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj){
+  static const PPoint zero = {0, 0};
+  int n;
+  if( p->nErr || pObj==0 ) return p->aTPath[0];
+  if( !pObj->type->isLine ){
+    pik_error(p, pErr, "object is not a line");
+    return zero;
+  }
+  n = atoi(pNth->z);
+  if( n<1 || n>pObj->nPath ){
+    pik_error(p, pNth, "no such vertex");
+    return zero;
+  }
+  return pObj->aPath[n-1];
+}
+
+/* Return the value of a property of an object.
+*/
+static PNum pik_property_of(PObj *pObj, PToken *pProp){
+  PNum v = 0.0;
+  if( pObj ){
+    switch( pProp->eType ){
+      case T_HEIGHT:    v = pObj->h;            break;
+      case T_WIDTH:     v = pObj->w;            break;
+      case T_RADIUS:    v = pObj->rad;          break;
+      case T_DIAMETER:  v = pObj->rad*2.0;      break;
+      case T_THICKNESS: v = pObj->sw;           break;
+      case T_DASHED:    v = pObj->dashed;       break;
+      case T_DOTTED:    v = pObj->dotted;       break;
+      case T_FILL:      v = pObj->fill;         break;
+      case T_COLOR:     v = pObj->color;        break;
+      case T_X:         v = pObj->ptAt.x;       break;
+      case T_Y:         v = pObj->ptAt.y;       break;
+      case T_TOP:       v = pObj->bbox.ne.y;    break;
+      case T_BOTTOM:    v = pObj->bbox.sw.y;    break;
+      case T_LEFT:      v = pObj->bbox.sw.x;    break;
+      case T_RIGHT:     v = pObj->bbox.ne.x;    break;
+    }
+  }
+  return v;
+}
+
+/* Compute one of the built-in functions
+*/
+static PNum pik_func(Pik *p, PToken *pFunc, PNum x, PNum y){
+  PNum v = 0.0;
+  switch( pFunc->eCode ){
+    case FN_ABS:  v = x<0.0 ? -x : x;  break;
+    case FN_COS:  v = cos(x);          break;
+    case FN_INT:  v = rint(x);         break;
+    case FN_SIN:  v = sin(x);          break;
+    case FN_SQRT:
+      if( x<0.0 ){
+        pik_error(p, pFunc, "sqrt of negative value");
+        v = 0.0;
+      }else{
+        v = sqrt(x);
+      }
+      break;
+    case FN_MAX:  v = x>y ? x : y;   break;
+    case FN_MIN:  v = x<y ? x : y;   break;
+    default:      v = 0.0;
+  }
+  return v;
+}
+
+/* Attach a name to an object
+*/
+static void pik_elem_setname(Pik *p, PObj *pObj, PToken *pName){
+  if( pObj==0 ) return;
+  if( pName==0 ) return;
+  free(pObj->zName);
+  pObj->zName = malloc(pName->n+1);
+  if( pObj->zName==0 ){
+    pik_error(p,0,0);
+  }else{
+    memcpy(pObj->zName,pName->z,pName->n);
+    pObj->zName[pName->n] = 0;
+  }
+  return;
+}
+
+/*
+** Search for object located at *pCenter that has an xChop method and
+** that does not enclose point pOther.
+**
+** Return a pointer to the object, or NULL if not found.
+*/
+static PObj *pik_find_chopper(PList *pList, PPoint *pCenter, PPoint *pOther){
+  int i;
+  if( pList==0 ) return 0;
+  for(i=pList->n-1; i>=0; i--){
+    PObj *pObj = pList->a[i];
+    if( pObj->type->xChop!=0
+     && pObj->ptAt.x==pCenter->x
+     && pObj->ptAt.y==pCenter->y
+     && !pik_bbox_contains_point(&pObj->bbox, pOther)
+    ){
+      return pObj;
+    }else if( pObj->pSublist ){
+      pObj = pik_find_chopper(pObj->pSublist,pCenter,pOther);
+      if( pObj ) return pObj;
+    }
+  }
+  return 0;
+}
+
+/*
+** There is a line traveling from pFrom to pTo.
+**
+** If pObj is not null and is a choppable object, then chop at
+** the boundary of pObj - where the line crosses the boundary
+** of pObj.
+**
+** If pObj is NULL or has no xChop method, then search for some
+** other object centered at pTo that is choppable and use it
+** instead.
+*/
+static void pik_autochop(Pik *p, PPoint *pFrom, PPoint *pTo, PObj *pObj){
+  if( pObj==0 || pObj->type->xChop==0 ){
+    pObj = pik_find_chopper(p->list, pTo, pFrom);
+  }
+  if( pObj ){
+    *pTo = pObj->type->xChop(p, pObj, pFrom);
+  }
+}
+
+/* This routine runs after all attributes have been received
+** on an object.
+*/
+static void pik_after_adding_attributes(Pik *p, PObj *pObj){
+  int i;
+  PPoint ofst;
+  PNum dx, dy;
+
+  if( p->nErr ) return;
+
+  /* Position block objects */
+  if( pObj->type->isLine==0 ){
+    /* A height or width less than or equal to zero means "autofit".
+    ** Change the height or width to be big enough to contain the text,
+    */
+    if( pObj->h<=0.0 ){
+      if( pObj->nTxt==0 ){
+        pObj->h = 0.0;
+      }else if( pObj->w<=0.0 ){
+        pik_size_to_fit(p, &pObj->errTok, 3);
+      }else{
+        pik_size_to_fit(p, &pObj->errTok, 2);
+      }
+    }
+    if( pObj->w<=0.0 ){
+      if( pObj->nTxt==0 ){
+        pObj->w = 0.0;
+      }else{
+        pik_size_to_fit(p, &pObj->errTok, 1);
+      }
+    }
+    ofst = pik_elem_offset(p, pObj, pObj->eWith);
+    dx = (pObj->with.x - ofst.x) - pObj->ptAt.x;
+    dy = (pObj->with.y - ofst.y) - pObj->ptAt.y;
+    if( dx!=0 || dy!=0 ){
+      pik_elem_move(pObj, dx, dy);
+    }
+  }
+
+  /* For a line object with no movement specified, a single movement
+  ** of the default length in the current direction
+  */
+  if( pObj->type->isLine && p->nTPath<2 ){
+    pik_next_rpath(p, 0);
+    assert( p->nTPath==2 );
+    switch( pObj->inDir ){
+      default:        p->aTPath[1].x += pObj->w; break;
+      case DIR_DOWN:  p->aTPath[1].y -= pObj->h; break;
+      case DIR_LEFT:  p->aTPath[1].x -= pObj->w; break;
+      case DIR_UP:    p->aTPath[1].y += pObj->h; break;
+    }
+    if( pObj->type->xInit==arcInit ){
+      pObj->outDir = (pObj->inDir + (pObj->cw ? 1 : 3))%4;
+      p->eDir = (unsigned char)pObj->outDir;
+      switch( pObj->outDir ){
+        default:        p->aTPath[1].x += pObj->w; break;
+        case DIR_DOWN:  p->aTPath[1].y -= pObj->h; break;
+        case DIR_LEFT:  p->aTPath[1].x -= pObj->w; break;
+        case DIR_UP:    p->aTPath[1].y += pObj->h; break;
+      }
+    }
+  }
+
+  /* Initialize the bounding box prior to running xCheck */
+  pik_bbox_init(&pObj->bbox);
+
+  /* Run object-specific code */
+  if( pObj->type->xCheck!=0 ){
+    pObj->type->xCheck(p,pObj);
+    if( p->nErr ) return;
+  }
+
+  /* Compute final bounding box, entry and exit points, center
+  ** point (ptAt) and path for the object
+  */
+  if( pObj->type->isLine ){
+    pObj->aPath = malloc( sizeof(PPoint)*p->nTPath );
+    if( pObj->aPath==0 ){
+      pik_error(p, 0, 0);
+      return;
+    }else{
+      pObj->nPath = p->nTPath;
+      for(i=0; i<p->nTPath; i++){
+        pObj->aPath[i] = p->aTPath[i];
+      }
+    }
+
+    /* "chop" processing:
+    ** If the line goes to the center of an object with an
+    ** xChop method, then use the xChop method to trim the line.
+    */
+    if( pObj->bChop && pObj->nPath>=2 ){
+      int n = pObj->nPath;
+      pik_autochop(p, &pObj->aPath[n-2], &pObj->aPath[n-1], pObj->pTo);
+      pik_autochop(p, &pObj->aPath[1], &pObj->aPath[0], pObj->pFrom);
+    }
+
+    pObj->ptEnter = pObj->aPath[0];
+    pObj->ptExit = pObj->aPath[pObj->nPath-1];
+
+    /* Compute the center of the line based on the bounding box over
+    ** the vertexes.  This is a difference from PIC.  In Pikchr, the
+    ** center of a line is the center of its bounding box. In PIC, the
+    ** center of a line is halfway between its .start and .end.  For
+    ** straight lines, this is the same point, but for multi-segment
+    ** lines the result is usually diferent */
+    for(i=0; i<pObj->nPath; i++){
+      pik_bbox_add_xy(&pObj->bbox, pObj->aPath[i].x, pObj->aPath[i].y);
+    }
+    pObj->ptAt.x = (pObj->bbox.ne.x + pObj->bbox.sw.x)/2.0;
+    pObj->ptAt.y = (pObj->bbox.ne.y + pObj->bbox.sw.y)/2.0;
+
+    /* Reset the width and height of the object to be the width and height
+    ** of the bounding box over vertexes */
+    pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x;
+    pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y;
+
+    /* If this is a polygon (if it has the "close" attribute), then
+    ** adjust the exit point */
+    if( pObj->bClose ){
+      /* For "closed" lines, the .end is one of the .e, .s, .w, or .n
+      ** points of the bounding box, as with block objects. */
+      pik_elem_set_exit(pObj, pObj->inDir);
+    }
+  }else{
+    PNum w2 = pObj->w/2.0;
+    PNum h2 = pObj->h/2.0;
+    pObj->ptEnter = pObj->ptAt;
+    pObj->ptExit = pObj->ptAt;
+    switch( pObj->inDir ){
+      default:         pObj->ptEnter.x -= w2;  break;
+      case DIR_LEFT:   pObj->ptEnter.x += w2;  break;
+      case DIR_UP:     pObj->ptEnter.y -= h2;  break;
+      case DIR_DOWN:   pObj->ptEnter.y += h2;  break;
+    }
+    switch( pObj->outDir ){
+      default:         pObj->ptExit.x += w2;  break;
+      case DIR_LEFT:   pObj->ptExit.x -= w2;  break;
+      case DIR_UP:     pObj->ptExit.y += h2;  break;
+      case DIR_DOWN:   pObj->ptExit.y -= h2;  break;
+    }
+    pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x - w2, pObj->ptAt.y - h2);
+    pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x + w2, pObj->ptAt.y + h2);
+  }
+  p->eDir = (unsigned char)pObj->outDir;
+}
+
+/* Show basic information about each object as a comment in the
+** generated HTML.  Used for testing and debugging.  Activated
+** by the (undocumented) "debug = 1;"
+** command.
+*/
+static void pik_elem_render(Pik *p, PObj *pObj){
+  char *zDir;
+  if( pObj==0 ) return;
+  pik_append(p,"<!-- ", -1);
+  if( pObj->zName ){
+    pik_append_text(p, pObj->zName, -1, 0);
+    pik_append(p, ": ", 2);
+  }
+  pik_append_text(p, pObj->type->zName, -1, 0);
+  if( pObj->nTxt ){
+    pik_append(p, " \"", 2);
+    pik_append_text(p, pObj->aTxt[0].z+1, pObj->aTxt[0].n-2, 1);
+    pik_append(p, "\"", 1);
+  }
+  pik_append_num(p, " w=", pObj->w);
+  pik_append_num(p, " h=", pObj->h);
+  pik_append_point(p, " center=", &pObj->ptAt);
+  pik_append_point(p, " enter=", &pObj->ptEnter);
+  switch( pObj->outDir ){
+    default:        zDir = " right";  break;
+    case DIR_LEFT:  zDir = " left";   break;
+    case DIR_UP:    zDir = " up";     break;
+    case DIR_DOWN:  zDir = " down";   break;
+  }
+  pik_append_point(p, " exit=", &pObj->ptExit);
+  pik_append(p, zDir, -1);
+  pik_append(p, " -->\n", -1);
+}
+
+/* Render a list of objects
+*/
+void pik_elist_render(Pik *p, PList *pList){
+  int i;
+  int iNextLayer = 0;
+  int iThisLayer;
+  int bMoreToDo;
+  int miss = 0;
+  int mDebug = pik_value_int(p, "debug", 5, 0);
+  PNum colorLabel;
+  do{
+    bMoreToDo = 0;
+    iThisLayer = iNextLayer;
+    iNextLayer = 0x7fffffff;
+    for(i=0; i<pList->n; i++){
+      PObj *pObj = pList->a[i];
+      void (*xRender)(Pik*,PObj*);
+      if( pObj->iLayer>iThisLayer ){
+        if( pObj->iLayer<iNextLayer ) iNextLayer = pObj->iLayer;
+        bMoreToDo = 1;
+        continue; /* Defer until another round */
+      }else if( pObj->iLayer<iThisLayer ){
+        continue;
+      }
+      if( mDebug & 1 ) pik_elem_render(p, pObj);
+      xRender = pObj->type->xRender;
+      if( xRender ){
+        xRender(p, pObj);
+      }
+      if( pObj->pSublist ){
+        pik_elist_render(p, pObj->pSublist);
+      }
+    }
+  }while( bMoreToDo );
+
+  /* If the color_debug_label value is defined, then go through
+  ** and paint a dot at every label location */
+  colorLabel = pik_value(p, "debug_label_color", 17, &miss);
+  if( miss==0 && colorLabel>=0.0 ){
+    PObj dot;
+    memset(&dot, 0, sizeof(dot));
+    dot.type = &noopClass;
+    dot.rad = 0.015;
+    dot.sw = 0.015;
+    dot.fill = colorLabel;
+    dot.color = colorLabel;
+    dot.nTxt = 1;
+    dot.aTxt[0].eCode = TP_ABOVE;
+    for(i=0; i<pList->n; i++){
+      PObj *pObj = pList->a[i];
+      if( pObj->zName==0 ) continue;
+      dot.ptAt = pObj->ptAt;
+      dot.aTxt[0].z = pObj->zName;
+      dot.aTxt[0].n = (int)strlen(pObj->zName);
+      dotRender(p, &dot);
+    }
+  }
+}
+
+/* Add all objects of the list pList to the bounding box
+*/
+static void pik_bbox_add_elist(Pik *p, PList *pList, PNum wArrow){
+  int i;
+  for(i=0; i<pList->n; i++){
+    PObj *pObj = pList->a[i];
+    if( pObj->sw>0.0 ) pik_bbox_addbox(&p->bbox, &pObj->bbox);
+    pik_append_txt(p, pObj, &p->bbox);
+    if( pObj->pSublist ) pik_bbox_add_elist(p, pObj->pSublist, wArrow);
+
+
+    /* Expand the bounding box to account for arrowheads on lines */
+    if( pObj->type->isLine && pObj->nPath>0 ){
+      if( pObj->larrow ){
+        pik_bbox_addellipse(&p->bbox, pObj->aPath[0].x, pObj->aPath[0].y,
+                            wArrow, wArrow);
+      }
+      if( pObj->rarrow ){
+        int j = pObj->nPath-1;
+        pik_bbox_addellipse(&p->bbox, pObj->aPath[j].x, pObj->aPath[j].y,
+                            wArrow, wArrow);
+      }
+    }
+  }
+}
+
+/* Recompute key layout parameters from variables. */
+static void pik_compute_layout_settings(Pik *p){
+  PNum thickness;  /* Line thickness */
+  PNum wArrow;     /* Width of arrowheads */
+
+  /* Set up rendering parameters */
+  if( p->bLayoutVars ) return;
+  thickness = pik_value(p,"thickness",9,0);
+  if( thickness<=0.01 ) thickness = 0.01;
+  wArrow = 0.5*pik_value(p,"arrowwid",8,0);
+  p->wArrow = wArrow/thickness;
+  p->hArrow = pik_value(p,"arrowht",7,0)/thickness;
+  p->fontScale = pik_value(p,"fontscale",9,0);
+  if( p->fontScale<=0.0 ) p->fontScale = 1.0;
+  p->rScale = 144.0;
+  p->charWidth = pik_value(p,"charwid",7,0)*p->fontScale;
+  p->charHeight = pik_value(p,"charht",6,0)*p->fontScale;
+  p->bLayoutVars = 1;
+}
+
+/* Render a list of objects.  Write the SVG into p->zOut.
+** Delete the input object_list before returnning.
+*/
+static void pik_render(Pik *p, PList *pList){
+  if( pList==0 ) return;
+  if( p->nErr==0 ){
+    PNum thickness;  /* Stroke width */
+    PNum margin;     /* Extra bounding box margin */
+    PNum w, h;       /* Drawing width and height */
+    PNum wArrow;
+    PNum pikScale;   /* Value of the "scale" variable */
+    int miss = 0;
+
+    /* Set up rendering parameters */
+    pik_compute_layout_settings(p);
+    thickness = pik_value(p,"thickness",9,0);
+    if( thickness<=0.01 ) thickness = 0.01;
+    margin = pik_value(p,"margin",6,0);
+    margin += thickness;
+    wArrow = p->wArrow*thickness;
+    miss = 0;
+    p->fgcolor = pik_value_int(p,"fgcolor",7,&miss);
+    if( miss ){
+      PToken t;
+      t.z = "fgcolor";
+      t.n = 7;
+      p->fgcolor = pik_round(pik_lookup_color(0, &t));
+    }
+    miss = 0;
+    p->bgcolor = pik_value_int(p,"bgcolor",7,&miss);
+    if( miss ){
+      PToken t;
+      t.z = "bgcolor";
+      t.n = 7;
+      p->bgcolor = pik_round(pik_lookup_color(0, &t));
+    }
+
+    /* Compute a bounding box over all objects so that we can know
+    ** how big to declare the SVG canvas */
+    pik_bbox_init(&p->bbox);
+    pik_bbox_add_elist(p, pList, wArrow);
+
+    /* Expand the bounding box slightly to account for line thickness
+    ** and the optional "margin = EXPR" setting. */
+    p->bbox.ne.x += margin + pik_value(p,"rightmargin",11,0);
+    p->bbox.ne.y += margin + pik_value(p,"topmargin",9,0);
+    p->bbox.sw.x -= margin + pik_value(p,"leftmargin",10,0);
+    p->bbox.sw.y -= margin + pik_value(p,"bottommargin",12,0);
+
+    /* Output the SVG */
+    pik_append(p, "<svg xmlns='http://www.w3.org/2000/svg'",-1);
+    if( p->zClass ){
+      pik_append(p, " class=\"", -1);
+      pik_append(p, p->zClass, -1);
+      pik_append(p, "\"", 1);
+    }
+    w = p->bbox.ne.x - p->bbox.sw.x;
+    h = p->bbox.ne.y - p->bbox.sw.y;
+    p->wSVG = pik_round(p->rScale*w);
+    p->hSVG = pik_round(p->rScale*h);
+    pikScale = pik_value(p,"scale",5,0);
+    if( pikScale>=0.001 && pikScale<=1000.0
+     && (pikScale<0.99 || pikScale>1.01)
+    ){
+      p->wSVG = pik_round(p->wSVG*pikScale);
+      p->hSVG = pik_round(p->hSVG*pikScale);
+      pik_append_num(p, " width=\"", p->wSVG);
+      pik_append_num(p, "\" height=\"", p->hSVG);
+      pik_append(p, "\"", 1);
+    }
+    pik_append_dis(p, " viewBox=\"0 0 ",w,"");
+    pik_append_dis(p, " ",h,"\">\n");
+    pik_elist_render(p, pList);
+    pik_append(p,"</svg>\n", -1);
+  }else{
+    p->wSVG = -1;
+    p->hSVG = -1;
+  }
+  pik_elist_free(p, pList);
+}
+
+
+
+/*
+** An array of this structure defines a list of keywords.
+*/
+typedef struct PikWord {
+  char *zWord;             /* Text of the keyword */
+  unsigned char nChar;     /* Length of keyword text in bytes */
+  unsigned char eType;     /* Token code */
+  unsigned char eCode;     /* Extra code for the token */
+  unsigned char eEdge;     /* CP_* code for corner/edge keywords */
+} PikWord;
+
+/*
+** Keywords
+*/
+static const PikWord pik_keywords[] = {
+  { "above",      5,   T_ABOVE,     0,         0        },
+  { "abs",        3,   T_FUNC1,     FN_ABS,    0        },
+  { "aligned",    7,   T_ALIGNED,   0,         0        },
+  { "and",        3,   T_AND,       0,         0        },
+  { "as",         2,   T_AS,        0,         0        },
+  { "assert",     6,   T_ASSERT,    0,         0        },
+  { "at",         2,   T_AT,        0,         0        },
+  { "behind",     6,   T_BEHIND,    0,         0        },
+  { "below",      5,   T_BELOW,     0,         0        },
+  { "between",    7,   T_BETWEEN,   0,         0        },
+  { "big",        3,   T_BIG,       0,         0        },
+  { "bold",       4,   T_BOLD,      0,         0        },
+  { "bot",        3,   T_EDGEPT,    0,         CP_S     },
+  { "bottom",     6,   T_BOTTOM,    0,         CP_S     },
+  { "c",          1,   T_EDGEPT,    0,         CP_C     },
+  { "ccw",        3,   T_CCW,       0,         0        },
+  { "center",     6,   T_CENTER,    0,         CP_C     },
+  { "chop",       4,   T_CHOP,      0,         0        },
+  { "close",      5,   T_CLOSE,     0,         0        },
+  { "color",      5,   T_COLOR,     0,         0        },
+  { "cos",        3,   T_FUNC1,     FN_COS,    0        },
+  { "cw",         2,   T_CW,        0,         0        },
+  { "dashed",     6,   T_DASHED,    0,         0        },
+  { "define",     6,   T_DEFINE,    0,         0        },
+  { "diameter",   8,   T_DIAMETER,  0,         0        },
+  { "dist",       4,   T_DIST,      0,         0        },
+  { "dotted",     6,   T_DOTTED,    0,         0        },
+  { "down",       4,   T_DOWN,      DIR_DOWN,  0        },
+  { "e",          1,   T_EDGEPT,    0,         CP_E     },
+  { "east",       4,   T_EDGEPT,    0,         CP_E     },
+  { "end",        3,   T_END,       0,         CP_END   },
+  { "even",       4,   T_EVEN,      0,         0        },
+  { "fill",       4,   T_FILL,      0,         0        },
+  { "first",      5,   T_NTH,       0,         0        },
+  { "fit",        3,   T_FIT,       0,         0        },
+  { "from",       4,   T_FROM,      0,         0        },
+  { "go",         2,   T_GO,        0,         0        },
+  { "heading",    7,   T_HEADING,   0,         0        },
+  { "height",     6,   T_HEIGHT,    0,         0        },
+  { "ht",         2,   T_HEIGHT,    0,         0        },
+  { "in",         2,   T_IN,        0,         0        },
+  { "int",        3,   T_FUNC1,     FN_INT,    0        },
+  { "invis",      5,   T_INVIS,     0,         0        },
+  { "invisible",  9,   T_INVIS,     0,         0        },
+  { "italic",     6,   T_ITALIC,    0,         0        },
+  { "last",       4,   T_LAST,      0,         0        },
+  { "left",       4,   T_LEFT,      DIR_LEFT,  CP_W     },
+  { "ljust",      5,   T_LJUST,     0,         0        },
+  { "max",        3,   T_FUNC2,     FN_MAX,    0        },
+  { "min",        3,   T_FUNC2,     FN_MIN,    0        },
+  { "n",          1,   T_EDGEPT,    0,         CP_N     },
+  { "ne",         2,   T_EDGEPT,    0,         CP_NE    },
+  { "north",      5,   T_EDGEPT,    0,         CP_N     },
+  { "nw",         2,   T_EDGEPT,    0,         CP_NW    },
+  { "of",         2,   T_OF,        0,         0        },
+  { "previous",   8,   T_LAST,      0,         0,       },
+  { "print",      5,   T_PRINT,     0,         0        },
+  { "rad",        3,   T_RADIUS,    0,         0        },
+  { "radius",     6,   T_RADIUS,    0,         0        },
+  { "right",      5,   T_RIGHT,     DIR_RIGHT, CP_E     },
+  { "rjust",      5,   T_RJUST,     0,         0        },
+  { "s",          1,   T_EDGEPT,    0,         CP_S     },
+  { "same",       4,   T_SAME,      0,         0        },
+  { "se",         2,   T_EDGEPT,    0,         CP_SE    },
+  { "sin",        3,   T_FUNC1,     FN_SIN,    0        },
+  { "small",      5,   T_SMALL,     0,         0        },
+  { "solid",      5,   T_SOLID,     0,         0        },
+  { "south",      5,   T_EDGEPT,    0,         CP_S     },
+  { "sqrt",       4,   T_FUNC1,     FN_SQRT,   0        },
+  { "start",      5,   T_START,     0,         CP_START },
+  { "sw",         2,   T_EDGEPT,    0,         CP_SW    },
+  { "t",          1,   T_TOP,       0,         CP_N     },
+  { "the",        3,   T_THE,       0,         0        },
+  { "then",       4,   T_THEN,      0,         0        },
+  { "thick",      5,   T_THICK,     0,         0        },
+  { "thickness",  9,   T_THICKNESS, 0,         0        },
+  { "thin",       4,   T_THIN,      0,         0        },
+  { "this",       4,   T_THIS,      0,         0        },
+  { "to",         2,   T_TO,        0,         0        },
+  { "top",        3,   T_TOP,       0,         CP_N     },
+  { "until",      5,   T_UNTIL,     0,         0        },
+  { "up",         2,   T_UP,        DIR_UP,    0        },
+  { "vertex",     6,   T_VERTEX,    0,         0        },
+  { "w",          1,   T_EDGEPT,    0,         CP_W     },
+  { "way",        3,   T_WAY,       0,         0        },
+  { "west",       4,   T_EDGEPT,    0,         CP_W     },
+  { "wid",        3,   T_WIDTH,     0,         0        },
+  { "width",      5,   T_WIDTH,     0,         0        },
+  { "with",       4,   T_WITH,      0,         0        },
+  { "x",          1,   T_X,         0,         0        },
+  { "y",          1,   T_Y,         0,         0        },
+};
+
+/*
+** Search a PikWordlist for the given keyword.  Return a pointer to the
+** keyword entry found.  Or return 0 if not found.
+*/
+static const PikWord *pik_find_word(
+  const char *zIn,              /* Word to search for */
+  int n,                        /* Length of zIn */
+  const PikWord *aList,         /* List to search */
+  int nList                     /* Number of entries in aList */
+){
+  int first = 0;
+  int last = nList-1;
+  while( first<=last ){
+    int mid = (first + last)/2;
+    int sz = aList[mid].nChar;
+    int c = strncmp(zIn, aList[mid].zWord, sz<n ? sz : n);
+    if( c==0 ){
+      c = n - sz;
+      if( c==0 ) return &aList[mid];
+    }
+    if( c<0 ){
+      last = mid-1;
+    }else{
+      first = mid+1;
+    }
+  }
+  return 0;
+}
+
+/*
+** Set a symbolic debugger breakpoint on this routine to receive a
+** breakpoint when the "#breakpoint" token is parsed.
+*/
+static void pik_breakpoint(const unsigned char *z){
+  /* Prevent C compilers from optimizing out this routine. */
+  if( z[2]=='X' ) exit(1);
+}
+
+
+/*
+** Return the length of next token.  The token starts on
+** the pToken->z character.  Fill in other fields of the
+** pToken object as appropriate.
+*/
+static int pik_token_length(PToken *pToken, int bAllowCodeBlock){
+  const unsigned char *z = (const unsigned char*)pToken->z;
+  int i;
+  unsigned char c, c2;
+  switch( z[0] ){
+    case '\\': {
+      pToken->eType = T_WHITESPACE;
+      for(i=1; z[i]=='\r' || z[i]==' ' || z[i]=='\t'; i++){}
+      if( z[i]=='\n'  ) return i+1;
+      pToken->eType = T_ERROR;
+      return 1;
+    }
+    case ';':
+    case '\n': {
+      pToken->eType = T_EOL;
+      return 1;
+    }
+    case '"': {
+      for(i=1; (c = z[i])!=0; i++){
+        if( c=='\\' ){ 
+          if( z[i+1]==0 ) break;
+          i++;
+          continue;
+        }
+        if( c=='"' ){
+          pToken->eType = T_STRING;
+          return i+1;
+        }
+      }
+      pToken->eType = T_ERROR;
+      return i;
+    }
+    case ' ':
+    case '\t':
+    case '\f':
+    case '\r': {
+      for(i=1; (c = z[i])==' ' || c=='\t' || c=='\r' || c=='\t'; i++){}
+      pToken->eType = T_WHITESPACE;
+      return i;
+    }
+    case '#': {
+      for(i=1; (c = z[i])!=0 && c!='\n'; i++){}
+      pToken->eType = T_WHITESPACE;
+      /* If the comment is "#breakpoint" then invoke the pik_breakpoint()
+      ** routine.  The pik_breakpoint() routie is a no-op that serves as
+      ** a convenient place to set a gdb breakpoint when debugging. */
+      if( strncmp((const char*)z,"#breakpoint",11)==0 ) pik_breakpoint(z);
+      return i;
+    }
+    case '/': {
+      if( z[1]=='*' ){
+        for(i=2; z[i]!=0 && (z[i]!='*' || z[i+1]!='/'); i++){}
+        if( z[i]=='*' ){
+          pToken->eType = T_WHITESPACE;
+          return i+2;
+        }else{
+          pToken->eType = T_ERROR;
+          return i;
+        }
+      }else if( z[1]=='/' ){
+        for(i=2; z[i]!=0 && z[i]!='\n'; i++){}
+        pToken->eType = T_WHITESPACE;
+        return i;
+      }else if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_SLASH;
+        return 2;
+      }else{
+        pToken->eType = T_SLASH;
+        return 1;
+      }
+    }
+    case '+': {
+      if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_PLUS;
+        return 2;
+      }
+      pToken->eType = T_PLUS;
+      return 1;
+    }
+    case '*': {
+      if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_STAR;
+        return 2;
+      }
+      pToken->eType = T_STAR;
+      return 1;
+    }
+    case '%': {   pToken->eType = T_PERCENT; return 1; }
+    case '(': {   pToken->eType = T_LP;      return 1; }
+    case ')': {   pToken->eType = T_RP;      return 1; }
+    case '[': {   pToken->eType = T_LB;      return 1; }
+    case ']': {   pToken->eType = T_RB;      return 1; }
+    case ',': {   pToken->eType = T_COMMA;   return 1; }
+    case ':': {   pToken->eType = T_COLON;   return 1; }
+    case '>': {   pToken->eType = T_GT;      return 1; }
+    case '=': {
+       if( z[1]=='=' ){
+         pToken->eType = T_EQ;
+         return 2;
+       }
+       pToken->eType = T_ASSIGN;
+       pToken->eCode = T_ASSIGN;
+       return 1;
+    }
+    case '-': {
+      if( z[1]=='>' ){
+        pToken->eType = T_RARROW;
+        return 2;
+      }else if( z[1]=='=' ){
+        pToken->eType = T_ASSIGN;
+        pToken->eCode = T_MINUS;
+        return 2;
+      }else{
+        pToken->eType = T_MINUS;
+        return 1;
+      }
+    }
+    case '<': { 
+      if( z[1]=='-' ){
+         if( z[2]=='>' ){
+           pToken->eType = T_LRARROW;
+           return 3;
+         }else{
+           pToken->eType = T_LARROW;
+           return 2;
+         }
+      }else{
+        pToken->eType = T_LT;
+        return 1;
+      }
+    }
+    case 0xe2: {
+      if( z[1]==0x86 ){
+        if( z[2]==0x90 ){
+          pToken->eType = T_LARROW;   /* <- */
+          return 3;
+        }
+        if( z[2]==0x92 ){
+          pToken->eType = T_RARROW;   /* -> */
+          return 3;
+        }
+        if( z[2]==0x94 ){
+          pToken->eType = T_LRARROW;  /* <-> */
+          return 3;
+        }
+      }
+      pToken->eType = T_ERROR;
+      return 1;
+    }
+    case '{': {
+      int len, depth;
+      i = 1;
+      if( bAllowCodeBlock ){
+        depth = 1;
+        while( z[i] && depth>0 ){
+          PToken x;
+          x.z = (char*)(z+i);
+          len = pik_token_length(&x, 0);
+          if( len==1 ){
+            if( z[i]=='{' ) depth++;
+            if( z[i]=='}' ) depth--;
+          }
+          i += len;
+        }
+      }else{
+        depth = 0;
+      }
+      if( depth ){
+        pToken->eType = T_ERROR;
+        return 1;
+      }
+      pToken->eType = T_CODEBLOCK;
+      return i;
+    }
+    case '&': {
+      static const struct {
+         int nByte;            /* Number of bytes in zEntity */
+         int eCode;            /* Corresponding token code */
+         const char *zEntity;  /* Name of the HTML entity */
+      } aEntity[] = {
+                      /*   123456789 1234567 */
+         { 6,  T_RARROW,  "&rarr;"           },   /* Same as -> */
+         { 12, T_RARROW,  "&rightarrow;"     },   /* Same as -> */
+         { 6,  T_LARROW,  "&larr;"           },   /* Same as <- */
+         { 11, T_LARROW,  "&leftarrow;"      },   /* Same as <- */
+         { 16, T_LRARROW, "&leftrightarrow;" },   /* Same as <-> */
+      };
+      unsigned int i;
+      for(i=0; i<sizeof(aEntity)/sizeof(aEntity[0]); i++){
+        if( strncmp((const char*)z,aEntity[i].zEntity,aEntity[i].nByte)==0 ){
+          pToken->eType = aEntity[i].eCode;
+          return aEntity[i].nByte;
+        }
+      }
+      pToken->eType = T_ERROR;
+      return 1;
+    }
+    default: {
+      c = z[0];
+      if( c=='.' ){
+        unsigned char c1 = z[1];
+        if( islower(c1) ){
+          const PikWord *pFound;
+          for(i=2; (c = z[i])>='a' && c<='z'; i++){}
+          pFound = pik_find_word((const char*)z+1, i-1,
+                                    pik_keywords, count(pik_keywords));
+          if( pFound && (pFound->eEdge>0 ||
+                         pFound->eType==T_EDGEPT ||
+                         pFound->eType==T_START ||
+                         pFound->eType==T_END )
+          ){
+            /* Dot followed by something that is a 2-D place value */
+            pToken->eType = T_DOT_E;
+          }else if( pFound && (pFound->eType==T_X || pFound->eType==T_Y) ){
+            /* Dot followed by "x" or "y" */
+            pToken->eType = T_DOT_XY;
+          }else{
+            /* Any other "dot" */
+            pToken->eType = T_DOT_L;
+          }
+          return 1;
+        }else if( isdigit(c1) ){
+          i = 0;
+          /* no-op.  Fall through to number handling */
+        }else if( isupper(c1) ){
+          for(i=2; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+          pToken->eType = T_DOT_U;
+          return 1;
+        }else{
+          pToken->eType = T_ERROR;
+          return 1;
+        }
+      }
+      if( (c>='0' && c<='9') || c=='.' ){
+        int nDigit;
+        int isInt = 1;
+        if( c!='.' ){
+          nDigit = 1;
+          for(i=1; (c = z[i])>='0' && c<='9'; i++){ nDigit++; }
+          if( i==1 && (c=='x' || c=='X') ){
+            for(i=2; (c = z[i])!=0 && isxdigit(c); i++){}
+            pToken->eType = T_NUMBER;
+            return i;
+          }
+        }else{
+          isInt = 0;
+          nDigit = 0;
+          i = 0;
+        }
+        if( c=='.' ){
+          isInt = 0;
+          for(i++; (c = z[i])>='0' && c<='9'; i++){ nDigit++; }
+        }
+        if( nDigit==0 ){
+          pToken->eType = T_ERROR;
+          return i;
+        }
+        if( c=='e' || c=='E' ){
+          int iBefore = i;
+          i++;
+          c2 = z[i];
+          if( c2=='+' || c2=='-' ){
+            i++;
+            c2 = z[i];
+          }
+          if( c2<'0' || c>'9' ){
+            /* This is not an exp */
+            i = iBefore;
+          }else{
+            i++;
+            isInt = 0;
+            while( (c = z[i])>='0' && c<='9' ){ i++; }
+          }
+        }
+        c2 = c ? z[i+1] : 0;
+        if( isInt ){
+          if( (c=='t' && c2=='h')
+           || (c=='r' && c2=='d')
+           || (c=='n' && c2=='d')
+           || (c=='s' && c2=='t')
+          ){
+            pToken->eType = T_NTH;
+            return i+2;
+          }
+        }
+        if( (c=='i' && c2=='n')
+         || (c=='c' && c2=='m')
+         || (c=='m' && c2=='m')
+         || (c=='p' && c2=='t')
+         || (c=='p' && c2=='x')
+         || (c=='p' && c2=='c')
+        ){
+          i += 2;
+        }
+        pToken->eType = T_NUMBER;
+        return i;
+      }else if( islower(c) ){
+        const PikWord *pFound;
+        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+        pFound = pik_find_word((const char*)z, i,
+                               pik_keywords, count(pik_keywords));
+        if( pFound ){
+          pToken->eType = pFound->eType;
+          pToken->eCode = pFound->eCode;
+          pToken->eEdge = pFound->eEdge;
+          return i;
+        }
+        pToken->n = i;
+        if( pik_find_class(pToken)!=0 ){
+          pToken->eType = T_CLASSNAME;
+        }else{
+          pToken->eType = T_ID;
+        }
+        return i;
+      }else if( c>='A' && c<='Z' ){
+        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+        pToken->eType = T_PLACENAME;
+        return i;
+      }else if( c=='$' && z[1]>='1' && z[1]<='9' && !isdigit(z[2]) ){
+        pToken->eType = T_PARAMETER;
+        pToken->eCode = z[1] - '1';
+        return 2;
+      }else if( c=='_' || c=='$' || c=='@' ){
+        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
+        pToken->eType = T_ID;
+        return i;
+      }else{
+        pToken->eType = T_ERROR;
+        return 1;
+      }
+    }
+  }
+}
+
+/*
+** Return a pointer to the next non-whitespace token after pThis.
+** This is used to help form error messages.
+*/
+static PToken pik_next_semantic_token(PToken *pThis){
+  PToken x;
+  int sz;
+  int i = pThis->n;
+  memset(&x, 0, sizeof(x));
+  x.z = pThis->z;
+  while(1){
+    x.z = pThis->z + i;
+    sz = pik_token_length(&x, 1);
+    if( x.eType!=T_WHITESPACE ){
+      x.n = sz;
+      return x;
+    }
+    i += sz;
+  }
+}
+
+/* Parser arguments to a macro invocation
+**
+**     (arg1, arg2, ...)
+**
+** Arguments are comma-separated, except that commas within string
+** literals or with (...), {...}, or [...] do not count.  The argument
+** list begins and ends with parentheses.  There can be at most 9
+** arguments.
+**
+** Return the number of bytes in the argument list.
+*/
+static unsigned int pik_parse_macro_args(
+  Pik *p,
+  const char *z,     /* Start of the argument list */
+  int n,             /* Available bytes */
+  PToken *args,      /* Fill in with the arguments */
+  PToken *pOuter     /* Arguments of the next outer context, or NULL */
+){
+  int nArg = 0;
+  int i, j, sz;
+  int iStart;
+  int depth = 0;
+  PToken x;
+  if( z[0]!='(' ) return 0;
+  args[0].z = z+1;
+  iStart = 1;
+  for(i=1; i<n && z[i]!=')'; i+=sz){
+    x.z = z+i;
+    sz = pik_token_length(&x, 0);
+    if( sz!=1 ) continue;
+    if( z[i]==',' && depth<=0 ){
+      args[nArg].n = i - iStart;
+      if( nArg==8 ){
+        x.z = z;
+        x.n = 1;
+        pik_error(p, &x, "too many macro arguments - max 9");
+        return 0;
+      }
+      nArg++;
+      args[nArg].z = z+i+1;
+      iStart = i+1;
+      depth = 0;
+    }else if( z[i]=='(' || z[i]=='{' || z[i]=='[' ){
+      depth++;
+    }else if( z[i]==')' || z[i]=='}' || z[i]==']' ){
+      depth--;
+    }
+  }
+  if( z[i]==')' ){
+    args[nArg].n = i - iStart;
+    /* Remove leading and trailing whitespace from each argument.
+    ** If what remains is one of $1, $2, ... $9 then transfer the
+    ** corresponding argument from the outer context */
+    for(j=0; j<=nArg; j++){
+      PToken *t = &args[j];
+      while( t->n>0 && isspace(t->z[0]) ){ t->n--; t->z++; }
+      while( t->n>0 && isspace(t->z[t->n-1]) ){ t->n--; }
+      if( t->n==2 && t->z[0]=='$' && t->z[1]>='1' && t->z[1]<='9' ){
+        if( pOuter ) *t = pOuter[t->z[1]-'1'];
+        else t->n = 0;
+      }
+    }
+    return i+1;
+  }
+  x.z = z;
+  x.n = 1;
+  pik_error(p, &x, "unterminated macro argument list");
+  return 0;
+}
+
+/*
+** Split up the content of a PToken into multiple tokens and
+** send each to the parser.
+*/
+void pik_tokenize(Pik *p, PToken *pIn, yyParser *pParser, PToken *aParam){
+  unsigned int i;
+  int sz = 0;
+  PToken token;
+  PMacro *pMac;
+  for(i=0; i<pIn->n && pIn->z[i] && p->nErr==0; i+=sz){
+    token.eCode = 0;
+    token.eEdge = 0;
+    token.z = pIn->z + i;
+    sz = pik_token_length(&token, 1);
+    if( token.eType==T_WHITESPACE ){
+      /* no-op */
+    }else if( sz>50000 ){
+      token.n = 1;
+      pik_error(p, &token, "token is too long - max length 50000 bytes");
+      break;
+    }else if( token.eType==T_ERROR ){
+      token.n = (unsigned short)(sz & 0xffff);
+      pik_error(p, &token, "unrecognized token");
+      break;
+    }else if( sz+i>pIn->n ){
+      token.n = pIn->n - i;
+      pik_error(p, &token, "syntax error");
+      break;
+    }else if( token.eType==T_PARAMETER ){
+      /* Substitute a parameter into the input stream */
+      if( aParam==0 || aParam[token.eCode].n==0 ){
+        continue;
+      }
+      token.n = (unsigned short)(sz & 0xffff);
+      if( p->nCtx>=count(p->aCtx) ){
+        pik_error(p, &token, "macros nested too deep");
+      }else{
+        p->aCtx[p->nCtx++] = token;
+        pik_tokenize(p, &aParam[token.eCode], pParser, 0);
+        p->nCtx--;
+      }
+    }else if( token.eType==T_ID
+               && (token.n = (unsigned short)(sz & 0xffff), 
+                   (pMac = pik_find_macro(p,&token))!=0)
+    ){
+      PToken args[9];
+      unsigned int j = i+sz;
+      if( pMac->inUse ){
+        pik_error(p, &pMac->macroName, "recursive macro definition");
+        break;
+      }
+      token.n = (short int)(sz & 0xffff);
+      if( p->nCtx>=count(p->aCtx) ){
+        pik_error(p, &token, "macros nested too deep");
+        break;
+      } 
+      pMac->inUse = 1;
+      memset(args, 0, sizeof(args));
+      p->aCtx[p->nCtx++] = token;
+      sz += pik_parse_macro_args(p, pIn->z+j, pIn->n-j, args, aParam);
+      pik_tokenize(p, &pMac->macroBody, pParser, args);
+      p->nCtx--;
+      pMac->inUse = 0;
+    }else{
+#if 0
+      printf("******** Token %s (%d): \"%.*s\" **************\n",
+             yyTokenName[token.eType], token.eType,
+             (int)(isspace(token.z[0]) ? 0 : sz), token.z);
+#endif
+      token.n = (unsigned short)(sz & 0xffff);
+      if( p->nToken++ > PIKCHR_TOKEN_LIMIT ){
+        pik_error(p, &token, "script is too complex");
+        break;
+      }
+      pik_parser(pParser, token.eType, token);
+    }
+  }
+}
+
+/*
+** Parse the PIKCHR script contained in zText[].  Return a rendering.  Or
+** if an error is encountered, return the error text.  The error message
+** is HTML formatted.  So regardless of what happens, the return text
+** is safe to be insertd into an HTML output stream.
+**
+** If pnWidth and pnHeight are not NULL, then this routine writes the
+** width and height of the <SVG> object into the integers that they
+** point to.  A value of -1 is written if an error is seen.
+**
+** If zClass is not NULL, then it is a class name to be included in
+** the <SVG> markup.
+**
+** The returned string is contained in memory obtained from malloc()
+** and should be released by the caller.
+*/
+char *pikchr(
+  const char *zText,     /* Input PIKCHR source text.  zero-terminated */
+  const char *zClass,    /* Add class="%s" to <svg> markup */
+  unsigned int mFlags,   /* Flags used to influence rendering behavior */
+  int *pnWidth,          /* Write width of <svg> here, if not NULL */
+  int *pnHeight          /* Write height here, if not NULL */
+){
+  Pik s;
+  yyParser sParse;
+
+  memset(&s, 0, sizeof(s));
+  s.sIn.z = zText;
+  s.sIn.n = (unsigned int)strlen(zText);
+  s.eDir = DIR_RIGHT;
+  s.zClass = zClass;
+  s.mFlags = mFlags;
+  pik_parserInit(&sParse, &s);
+#if 0
+  pik_parserTrace(stdout, "parser: ");
+#endif
+  pik_tokenize(&s, &s.sIn, &sParse, 0);
+  if( s.nErr==0 ){
+    PToken token;
+    memset(&token,0,sizeof(token));
+    token.z = zText + (s.sIn.n>0 ? s.sIn.n-1 : 0);
+    token.n = 1;
+    pik_parser(&sParse, 0, token);
+  }
+  pik_parserFinalize(&sParse);
+  if( s.zOut==0 && s.nErr==0 ){
+    pik_append(&s, "<!-- empty pikchr diagram -->\n", -1);
+  }
+  while( s.pVar ){
+    PVar *pNext = s.pVar->pNext;
+    free(s.pVar);
+    s.pVar = pNext;
+  }
+  while( s.pMacros ){
+    PMacro *pNext = s.pMacros->pNext;
+    free(s.pMacros);
+    s.pMacros = pNext;
+  }
+  if( pnWidth ) *pnWidth = s.nErr ? -1 : s.wSVG;
+  if( pnHeight ) *pnHeight = s.nErr ? -1 : s.hSVG;
+  if( s.zOut ){
+    s.zOut[s.nOut] = 0;
+    s.zOut = realloc(s.zOut, s.nOut+1);
+  }
+  return s.zOut;
+}
+
+#if defined(PIKCHR_FUZZ)
+#include <stdint.h>
+int LLVMFuzzerTestOneInput(const uint8_t *aData, size_t nByte){
+  int w,h;
+  char *zIn, *zOut;
+  unsigned int mFlags = nByte & 3;
+  zIn = malloc( nByte + 1 );
+  if( zIn==0 ) return 0;
+  memcpy(zIn, aData, nByte);
+  zIn[nByte] = 0;
+  zOut = pikchr(zIn, "pikchr", mFlags, &w, &h);
+  free(zIn);
+  free(zOut);
+  return 0;
+}
+#endif /* PIKCHR_FUZZ */
+
+#if defined(PIKCHR_SHELL)
+/* Print a usage comment for the shell and exit. */
+static void usage(const char *argv0){
+  fprintf(stderr, "usage: %s [OPTIONS] FILE ...\n", argv0);
+  fprintf(stderr,
+    "Convert Pikchr input files into SVG.  Filename \"-\" means stdin.\n"
+    "All output goes to stdout.\n"
+    "Options:\n"
+    "   --dont-stop      Process all files even if earlier files have errors\n"
+    "   --svg-only       Emit raw SVG without the HTML wrapper\n"
+  );
+  exit(1);
+}
+
+/* Send text to standard output, but escape HTML markup */
+static void print_escape_html(const char *z){
+  int j;
+  char c;
+  while( z[0]!=0 ){
+    for(j=0; (c = z[j])!=0 && c!='<' && c!='>' && c!='&'; j++){}
+    if( j ) printf("%.*s", j, z);
+    z += j+1;
+    j = -1;
+    if( c=='<' ){
+      printf("&lt;");
+    }else if( c=='>' ){
+      printf("&gt;");
+    }else if( c=='&' ){
+      printf("&amp;");
+    }else if( c==0 ){
+      break;
+    }
+  }
+}
+
+/* Read the content of file zFilename into memory obtained from malloc()
+** Return the memory.  If something goes wrong (ex: the file does not exist
+** or cannot be opened) put an error message on stderr and return NULL.
+**
+** If the filename is "-" read stdin.
+*/
+static char *readFile(const char *zFilename){
+  FILE *in;
+  size_t n;
+  size_t nUsed = 0;
+  size_t nAlloc = 0;
+  char *z = 0, *zNew = 0;
+  in = strcmp(zFilename,"-")==0 ? stdin : fopen(zFilename, "rb");
+  if( in==0 ){
+    fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename);
+    return 0;
+  }
+  while(1){
+    if( nUsed+2>=nAlloc ){
+      nAlloc = nAlloc*2 + 4000;
+      zNew = realloc(z, nAlloc);
+    }
+    if( zNew==0 ){
+      free(z);
+      fprintf(stderr, "out of memory trying to allocate %lld bytes\n",
+              (long long int)nAlloc);
+      exit(1);
+    }
+    z = zNew;
+    n = fread(z+nUsed, 1, nAlloc-nUsed-1, in);
+    if( n<=0 ){
+      break;
+    }
+    nUsed += n;
+  }
+  if( in!=stdin ) fclose(in);
+  z[nUsed] = 0;
+  return z;
+}
+
+
+/* Testing interface
+**
+** Generate HTML on standard output that displays both the original
+** input text and the rendered SVG for all files named on the command
+** line.
+*/
+int main(int argc, char **argv){
+  int i;
+  int bSvgOnly = 0;            /* Output SVG only.  No HTML wrapper */
+  int bDontStop = 0;           /* Continue in spite of errors */
+  int exitCode = 0;            /* What to return */
+  int mFlags = 0;              /* mFlags argument to pikchr() */
+  const char *zStyle = "";     /* Extra styling */
+  const char *zHtmlHdr = 
+    "<!DOCTYPE html>\n"
+    "<html lang=\"en-US\">\n"
+    "<head>\n<title>PIKCHR Test</title>\n"
+    "<style>\n"
+    "  .hidden {\n"
+    "     position: absolute !important;\n"
+    "     opacity: 0 !important;\n"
+    "     pointer-events: none !important;\n"
+    "     display: none !important;\n"
+    "  }\n"
+    "</style>\n"
+    "<script>\n"
+    "  function toggleHidden(id){\n"
+    "    for(var c of document.getElementById(id).children){\n"
+    "      c.classList.toggle('hidden');\n"
+    "    }\n"
+    "  }\n"
+    "</script>\n"
+    "<meta charset=\"utf-8\">\n"
+    "</head>\n"
+    "<body>\n"
+  ;
+  if( argc<2 ) usage(argv[0]);
+  for(i=1; i<argc; i++){
+    char *zIn;
+    char *zOut;
+    int w, h;
+
+    if( argv[i][0]=='-' && argv[i][1]!=0 ){
+      char *z = argv[i];
+      z++;
+      if( z[0]=='-' ) z++;
+      if( strcmp(z,"dont-stop")==0 ){
+        bDontStop = 1;
+      }else
+      if( strcmp(z,"dark-mode")==0 ){
+        zStyle = "color:white;background-color:black;";
+        mFlags |= PIKCHR_DARK_MODE;
+      }else
+      if( strcmp(z,"svg-only")==0 ){
+        if( zHtmlHdr==0 ){
+          fprintf(stderr, "the \"%s\" option must come first\n",argv[i]);
+          exit(1);
+        }
+        bSvgOnly = 1;
+        mFlags |= PIKCHR_PLAINTEXT_ERRORS;
+      }else
+      {
+        fprintf(stderr,"unknown option: \"%s\"\n", argv[i]);
+        usage(argv[0]);
+      }
+      continue;
+    }
+    zIn = readFile(argv[i]);
+    if( zIn==0 ) continue;
+    zOut = pikchr(zIn, "pikchr", mFlags, &w, &h);
+    if( w<0 ) exitCode = 1;
+    if( zOut==0 ){
+      fprintf(stderr, "pikchr() returns NULL.  Out of memory?\n");
+      if( !bDontStop ) exit(1);
+    }else if( bSvgOnly ){
+      printf("%s\n", zOut);
+    }else{
+      if( zHtmlHdr ){
+        printf("%s", zHtmlHdr);
+        zHtmlHdr = 0;
+      }
+      printf("<h1>File %s</h1>\n", argv[i]);
+      if( w<0 ){
+        printf("<p>ERROR</p>\n%s\n", zOut);
+      }else{
+        printf("<div id=\"svg-%d\" onclick=\"toggleHidden('svg-%d')\">\n",i,i);
+        printf("<div style='border:3px solid lightgray;max-width:%dpx;%s'>\n",
+               w,zStyle);
+        printf("%s</div>\n", zOut);
+        printf("<pre class='hidden'>");
+        print_escape_html(zIn);
+        printf("</pre>\n</div>\n");
+      }
+    }
+    free(zOut);
+    free(zIn);
+  }
+  if( !bSvgOnly ){
+    printf("</body></html>\n");
+  }
+  return exitCode ? EXIT_FAILURE : EXIT_SUCCESS; 
+}
+#endif /* PIKCHR_SHELL */
+
+#ifdef PIKCHR_TCL
+#include <tcl.h>
+/*
+** An interface to TCL
+**
+** TCL command:     pikchr $INPUTTEXT
+**
+** Returns a list of 3 elements which are the output text, the width, and
+** the height.
+**
+** Register the "pikchr" command by invoking Pikchr_Init(Tcl_Interp*).  Or
+** compile this source file as a shared library and load it using the
+** "load" command of Tcl.
+**
+** Compile this source-code file into a shared library using a command
+** similar to this:
+**
+**    gcc -c pikchr.so -DPIKCHR_TCL -shared pikchr.c
+*/
+static int pik_tcl_command(
+  ClientData clientData, /* Not Used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  int w, h;              /* Width and height of the pikchr */
+  const char *zIn;       /* Source text input */
+  char *zOut;            /* SVG output text */
+  Tcl_Obj *pRes;         /* The result TCL object */
+
+  (void)clientData;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PIKCHR_SOURCE_TEXT");
+    return TCL_ERROR;
+  }
+  zIn = Tcl_GetString(objv[1]);
+  w = h = 0;
+  zOut = pikchr(zIn, "pikchr", 0, &w, &h);
+  if( zOut==0 ){
+    return TCL_ERROR;  /* Out of memory */
+  }
+  pRes = Tcl_NewObj();
+  Tcl_ListObjAppendElement(0, pRes, Tcl_NewStringObj(zOut, -1));
+  free(zOut);
+  Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(w));
+  Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(h));
+  Tcl_SetObjResult(interp, pRes);
+  return TCL_OK;
+}
+
+#ifndef PACKAGE_NAME
+# define PACKAGE_NAME "pikchr"
+#endif
+#ifndef PACKAGE_VERSION
+# define PACKAGE_VERSION "1.0"
+#endif
+
+/* Invoke this routine to register the "pikchr" command with the interpreter
+** given in the argument */
+int Pikchr_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "pikchr", pik_tcl_command, 0, 0);
+  Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION);
+  return TCL_OK;
+}
+
+
+#endif /* PIKCHR_TCL */
+
+
+} // end %code

+ 38 - 0
pikchr.mod/pikchr/tests/autochop01.pikchr

@@ -0,0 +1,38 @@
+C: box "box"
+
+line from C to 3cm heading  00 from C chop;
+line from C to 3cm heading  10 from C chop;
+line from C to 3cm heading  20 from C chop;
+line from C to 3cm heading  30 from C chop;
+line from C to 3cm heading  40 from C chop;
+line from C to 3cm heading  50 from C chop;
+line from C to 3cm heading  60 from C chop;
+line from C to 3cm heading  70 from C chop;
+line from C to 3cm heading  80 from C chop;
+line from C to 3cm heading  90 from C chop;
+line from C to 3cm heading 100 from C chop;
+line from C to 3cm heading 110 from C chop;
+line from C to 3cm heading 120 from C chop;
+line from C to 3cm heading 130 from C chop;
+line from C to 3cm heading 140 from C chop;
+line from C to 3cm heading 150 from C chop;
+line from C to 3cm heading 160 from C chop;
+line from C to 3cm heading 170 from C chop;
+line from C to 3cm heading 180 from C chop;
+line from C to 3cm heading 190 from C chop;
+line from C to 3cm heading 200 from C chop;
+line from C to 3cm heading 210 from C chop;
+line from C to 3cm heading 220 from C chop;
+line from C to 3cm heading 230 from C chop;
+line from C to 3cm heading 240 from C chop;
+line from C to 3cm heading 250 from C chop;
+line from C to 3cm heading 260 from C chop;
+line from C to 3cm heading 270 from C chop;
+line from C to 3cm heading 280 from C chop;
+line from C to 3cm heading 290 from C chop;
+line from C to 3cm heading 300 from C chop;
+line from C to 3cm heading 310 from C chop;
+line from C to 3cm heading 320 from C chop;
+line from C to 3cm heading 330 from C chop;
+line from C to 3cm heading 340 from C chop;
+line from C to 3cm heading 350 from C chop;

+ 38 - 0
pikchr.mod/pikchr/tests/autochop02.pikchr

@@ -0,0 +1,38 @@
+C: box "box" radius 10px
+
+line from C to 3cm heading  00 from C chop;
+line from C to 3cm heading  10 from C chop;
+line from C to 3cm heading  20 from C chop;
+line from C to 3cm heading  30 from C chop;
+line from C to 3cm heading  40 from C chop;
+line from C to 3cm heading  50 from C chop;
+line from C to 3cm heading  60 from C chop;
+line from C to 3cm heading  70 from C chop;
+line from C to 3cm heading  80 from C chop;
+line from C to 3cm heading  90 from C chop;
+line from C to 3cm heading 100 from C chop;
+line from C to 3cm heading 110 from C chop;
+line from C to 3cm heading 120 from C chop;
+line from C to 3cm heading 130 from C chop;
+line from C to 3cm heading 140 from C chop;
+line from C to 3cm heading 150 from C chop;
+line from C to 3cm heading 160 from C chop;
+line from C to 3cm heading 170 from C chop;
+line from C to 3cm heading 180 from C chop;
+line from C to 3cm heading 190 from C chop;
+line from C to 3cm heading 200 from C chop;
+line from C to 3cm heading 210 from C chop;
+line from C to 3cm heading 220 from C chop;
+line from C to 3cm heading 230 from C chop;
+line from C to 3cm heading 240 from C chop;
+line from C to 3cm heading 250 from C chop;
+line from C to 3cm heading 260 from C chop;
+line from C to 3cm heading 270 from C chop;
+line from C to 3cm heading 280 from C chop;
+line from C to 3cm heading 290 from C chop;
+line from C to 3cm heading 300 from C chop;
+line from C to 3cm heading 310 from C chop;
+line from C to 3cm heading 320 from C chop;
+line from C to 3cm heading 330 from C chop;
+line from C to 3cm heading 340 from C chop;
+line from C to 3cm heading 350 from C chop;

+ 38 - 0
pikchr.mod/pikchr/tests/autochop03.pikchr

@@ -0,0 +1,38 @@
+C: circle "circle"
+
+line from C to 3cm heading  00 from C chop;
+line from C to 3cm heading  10 from C chop;
+line from C to 3cm heading  20 from C chop;
+line from C to 3cm heading  30 from C chop;
+line from C to 3cm heading  40 from C chop;
+line from C to 3cm heading  50 from C chop;
+line from C to 3cm heading  60 from C chop;
+line from C to 3cm heading  70 from C chop;
+line from C to 3cm heading  80 from C chop;
+line from C to 3cm heading  90 from C chop;
+line from C to 3cm heading 100 from C chop;
+line from C to 3cm heading 110 from C chop;
+line from C to 3cm heading 120 from C chop;
+line from C to 3cm heading 130 from C chop;
+line from C to 3cm heading 140 from C chop;
+line from C to 3cm heading 150 from C chop;
+line from C to 3cm heading 160 from C chop;
+line from C to 3cm heading 170 from C chop;
+line from C to 3cm heading 180 from C chop;
+line from C to 3cm heading 190 from C chop;
+line from C to 3cm heading 200 from C chop;
+line from C to 3cm heading 210 from C chop;
+line from C to 3cm heading 220 from C chop;
+line from C to 3cm heading 230 from C chop;
+line from C to 3cm heading 240 from C chop;
+line from C to 3cm heading 250 from C chop;
+line from C to 3cm heading 260 from C chop;
+line from C to 3cm heading 270 from C chop;
+line from C to 3cm heading 280 from C chop;
+line from C to 3cm heading 290 from C chop;
+line from C to 3cm heading 300 from C chop;
+line from C to 3cm heading 310 from C chop;
+line from C to 3cm heading 320 from C chop;
+line from C to 3cm heading 330 from C chop;
+line from C to 3cm heading 340 from C chop;
+line from C to 3cm heading 350 from C chop;

+ 38 - 0
pikchr.mod/pikchr/tests/autochop04.pikchr

@@ -0,0 +1,38 @@
+C: ellipse "ellipse"
+
+line from C to 3cm heading  00 from C chop;
+line from C to 3cm heading  10 from C chop;
+line from C to 3cm heading  20 from C chop;
+line from C to 3cm heading  30 from C chop;
+line from C to 3cm heading  40 from C chop;
+line from C to 3cm heading  50 from C chop;
+line from C to 3cm heading  60 from C chop;
+line from C to 3cm heading  70 from C chop;
+line from C to 3cm heading  80 from C chop;
+line from C to 3cm heading  90 from C chop;
+line from C to 3cm heading 100 from C chop;
+line from C to 3cm heading 110 from C chop;
+line from C to 3cm heading 120 from C chop;
+line from C to 3cm heading 130 from C chop;
+line from C to 3cm heading 140 from C chop;
+line from C to 3cm heading 150 from C chop;
+line from C to 3cm heading 160 from C chop;
+line from C to 3cm heading 170 from C chop;
+line from C to 3cm heading 180 from C chop;
+line from C to 3cm heading 190 from C chop;
+line from C to 3cm heading 200 from C chop;
+line from C to 3cm heading 210 from C chop;
+line from C to 3cm heading 220 from C chop;
+line from C to 3cm heading 230 from C chop;
+line from C to 3cm heading 240 from C chop;
+line from C to 3cm heading 250 from C chop;
+line from C to 3cm heading 260 from C chop;
+line from C to 3cm heading 270 from C chop;
+line from C to 3cm heading 280 from C chop;
+line from C to 3cm heading 290 from C chop;
+line from C to 3cm heading 300 from C chop;
+line from C to 3cm heading 310 from C chop;
+line from C to 3cm heading 320 from C chop;
+line from C to 3cm heading 330 from C chop;
+line from C to 3cm heading 340 from C chop;
+line from C to 3cm heading 350 from C chop;

+ 38 - 0
pikchr.mod/pikchr/tests/autochop05.pikchr

@@ -0,0 +1,38 @@
+C: oval "oval"
+
+line from C to 3cm heading  00 from C chop;
+line from C to 3cm heading  10 from C chop;
+line from C to 3cm heading  20 from C chop;
+line from C to 3cm heading  30 from C chop;
+line from C to 3cm heading  40 from C chop;
+line from C to 3cm heading  50 from C chop;
+line from C to 3cm heading  60 from C chop;
+line from C to 3cm heading  70 from C chop;
+line from C to 3cm heading  80 from C chop;
+line from C to 3cm heading  90 from C chop;
+line from C to 3cm heading 100 from C chop;
+line from C to 3cm heading 110 from C chop;
+line from C to 3cm heading 120 from C chop;
+line from C to 3cm heading 130 from C chop;
+line from C to 3cm heading 140 from C chop;
+line from C to 3cm heading 150 from C chop;
+line from C to 3cm heading 160 from C chop;
+line from C to 3cm heading 170 from C chop;
+line from C to 3cm heading 180 from C chop;
+line from C to 3cm heading 190 from C chop;
+line from C to 3cm heading 200 from C chop;
+line from C to 3cm heading 210 from C chop;
+line from C to 3cm heading 220 from C chop;
+line from C to 3cm heading 230 from C chop;
+line from C to 3cm heading 240 from C chop;
+line from C to 3cm heading 250 from C chop;
+line from C to 3cm heading 260 from C chop;
+line from C to 3cm heading 270 from C chop;
+line from C to 3cm heading 280 from C chop;
+line from C to 3cm heading 290 from C chop;
+line from C to 3cm heading 300 from C chop;
+line from C to 3cm heading 310 from C chop;
+line from C to 3cm heading 320 from C chop;
+line from C to 3cm heading 330 from C chop;
+line from C to 3cm heading 340 from C chop;
+line from C to 3cm heading 350 from C chop;

+ 38 - 0
pikchr.mod/pikchr/tests/autochop06.pikchr

@@ -0,0 +1,38 @@
+C: cylinder "cylinder"
+
+line from C to 3cm heading  00 from C chop;
+line from C to 3cm heading  10 from C chop;
+line from C to 3cm heading  20 from C chop;
+line from C to 3cm heading  30 from C chop;
+line from C to 3cm heading  40 from C chop;
+line from C to 3cm heading  50 from C chop;
+line from C to 3cm heading  60 from C chop;
+line from C to 3cm heading  70 from C chop;
+line from C to 3cm heading  80 from C chop;
+line from C to 3cm heading  90 from C chop;
+line from C to 3cm heading 100 from C chop;
+line from C to 3cm heading 110 from C chop;
+line from C to 3cm heading 120 from C chop;
+line from C to 3cm heading 130 from C chop;
+line from C to 3cm heading 140 from C chop;
+line from C to 3cm heading 150 from C chop;
+line from C to 3cm heading 160 from C chop;
+line from C to 3cm heading 170 from C chop;
+line from C to 3cm heading 180 from C chop;
+line from C to 3cm heading 190 from C chop;
+line from C to 3cm heading 200 from C chop;
+line from C to 3cm heading 210 from C chop;
+line from C to 3cm heading 220 from C chop;
+line from C to 3cm heading 230 from C chop;
+line from C to 3cm heading 240 from C chop;
+line from C to 3cm heading 250 from C chop;
+line from C to 3cm heading 260 from C chop;
+line from C to 3cm heading 270 from C chop;
+line from C to 3cm heading 280 from C chop;
+line from C to 3cm heading 290 from C chop;
+line from C to 3cm heading 300 from C chop;
+line from C to 3cm heading 310 from C chop;
+line from C to 3cm heading 320 from C chop;
+line from C to 3cm heading 330 from C chop;
+line from C to 3cm heading 340 from C chop;
+line from C to 3cm heading 350 from C chop;

+ 38 - 0
pikchr.mod/pikchr/tests/autochop07.pikchr

@@ -0,0 +1,38 @@
+C: file "file"
+
+line from C to 3cm heading  00 from C chop;
+line from C to 3cm heading  10 from C chop;
+line from C to 3cm heading  20 from C chop;
+line from C to 3cm heading  30 from C chop;
+line from C to 3cm heading  40 from C chop;
+line from C to 3cm heading  50 from C chop;
+line from C to 3cm heading  60 from C chop;
+line from C to 3cm heading  70 from C chop;
+line from C to 3cm heading  80 from C chop;
+line from C to 3cm heading  90 from C chop;
+line from C to 3cm heading 100 from C chop;
+line from C to 3cm heading 110 from C chop;
+line from C to 3cm heading 120 from C chop;
+line from C to 3cm heading 130 from C chop;
+line from C to 3cm heading 140 from C chop;
+line from C to 3cm heading 150 from C chop;
+line from C to 3cm heading 160 from C chop;
+line from C to 3cm heading 170 from C chop;
+line from C to 3cm heading 180 from C chop;
+line from C to 3cm heading 190 from C chop;
+line from C to 3cm heading 200 from C chop;
+line from C to 3cm heading 210 from C chop;
+line from C to 3cm heading 220 from C chop;
+line from C to 3cm heading 230 from C chop;
+line from C to 3cm heading 240 from C chop;
+line from C to 3cm heading 250 from C chop;
+line from C to 3cm heading 260 from C chop;
+line from C to 3cm heading 270 from C chop;
+line from C to 3cm heading 280 from C chop;
+line from C to 3cm heading 290 from C chop;
+line from C to 3cm heading 300 from C chop;
+line from C to 3cm heading 310 from C chop;
+line from C to 3cm heading 320 from C chop;
+line from C to 3cm heading 330 from C chop;
+line from C to 3cm heading 340 from C chop;
+line from C to 3cm heading 350 from C chop;

+ 11 - 0
pikchr.mod/pikchr/tests/autochop08.pikchr

@@ -0,0 +1,11 @@
+circle "C0"
+move
+circle "C1"
+box at C0.c width 11cm height 2.5cm
+arrow from C0 to C1 chop color red
+move
+text "bug report 2021-07-17" \
+     "forum 73eea815afda0715" \
+     "arrow C0 to C1 chopped" \
+     "C0 and box have same center" \
+     at 0.5 between last box.w and C0.w

+ 17 - 0
pikchr.mod/pikchr/tests/autochop09.pikchr

@@ -0,0 +1,17 @@
+circle "C0" fit color blue
+C1: circle at C0 radius 2.5*C0.radius color red
+text "C1" with .n at C1.n color red
+circle "X1" at 8*C0.radius heading 60 from C0 fit color blue
+circle "X2" at 8*C0.radius heading 80 from C0 fit color red
+circle "X3" at 8*C0.radius heading 100 from C0 fit color blue
+circle "X4" at 8*C0.radius heading 120 from C0 fit color red
+arrow color blue from C0 to X1 chop
+arrow color red from C1 to X2 chop
+arrow color blue from X3 to C0 chop
+arrow color red from X4 to C1 chop
+
+text "chop bug 2021-07-17" \
+     "forum 1d46e3a0bc5c5631" \
+     "red lines to red circles" \
+     "blue lines to blue circle" \
+     at 2cm below C0

+ 23 - 0
pikchr.mod/pikchr/tests/colortest1.pikchr

@@ -0,0 +1,23 @@
+box "red" fill red fit
+move
+box "orange" fill orange fit
+move
+box "yellow" fill yellow fit
+move
+box "green" fill green fit
+move
+box "blue" fill blue fit
+move
+box "violet" fill violet fit
+
+box "red" color red fit with .nw at 1cm below 1st box.sw
+move
+box "orange" color orange fit
+move
+box "yellow" color yellow fit
+move
+box "green" color green fit
+move
+box "blue" color blue fit
+move
+box "violet" color violet fit

+ 0 - 0
pikchr.mod/pikchr/tests/empty.pikchr


+ 400 - 0
pikchr.mod/pikchr/tests/expr.pikchr

@@ -0,0 +1,400 @@
+      linerad = 10px
+      linewid *= 0.5
+      $h = 0.21
+margin = 1cm
+debug_label_color = Red
+color = lightgray
+ 
+      circle radius 10%
+OUT:  6.3in right of previous.e  # FIX ME
+IN_X: linerad east of first circle.e
+
+      # The literal-value line
+      arrow
+LTV:  box "literal-value" fit
+      arrow right even with linerad+2*arrowht east of OUT
+      circle same
+
+      # The bind-parameter line
+      right
+BNDP: oval "bind-parameter" fit with .w at 1.25*$h below LTV.w
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from first circle.e right linerad then down even with BNDP \
+        then to BNDP.w
+
+      # The table column reference line
+      right
+SN:   oval "schema-name" fit with .w at 2.0*$h below BNDP.w
+      arrow 2*arrowht
+      oval "." bold fit
+      arrow
+TN:   oval "table-name" fit
+      arrow right 2*arrowht
+      oval "." bold fit
+      arrow
+CN:   oval "column-name" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,SN.n) down even with SN then to SN.w
+TN_Y: 0.375*$h above TN.n
+      arrow from (IN_X,linerad above TN_Y) down linerad \
+         then right even with SN
+      arrow right right even with TN
+      line right even with linerad+arrowht west of CN.w \
+         then down even with CN then right linerad
+      line from (linerad+2*arrowht left of TN.w,TN_Y) right linerad \
+         then down even with TN then right linerad
+
+      # Unary operators
+      right
+UOP:  oval "unary-operator" fit with .w at 1.25*$h below SN.w
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,UOP.n) down even with UOP then to UOP.w
+
+      # Binary operators
+      right
+BINY: box "expr" fit with .w at 1.25*$h below UOP.w
+      arrow 2*arrowht
+      oval "binary-operator" fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,BINY.n) down even with BINY then to BINY.w
+ 
+      # Function calls
+      right
+FUNC: oval "function-name" fit with .w at 2.0*$h below BINY.w
+      arrow 1.5*arrowht
+FLP:  oval "(" bold fit
+      arrow
+FDCT: oval "DISTINCT" fit
+      arrow
+FEXP: box "expr" fit
+      arrow 150%
+FRP:  oval ")" bold fit
+      arrow right linerad then down $h then right 2*arrowht
+FFC:  box "filter-clause" fit
+FA1:  arrow right linerad then up even with FUNC then right 2*arrowht
+      arrow right linerad then down $h then right 2*arrowht
+FOC:  box "over-clause" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,FUNC.n) down even with FUNC then to FUNC.w
+
+             # filter clause bypass
+             arrow from FRP.e right even with FFC
+             line to arrowht left of FA1.end
+
+             # over clause bypass
+             arrow from FA1.end right even with OUT
+             line right linerad then up linerad
+
+             # expr loop
+      FCMA:  oval "," bold fit at 1.25*$h above FEXP
+             arrow from FEXP.e right linerad then up even with FCMA \
+               then to FCMA.e
+             line from FCMA.w left even with 2*arrowht west of FEXP.w \
+               then down even with FEXP then right linerad
+
+             # "*" argument list
+      FSTR:  oval "*" bold fit with .w at 1.25*$h below FDCT.w
+             arrow from FLP.e right linerad then down even with FSTR \
+                then to FSTR.w
+      FA2:   arrow from FSTR.e right even with linerad+2*arrowht west of FRP.w
+             line right linerad then up even with FRP then right linerad
+
+             # empty argument list
+             arrow from (linerad east of FLP.e,FSTR.n) \
+                down even with $h below FSTR then right even with FDCT.w
+             arrow right even with FA2.end
+             line right linerad then up even with FSTR.n
+
+      # parenthesized and vector expressions
+      right
+PRN:  oval "(" bold fit with .w at 3.0*$h below FUNC.w
+      arrow
+PEXP: box "expr" fit
+      arrow
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,PRN.n) down even with PRN then to PRN.w
+
+             # expr loop
+      PCMA:  oval "," bold fit at 1.25*$h above PEXP
+             arrow from PEXP.e right linerad then up even with PCMA \
+               then to PCMA.e
+             line from PCMA.w left even with 2*arrowht left of PEXP.w \
+               then down even with PEXP then right linerad
+
+      # CAST expression
+      right
+CAST: oval "CAST" fit with .w at 1.25*$h below PRN.w
+      arrow 2*arrowht
+      oval "(" bold fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow 2*arrowht
+      oval "AS" fit
+      arrow 2*arrowht
+      box "type-name" fit
+      arrow 2*arrowht
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,CAST.n) down even with CAST then to CAST.w
+
+      # COLLATE expression
+      right
+COLL: box "expr" fit with .w at 1.25*$h below CAST.w
+      arrow 2*arrowht
+      oval "COLLATE" fit
+      arrow 2*arrowht
+      oval "collation-name" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,COLL.n) down even with COLL then to COLL.w
+
+      # LIKE expressions
+      right
+LIKE: box "expr" fit with .w at 1.25*$h below COLL.w
+      arrow
+LNOT: oval "NOT" fit
+      arrow 150%
+LOP1: oval "LIKE" fit
+LOP2: oval "GLOB" fit with .w at 1.25*$h below LOP1.w
+LOP3: oval "REGEXP" fit with .w at 1.25*$h below LOP2.w
+LOP4: oval "MATCH" fit with .w at 1.25*$h below LOP3.w
+LE2:  box "expr" fit with .w at (4*arrowht+linerad east of LOP3.e,LIKE)
+      arrow from LE2.e right linerad then down $h then right 2*arrowht
+LESC: oval "ESCAPE" fit
+      arrow 2*arrowht
+      box "expr" fit
+LA1:  arrow right linerad then up even with LIKE then right
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,LIKE.n) down even with LIKE then to LIKE.w
+
+            # NOT bypass
+            line from linerad*2 west of LNOT.w \
+              right linerad then down $h \
+              then right even with arrowht east of LNOT.e \
+              then up even with LNOT then right linerad
+
+            # Inputs to the operators
+       LX1: 2*arrowht west of LOP1.w
+            arrow from linerad west of LX1 right linerad \
+               then down even with LOP4 then to LOP4.w
+            arrow from (LX1,LOP2.n) down even with LOP2 then to LOP2.w
+            arrow from (LX1,LOP3.n) down even with LOP3 then to LOP3.w
+
+            # Outputs from the operators
+       LX2: 2*arrowht east of LOP3.e
+            arrow from LOP4.e right even with LX2
+            arrow right linerad then up even with LE2 then to LE2.w
+            arrow from LOP3.e right even with LX2
+            line right linerad then up linerad
+            arrow from LOP2.e right even with LX2
+            line right linerad then up linerad
+            line from LOP1.e to arrowht west of LE2.w
+
+            # ESCAPE bypass
+            arrow from LE2.e right even with LESC
+            line to arrowht left of LA1.end
+
+      # ISNULL and NOTNULL operators
+      right
+NNUL: box "expr" fit with .w at 5.0*$h below LIKE.w
+      arrow
+NN1:  oval "ISNULL" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,NNUL.n) down even with NNUL then to NNUL.w
+NN2:  oval "NOTNULL" fit with .w at 1.25*$h below NN1.w
+      right
+NN3:  oval "NOT" fit with .w at 1.25*$h below NN2.w
+      arrow 2*arrowht
+NN3B: oval "NULL" fit
+NNA1: arrow 2*arrowht
+      arrow right linerad then up even with NN1 then right
+      arrow from NN2.e right even with NNA1.end
+      line right linerad then up linerad
+      arrow from NNUL.e right linerad then down even with NN3 then to NN3.w
+      arrow from NNUL.e right linerad then down even with NN2 then to NN2.w
+
+      # The IS operator
+      right
+IS:   box "expr" fit with .w at 3.75*$h below NNUL.w
+      arrow 2*arrowht
+      oval "IS" fit
+      arrow
+ISN:  oval "NOT" fit
+      arrow
+      box "expr" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,IS.n) down even with IS then to IS.w
+      # NOT bypass
+      line from 3*arrowht west of ISN.w right linerad \
+         then down 0.8*$h then right even with arrowht east of ISN.e \
+         then up even with ISN then right linerad
+
+      # The BETWEEN operator
+      right
+BTW:  box "expr" fit with .w at 1.5*$h below IS.w
+      arrow
+BTWN: oval "NOT" fit
+      arrow
+      oval "BETWEEN" fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow 2*arrowht
+      oval "AND" fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,BTW.n) down even with BTW then to BTW.w
+      # NOT bypass
+      line from 3*arrowht west of BTWN.w right linerad \
+         then down 0.8*$h then right even with arrowht east of BTWN.e \
+         then up even with BTWN then right linerad
+
+      # The IN operator
+      right
+IN:   box "expr" fit with .w at 1.75*$h below BTW.w
+      arrow
+INNT: oval "NOT" fit
+      arrow
+ININ: oval "IN" fit
+      arrow
+INLP: oval "(" bold fit
+      arrow
+INSS: box "select-stmt" fit
+      arrow
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,IN.n) down even with IN then to IN.w
+
+             # NOT bypass
+             line from 3*arrowht west of INNT.w right linerad \
+             then down 0.8*$h then right even with arrowht east of INNT.e \
+             then up even with INNT then right linerad
+
+             # select-stmt bypass
+             line from 3*arrowht west of INSS.w right linerad \
+             then up 0.8*$h then right even with arrowht east of INSS.e \
+             then up even with INSS then right linerad
+
+             # expr list instead of select-stmt
+       INE1: box "expr" fit at 1.25*$h below INSS
+             arrow from 3*arrowht west of INSS.w right linerad \
+               then down even with INE1 then to INE1.w
+             line from INE1.e right even with arrowht east of INSS.e \
+               then up even with INSS then right linerad
+
+             # expr loop
+       INC1: oval "," bold fit at 1.25*$h below INE1
+             arrow from INE1.e right linerad then down even with INC1 \
+                then to INC1.e
+             line from INC1.w left even with 2*arrowht west of INE1.w \
+                then up even with INE1 then right linerad
+
+             # reference-to-table choice as RHS
+       INSN: oval "schema-name" fit with .w at 4.25*$h below INLP.w
+             arrow from INSN.e right 1.5*arrowht
+       INDT: oval "." bold fit
+             arrow 150%
+       INTF: oval "table-function" fit
+             arrow 1.5*arrowht
+       INL2: oval "(" bold fit
+             arrow 125%
+       INE2: box "expr" fit
+             arrow 125%
+       INR2: oval ")" bold fit
+             arrow right even with OUT; line right linerad then up linerad
+
+             # table reference branch
+             right
+       INTB: oval "table-name" fit with .w at 2*$h above INTF.w
+             arrow right even with OUT; line right linerad then up linerad
+             arrow from linerad+2*arrowht west of INTF.w right linerad \
+                then up even with INTB then to INTB.w
+
+             # expr-list no table-valued-functions
+       INC2: oval "," bold fit at 1.1*$h above INE2
+             arrow from INE2.e right linerad then up even with INC2 \
+               then to INC2.e
+             line from INC2.w right even with 2*arrowht west of INE2.w \
+               then down even with INE2 then right linerad
+
+             # expr-list bypass for table-valued functions
+             line from INL2.e right linerad then down .7*$h \
+                then right even with 2*arrowht left of INR2.w \
+                then up even with INR2 then right linerad
+
+             # links from IN operator to table references
+             arrow from ININ.e right linerad then down even with INSN \
+                then to INSN.w
+
+             # schema-name bypass
+       INY3: 0.45*$h above INSN.n
+             arrow from (linerad east of ININ.e,linerad above INY3) \
+                down linerad then right even with arrowht right of INSN.e
+             line right even with arrowht east of INDT.e \
+                then down even with INDT then right linerad
+
+      # NOT? EXISTS? (SELECT) clause
+      right
+NE:   oval "NOT" fit with .w at (IN.w,1.5*$h below INSN)
+      arrow
+NEE:  oval "EXISTS" fit
+      arrow
+NELP: oval "(" bold fit
+      arrow 2*arrowht
+      box "select-stmt" fit
+      arrow 2*arrowht
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,NE.n) down even with NE then to NE.w
+NE_Y: 0.375*$h above NE.n
+      arrow from (IN_X,linerad above NE_Y) down linerad \
+         then right even with NE
+      line right even with linerad+arrowht west of NELP.w \
+         then down even with NELP then right linerad
+      line from (linerad+2*arrowht left of NEE.w,NE_Y) right linerad \
+         then down even with NEE then right linerad
+      
+      # CASE expressions
+      right
+CS:   oval "CASE" fit with .w at 1.25*$h below NE.w
+      arrow
+CSE1: box "expr" fit
+      arrow 150%
+CSW:  oval "WHEN" fit
+      arrow 2*arrowht
+CSE2: box "expr" fit
+      arrow 2*arrowht
+      oval "THEN" fit
+      arrow 2*arrowht
+CSE3: box "expr" fit
+      arrow 200%
+CSEL: oval "ELSE" fit
+      arrow 2*arrowht
+CSE4: box "expr" fit
+      arrow
+      oval "END" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,CS.n) down even with CS then to CS.w
+      # first expr bypass
+CSY:  0.9*$h below CS
+      line from CS.e right linerad then down even with CSY \
+         then right even with arrowht east of CSE1.e then up even with CSE1 \
+         then right linerad
+      # when clause loop
+      arrow from CSE3.e right linerad then down even with CSY \
+         then left even with CSE2
+      line left even with 2*arrowht west of CSW.w \
+         then up even with CSW then right linerad
+      # ELSE clause bypass
+      line from linerad+2*arrowht west of CSEL.w right linerad \
+         then down even with CSY then right even with arrowht east of CSE4.e \
+         then up even with CSE4 then right linerad
+
+      # The RAISE function
+      right
+RSE:  box "raise-function" fit with .w at 1.9*$h below CS.w
+      arrow right even with OUT; 
+      line right linerad then up even with first circle then right linerad
+      arrow from (IN_X,BNDP.n) down even with RSE then to RSE.w

+ 402 - 0
pikchr.mod/pikchr/tests/expr.txt

@@ -0,0 +1,402 @@
+# Test the "debug_label_color" feature
+#
+      linerad = 10px
+      linewid *= 0.5
+      $h = 0.21
+margin = 1cm
+debug_label_color = Red
+color = LightGray
+ 
+      circle radius 10%
+OUT:  6.3in right of previous.e  # FIX ME
+IN_X: linerad east of first circle.e
+
+      # The literal-value line
+      arrow
+LTV:  box "literal-value" fit
+      arrow right even with linerad+2*arrowht east of OUT
+      circle same
+
+      # The bind-parameter line
+      right
+BNDP: oval "bind-parameter" fit with .w at 1.25*$h below LTV.w
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from first circle.e right linerad then down even with BNDP \
+        then to BNDP.w
+
+      # The table column reference line
+      right
+SN:   oval "schema-name" fit with .w at 2.0*$h below BNDP.w
+      arrow 2*arrowht
+      oval "." bold fit
+      arrow
+TN:   oval "table-name" fit
+      arrow right 2*arrowht
+      oval "." bold fit
+      arrow
+CN:   oval "column-name" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,SN.n) down even with SN then to SN.w
+TN_Y: 0.375*$h above TN.n
+      arrow from (IN_X,linerad above TN_Y) down linerad \
+         then right even with SN
+      arrow right right even with TN
+      line right even with linerad+arrowht west of CN.w \
+         then down even with CN then right linerad
+      line from (linerad+2*arrowht left of TN.w,TN_Y) right linerad \
+         then down even with TN then right linerad
+
+      # Unary operators
+      right
+UOP:  oval "unary-operator" fit with .w at 1.25*$h below SN.w
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,UOP.n) down even with UOP then to UOP.w
+
+      # Binary operators
+      right
+BINY: box "expr" fit with .w at 1.25*$h below UOP.w
+      arrow 2*arrowht
+      oval "binary-operator" fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,BINY.n) down even with BINY then to BINY.w
+ 
+      # Function calls
+      right
+FUNC: oval "function-name" fit with .w at 2.0*$h below BINY.w
+      arrow 1.5*arrowht
+FLP:  oval "(" bold fit
+      arrow
+FDCT: oval "DISTINCT" fit
+      arrow
+FEXP: box "expr" fit
+      arrow 150%
+FRP:  oval ")" bold fit
+      arrow right linerad then down $h then right 2*arrowht
+FFC:  box "filter-clause" fit
+FA1:  arrow right linerad then up even with FUNC then right 2*arrowht
+      arrow right linerad then down $h then right 2*arrowht
+FOC:  box "over-clause" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,FUNC.n) down even with FUNC then to FUNC.w
+
+             # filter clause bypass
+             arrow from FRP.e right even with FFC
+             line to arrowht left of FA1.end
+
+             # over clause bypass
+             arrow from FA1.end right even with OUT
+             line right linerad then up linerad
+
+             # expr loop
+      FCMA:  oval "," bold fit at 1.25*$h above FEXP
+             arrow from FEXP.e right linerad then up even with FCMA \
+               then to FCMA.e
+             line from FCMA.w left even with 2*arrowht west of FEXP.w \
+               then down even with FEXP then right linerad
+
+             # "*" argument list
+      FSTR:  oval "*" bold fit with .w at 1.25*$h below FDCT.w
+             arrow from FLP.e right linerad then down even with FSTR \
+                then to FSTR.w
+      FA2:   arrow from FSTR.e right even with linerad+2*arrowht west of FRP.w
+             line right linerad then up even with FRP then right linerad
+
+             # empty argument list
+             arrow from (linerad east of FLP.e,FSTR.n) \
+                down even with $h below FSTR then right even with FDCT.w
+             arrow right even with FA2.end
+             line right linerad then up even with FSTR.n
+
+      # parenthesized and vector expressions
+      right
+PRN:  oval "(" bold fit with .w at 3.0*$h below FUNC.w
+      arrow
+PEXP: box "expr" fit
+      arrow
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,PRN.n) down even with PRN then to PRN.w
+
+             # expr loop
+      PCMA:  oval "," bold fit at 1.25*$h above PEXP
+             arrow from PEXP.e right linerad then up even with PCMA \
+               then to PCMA.e
+             line from PCMA.w left even with 2*arrowht left of PEXP.w \
+               then down even with PEXP then right linerad
+
+      # CAST expression
+      right
+CAST: oval "CAST" fit with .w at 1.25*$h below PRN.w
+      arrow 2*arrowht
+      oval "(" bold fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow 2*arrowht
+      oval "AS" fit
+      arrow 2*arrowht
+      box "type-name" fit
+      arrow 2*arrowht
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,CAST.n) down even with CAST then to CAST.w
+
+      # COLLATE expression
+      right
+COLL: box "expr" fit with .w at 1.25*$h below CAST.w
+      arrow 2*arrowht
+      oval "COLLATE" fit
+      arrow 2*arrowht
+      oval "collation-name" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,COLL.n) down even with COLL then to COLL.w
+
+      # LIKE expressions
+      right
+LIKE: box "expr" fit with .w at 1.25*$h below COLL.w
+      arrow
+LNOT: oval "NOT" fit
+      arrow 150%
+LOP1: oval "LIKE" fit
+LOP2: oval "GLOB" fit with .w at 1.25*$h below LOP1.w
+LOP3: oval "REGEXP" fit with .w at 1.25*$h below LOP2.w
+LOP4: oval "MATCH" fit with .w at 1.25*$h below LOP3.w
+LE2:  box "expr" fit with .w at (4*arrowht+linerad east of LOP3.e,LIKE)
+      arrow from LE2.e right linerad then down $h then right 2*arrowht
+LESC: oval "ESCAPE" fit
+      arrow 2*arrowht
+      box "expr" fit
+LA1:  arrow right linerad then up even with LIKE then right
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,LIKE.n) down even with LIKE then to LIKE.w
+
+            # NOT bypass
+            line from linerad*2 west of LNOT.w \
+              right linerad then down $h \
+              then right even with arrowht east of LNOT.e \
+              then up even with LNOT then right linerad
+
+            # Inputs to the operators
+       LX1: 2*arrowht west of LOP1.w
+            arrow from linerad west of LX1 right linerad \
+               then down even with LOP4 then to LOP4.w
+            arrow from (LX1,LOP2.n) down even with LOP2 then to LOP2.w
+            arrow from (LX1,LOP3.n) down even with LOP3 then to LOP3.w
+
+            # Outputs from the operators
+       LX2: 2*arrowht east of LOP3.e
+            arrow from LOP4.e right even with LX2
+            arrow right linerad then up even with LE2 then to LE2.w
+            arrow from LOP3.e right even with LX2
+            line right linerad then up linerad
+            arrow from LOP2.e right even with LX2
+            line right linerad then up linerad
+            line from LOP1.e to arrowht west of LE2.w
+
+            # ESCAPE bypass
+            arrow from LE2.e right even with LESC
+            line to arrowht left of LA1.end
+
+      # ISNULL and NOTNULL operators
+      right
+NNUL: box "expr" fit with .w at 5.0*$h below LIKE.w
+      arrow
+NN1:  oval "ISNULL" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,NNUL.n) down even with NNUL then to NNUL.w
+NN2:  oval "NOTNULL" fit with .w at 1.25*$h below NN1.w
+      right
+NN3:  oval "NOT" fit with .w at 1.25*$h below NN2.w
+      arrow 2*arrowht
+NN3B: oval "NULL" fit
+NNA1: arrow 2*arrowht
+      arrow right linerad then up even with NN1 then right
+      arrow from NN2.e right even with NNA1.end
+      line right linerad then up linerad
+      arrow from NNUL.e right linerad then down even with NN3 then to NN3.w
+      arrow from NNUL.e right linerad then down even with NN2 then to NN2.w
+
+      # The IS operator
+      right
+IS:   box "expr" fit with .w at 3.75*$h below NNUL.w
+      arrow 2*arrowht
+      oval "IS" fit
+      arrow
+ISN:  oval "NOT" fit
+      arrow
+      box "expr" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,IS.n) down even with IS then to IS.w
+      # NOT bypass
+      line from 3*arrowht west of ISN.w right linerad \
+         then down 0.8*$h then right even with arrowht east of ISN.e \
+         then up even with ISN then right linerad
+
+      # The BETWEEN operator
+      right
+BTW:  box "expr" fit with .w at 1.5*$h below IS.w
+      arrow
+BTWN: oval "NOT" fit
+      arrow
+      oval "BETWEEN" fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow 2*arrowht
+      oval "AND" fit
+      arrow 2*arrowht
+      box "expr" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,BTW.n) down even with BTW then to BTW.w
+      # NOT bypass
+      line from 3*arrowht west of BTWN.w right linerad \
+         then down 0.8*$h then right even with arrowht east of BTWN.e \
+         then up even with BTWN then right linerad
+
+      # The IN operator
+      right
+IN:   box "expr" fit with .w at 1.75*$h below BTW.w
+      arrow
+INNT: oval "NOT" fit
+      arrow
+ININ: oval "IN" fit
+      arrow
+INLP: oval "(" bold fit
+      arrow
+INSS: box "select-stmt" fit
+      arrow
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,IN.n) down even with IN then to IN.w
+
+             # NOT bypass
+             line from 3*arrowht west of INNT.w right linerad \
+             then down 0.8*$h then right even with arrowht east of INNT.e \
+             then up even with INNT then right linerad
+
+             # select-stmt bypass
+             line from 3*arrowht west of INSS.w right linerad \
+             then up 0.8*$h then right even with arrowht east of INSS.e \
+             then up even with INSS then right linerad
+
+             # expr list instead of select-stmt
+       INE1: box "expr" fit at 1.25*$h below INSS
+             arrow from 3*arrowht west of INSS.w right linerad \
+               then down even with INE1 then to INE1.w
+             line from INE1.e right even with arrowht east of INSS.e \
+               then up even with INSS then right linerad
+
+             # expr loop
+       INC1: oval "," bold fit at 1.25*$h below INE1
+             arrow from INE1.e right linerad then down even with INC1 \
+                then to INC1.e
+             line from INC1.w left even with 2*arrowht west of INE1.w \
+                then up even with INE1 then right linerad
+
+             # reference-to-table choice as RHS
+       INSN: oval "schema-name" fit with .w at 4.25*$h below INLP.w
+             arrow from INSN.e right 1.5*arrowht
+       INDT: oval "." bold fit
+             arrow 150%
+       INTF: oval "table-function" fit
+             arrow 1.5*arrowht
+       INL2: oval "(" bold fit
+             arrow 125%
+       INE2: box "expr" fit
+             arrow 125%
+       INR2: oval ")" bold fit
+             arrow right even with OUT; line right linerad then up linerad
+
+             # table reference branch
+             right
+       INTB: oval "table-name" fit with .w at 2*$h above INTF.w
+             arrow right even with OUT; line right linerad then up linerad
+             arrow from linerad+2*arrowht west of INTF.w right linerad \
+                then up even with INTB then to INTB.w
+
+             # expr-list no table-valued-functions
+       INC2: oval "," bold fit at 1.1*$h above INE2
+             arrow from INE2.e right linerad then up even with INC2 \
+               then to INC2.e
+             line from INC2.w right even with 2*arrowht west of INE2.w \
+               then down even with INE2 then right linerad
+
+             # expr-list bypass for table-valued functions
+             line from INL2.e right linerad then down .7*$h \
+                then right even with 2*arrowht left of INR2.w \
+                then up even with INR2 then right linerad
+
+             # links from IN operator to table references
+             arrow from ININ.e right linerad then down even with INSN \
+                then to INSN.w
+
+             # schema-name bypass
+       INY3: 0.45*$h above INSN.n
+             arrow from (linerad east of ININ.e,linerad above INY3) \
+                down linerad then right even with arrowht right of INSN.e
+             line right even with arrowht east of INDT.e \
+                then down even with INDT then right linerad
+
+      # NOT? EXISTS? (SELECT) clause
+      right
+NE:   oval "NOT" fit with .w at (IN.w,1.5*$h below INSN)
+      arrow
+NEE:  oval "EXISTS" fit
+      arrow
+NELP: oval "(" bold fit
+      arrow 2*arrowht
+      box "select-stmt" fit
+      arrow 2*arrowht
+      oval ")" bold fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,NE.n) down even with NE then to NE.w
+NE_Y: 0.375*$h above NE.n
+      arrow from (IN_X,linerad above NE_Y) down linerad \
+         then right even with NE
+      line right even with linerad+arrowht west of NELP.w \
+         then down even with NELP then right linerad
+      line from (linerad+2*arrowht left of NEE.w,NE_Y) right linerad \
+         then down even with NEE then right linerad
+      
+      # CASE expressions
+      right
+CS:   oval "CASE" fit with .w at 1.25*$h below NE.w
+      arrow
+CSE1: box "expr" fit
+      arrow 150%
+CSW:  oval "WHEN" fit
+      arrow 2*arrowht
+CSE2: box "expr" fit
+      arrow 2*arrowht
+      oval "THEN" fit
+      arrow 2*arrowht
+CSE3: box "expr" fit
+      arrow 200%
+CSEL: oval "ELSE" fit
+      arrow 2*arrowht
+CSE4: box "expr" fit
+      arrow
+      oval "END" fit
+      arrow right even with OUT; line right linerad then up linerad
+      arrow from (IN_X,CS.n) down even with CS then to CS.w
+      # first expr bypass
+CSY:  0.9*$h below CS
+      line from CS.e right linerad then down even with CSY \
+         then right even with arrowht east of CSE1.e then up even with CSE1 \
+         then right linerad
+      # when clause loop
+      arrow from CSE3.e right linerad then down even with CSY \
+         then left even with CSE2
+      line left even with 2*arrowht west of CSW.w \
+         then up even with CSW then right linerad
+      # ELSE clause bypass
+      line from linerad+2*arrowht west of CSEL.w right linerad \
+         then down even with CSY then right even with arrowht east of CSE4.e \
+         then up even with CSE4 then right linerad
+
+      # The RAISE function
+      right
+RSE:  box "raise-function" fit with .w at 1.9*$h below CS.w
+      arrow right even with OUT; 
+      line right linerad then up even with first circle then right linerad
+      arrow from (IN_X,BNDP.n) down even with RSE then to RSE.w

+ 21 - 0
pikchr.mod/pikchr/tests/gridlines1.pikchr

@@ -0,0 +1,21 @@
+# Gridlines
+# (Copied, with modifications, from a post by Steve Nicolai.
+#
+thickness *= 0.5
+
+line color gray from -1.25, 1 to 1.25, 1
+line same from -1.25, .5 to 1.25, .5
+line same from -1.25, -0.5 to 1.25, -0.5
+line same from -1.25, -1 to 1.25, -1
+
+line same from -1.0, -1.25 to -1.0, 1.25
+line same from -0.5, -1.25 to -0.5, 1.25
+line same from 0.5, -1.25 to 0.5, 1.25
+line same from 1.0, -1.25 to 1.0, 1.25
+
+right
+arrow from -1.375, 0 to 1.375, 0 thick
+text "x" fit
+up
+arrow from 0, -1.375 to 0, 1.375 thick
+text "y" fit

+ 37 - 0
pikchr.mod/pikchr/tests/test01.pikchr

@@ -0,0 +1,37 @@
+    right;
+    circle "C0";
+A0: arrow;
+    circle "C1";
+A1: arrow; circle "C2";
+    arrow;
+    circle "C4";
+    arrow;
+    circle "C6"
+    circle "C3" at (C4.x-C2.x) ne of C2;
+    arrow;
+    circle "C5"
+    arrow from C2.ne to C3.sw
+
+assert( previous == last arrow )
+assert( previous == 6th arrow )
+
+# Demonstrate that a new point can be established using LABEL: notation
+AS: start of last arrow
+AE: end of last arrow
+
+# Validate various kinds of expressions and locations as a test of
+# parser expression processing and layout
+#
+assert( last arrow.start.x == C2.ne.x )
+assert( AS.y == C2.ne.y )
+assert( last arrow.end.x == C3.sw.x )
+assert( AE.y == C3.sw.y )
+assert( AE == C3.sw )
+assert( 1st last arrow.end == C3.sw )
+assert( start of A0 == C0.e )
+
+assert( C0.y == C1.y );
+assert( C0.y == C2.y );
+assert( C0.y == C4.y );
+assert( C0.y == C6.y );
+assert( C3.y == C5.y );

+ 34 - 0
pikchr.mod/pikchr/tests/test02.pikchr

@@ -0,0 +1,34 @@
+/* First generate the main graph */
+scale = 0.75
+Main: [
+  circle "C0"
+  arrow
+  circle "C1"
+  arrow
+  circle "C2"
+  arrow
+  circle "C4"
+  arrow
+  circle "C6"
+]
+Branch: [
+  circle "C3"
+  arrow
+  circle "C5"
+] with .sw at Main.C2.n + (0.35,0.35)
+arrow from Main.C2 to Branch.C3 chop
+
+/* Now generate the background colors */
+layer = 0
+$featurecolor = 0xfedbce
+$maincolor = 0xd9fece
+$divY = (Branch.y + Main.y)/2
+$divH = (Branch.y - Main.y)
+box fill $featurecolor color $featurecolor \
+    width Branch.width*1.5 height $divH \
+    at Branch
+box fill $maincolor color $maincolor \
+    width Main.width+0.1 height $divH \
+    at Main
+"main" ljust at 0.1 se of nw of 2nd box
+"feature" ljust at 0.1 se of nw of 1st box

+ 18 - 0
pikchr.mod/pikchr/tests/test03.pikchr

@@ -0,0 +1,18 @@
+right
+B1: box "One"; line
+B2: box "Two"; arrow
+B3: box "Three"; down; arrow down 50%; circle "Hi!"; left;
+spline -> left 2cm then to One.se
+Macro: [
+  B4: box "four"
+  B5: box "five"
+  B6: box "six"
+] with n at 3cm below s of 2nd box
+
+arrow from s of 2nd box to Macro.B5.n
+
+spline -> from e of last circle right 1cm then down 1cm then to Macro.B4.e
+
+box width Macro.width+0.1 height Macro.height+0.1 at Macro color Red
+box width Macro.B5.width+0.05 \
+    height Macro.B5.height+0.05 at Macro.B5 color blue

+ 4 - 0
pikchr.mod/pikchr/tests/test04.pikchr

@@ -0,0 +1,4 @@
+print 5+8;
+
+# NB: Deliberate division-by-zero error in order to test math logic
+print (17.4-5)/(2-2);

+ 15 - 0
pikchr.mod/pikchr/tests/test05.pikchr

@@ -0,0 +1,15 @@
+print linewid
+linewid=0.8
+print linewid
+scale=2.0
+print scale
+print hotpink
+print 17 + linewid*1000;
+print sin(scale);
+print cos(scale)
+print sqrt(17)
+print min(5,10)
+print max(5,10)
+
+# NB: deliberate sqrt() error in order to test math logic
+print sqrt(-11)

+ 9 - 0
pikchr.mod/pikchr/tests/test06.pikchr

@@ -0,0 +1,9 @@
+B1: box "one" width 1 height 1 at 2,2;
+B2: box thickness 300% dotted 0.03 "two" at 1,3;
+print "B2.n: ",B2.n.x,",",B2.n.y
+print "B2.c: ",B2.c.x,",",B2.c.y
+print "B2.e: ",B2.e.x,",",B2.e.y
+scale = 1
+box "three" "four" ljust "five" with .n at 0.1 below B2.s width 50%
+
+#  Text demo: <text x="100" y="100" text-anchor="end" dominant-baseline="central">I love SVG!</text>

+ 15 - 0
pikchr.mod/pikchr/tests/test07.pikchr

@@ -0,0 +1,15 @@
+B: box "This is" "<b>" "box \"B\"" color DarkRed
+
+   "B.nw" rjust above at 0.05 left of B.nw
+   "B.w" rjust at 0.05 left of B.w
+   "B.sw" rjust below at 0.05 left of B.sw;  $abc = DarkBlue
+   "B.s" below at B.s
+   // C++ style comments allowed.
+   "B.se" ljust below at 0.05 right of B.se color DarkBlue
+   "B.e" ljust at 0.05 right of B.e  /* C-style comments */
+   "B.ne" ljust above at 0.05 right of B.ne
+   "B.n" center above at B.n
+print "this is a test: " /*comment ignored*/, $abc
+print "Colors:", Orange, Black, White, Red, Green, Blue
+
+#   line from B.ne + (0.05,0) right 1.0 then down 2.5 then left 1.5

+ 28 - 0
pikchr.mod/pikchr/tests/test08.pikchr

@@ -0,0 +1,28 @@
+     debug = 1;
+
+     box "one" width 80% height 80%
+     box "two" width 150% color DarkRed   # Comment does not mask newline
+     arrow "xyz" above                   // Comment does not mask newline
+     box "three" height 150% color DarkBlue
+     down
+     arrow
+B4:  box "four"
+B45: box "4.5" fill SkyBlue
+     move
+B5:  box "five"
+     left
+B6:  box "six"
+     up
+     box "seven" width 50% height 50%
+
+     line from 0.1 right of B4.e right 1 then down until even with B5 \
+         then to B5 rad 0.1 chop
+
+     arrow from B6 left even with 2nd box then up to 2nd box chop rad 0.1
+     arrow from 1/2 way between B6.w and B6.sw left until even with first box \
+         then up to first box rad 0.1 chop
+
+oval wid 25% ht B4.n.y - B45.s.y at (B6.x,B4.s.y)
+arrow from last box to last oval chop
+arrow <- from B4.w left until even with last oval.e
+arrow <- from B45 left until even with last oval.e chop

+ 3 - 0
pikchr.mod/pikchr/tests/test09.pikchr

@@ -0,0 +1,3 @@
+HEAD: circle
+      circle radius 50% with .se at HEAD.nw
+      circle radius 50% with .sw at HEAD.ne

+ 58 - 0
pikchr.mod/pikchr/tests/test10.pikchr

@@ -0,0 +1,58 @@
+C: "+";    $x = 0
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C
+   $x = $x + 10
+   line from 0.5 heading $x from C to 1.0 heading $x from C

+ 7 - 0
pikchr.mod/pikchr/tests/test12.pikchr

@@ -0,0 +1,7 @@
+circle "One"
+arrow
+circle "Two"; down
+arrow down 40%
+circle "Three"
+move
+circle "Four"

+ 6 - 0
pikchr.mod/pikchr/tests/test13.pikchr

@@ -0,0 +1,6 @@
+// margin = 1
+line up 1 right 2
+linewid = 0
+arrow left 2
+move left 0.1
+line <-> down 1 "height " rjust

+ 20 - 0
pikchr.mod/pikchr/tests/test14.pikchr

@@ -0,0 +1,20 @@
+print "1in=",1in
+print "1cm=",1cm
+print "1mm=",1mm
+print "1pt=",1pt
+print "1px=",1px
+print "1pc=",1pc
+scale = 0.25
+circle "in" radius 1in
+circle "cm" radius 2.54cm
+circle "mm" radius 25.4mm
+circle "pt" radius 72pt
+circle "px" radius 96px
+circle "pc" radius 6pc
+
+circle "in" radius 0.5*1in with .n at s of 1st circle
+circle "cm" radius 0.5*2.54cm
+circle "mm" radius 25.4mm * 0.5
+circle "pt" radius 72pt * 0.5
+circle "px" radius 96px * 0.5
+circle "pc" radius 6pc  * 0.5

+ 9 - 0
pikchr.mod/pikchr/tests/test15.pikchr

@@ -0,0 +1,9 @@
+ellipse "document"
+arrow
+box "PIC"
+arrow
+box "TBL/EQN" "(optional)" dashed
+arrow
+box "TROFF"
+arrow
+ellipse "typesetter"

+ 1 - 0
pikchr.mod/pikchr/tests/test16.pikchr

@@ -0,0 +1 @@
+box "this is" "a box"

+ 1 - 0
pikchr.mod/pikchr/tests/test17.pikchr

@@ -0,0 +1 @@
+line "this is" "a line"

+ 1 - 0
pikchr.mod/pikchr/tests/test18.pikchr

@@ -0,0 +1 @@
+box width 3 height 0.1; circle radius 0.1

+ 1 - 0
pikchr.mod/pikchr/tests/test19.pikchr

@@ -0,0 +1 @@
+line up right; line down; line down left; line up

+ 1 - 0
pikchr.mod/pikchr/tests/test20.pikchr

@@ -0,0 +1 @@
+box dotted; line dotted; move; line dashed; box dashed

+ 4 - 0
pikchr.mod/pikchr/tests/test21.pikchr

@@ -0,0 +1,4 @@
+line right 5 dashed; move left 5 down .25; right
+line right 5 dashed 0.25; move left 5 down .25; right
+line right 5 dashed 0.5; move left 5 down .25; right
+line right 5 dashed 1;

Некоторые файлы не были показаны из-за большого количества измененных файлов