PIC [4] is a language for drawing simple pictures, such as trees and block diagrams. It has primitives for drawing boxes, circles, and other shapes, with or without labels, and for drawing lines and arrows between them. It also has a facility for naming points on pictures, to be used, for example, as the endpoints of lines and arrows. These constructs are provided in a concise syntax, with a simple language structure (including loops) added on.
FPIC was inspired by PIC. Our goal was to demonstrate that we could benefit by following the language design philosophy outlined in the introduction. That is, by using essentially the same primitive data types and operations as in PIC, but embedding them in a functional language, we could obtain a far more powerful language than PIC and do so at a far lower cost than if we had built the language from scratch.
In this section, we present a few examples to show the principal primitive operations of FPIC, making only minimal use of the programming features of Standard ML. Section 5 gives many more examples, emphasizing the utility of the features of ML in combination with the FPIC primitives.
The most basic primitives are those for drawing simple shapes and placing them next to one another: 1
box 1.0 2.0 hseq circle 1.5 vseq label "\\Huge Hello!" (oval 2.0 1.0);
hseq and vseq represent the operations of placing pictures next to one another, either horizontally or vertically. (Note that backslashes inside quotes must be doubled.)
Pictures can be moved and otherwise transformed in various ways. In this example, we use ML's name definition facility in the first line. dtriangle is a "default triangle;" similarly for doval (and dcircle and dbox later in the paper).
val nose = dtriangle; circle 2.5 seq (nose at (2.0,2.0)) seq (nose rotate ~90.0 scale 0.7 at (1.2,2.7)) seq (nose rotate 90.0 scale 0.7 at (3.1,2.7)) seq doval scaleXY (0.5,0.3) at (1.7,0.7);
The seq operation simply places pictures on top of one another, without moving them either right or down. The expression pic at point draws the picture pic with its reference point (the lower-left corner) at point.
An important feature of PIC, which we have adopted in FPIC, is the ability to name points in a picture and subsequently refer to them. The compass points--s for south, ne for northeast, and so on, plus c for center--are automatically defined for every picture. This allows us to eliminate some of the guesswork in the previous example:
val face = circle 2.5; val facecenter = face pt "c"; val lefteye = nose rotate ~90.0 scale 0.7; val righteye = nose rotate 90.0 scale 0.7; val mouth = doval scaleXY (0.5,0.3); face seq (nose centeredAt facecenter) seq (lefteye centeredAt (facecenter -- (1.0,~0.7))) seq (righteye centeredAt (facecenter ++ (1.0,0.7))) seq (mouth centeredAt (facecenter -- (0.0,1.5)));
Named points can be added to a picture. A third way to produce the same "pumpkin face" is to draw the face and name the locations of the facial features:
val face = let val f = circle 2.5 val facecenter = f pt "c" in namePts f [("nosepos", facecenter), ("lefteyepos", facecenter -- (0.9,~0.8)), ("righteyepos", facecenter ++ (0.9,0.8)), ("mouthpos", facecenter -- (0.0,1.5))] end; face seq (nose centeredAt (face pt "nosepos")) seq (lefteye centeredAt (face pt "lefteyepos")) seq (righteye centeredAt (face pt "righteyepos")) seq (mouth centeredAt (face pt "mouthpos"));
Pictures can be named as well as points. This is useful when a number of points on a picture may be of interest. For example, cell is a "cons cell" consisting of two boxes vertically stacked; for future reference, the individual boxes are named car and cdr, respectively:
val cell = namePic dbox "car" vseq namePic dbox "cdr";
In addition to optionally being named, subpictures are automatically numbered. p nthpic i is the ith subpicture in p.
Finally, another important feature of PIC and FPIC is the ability to draw lines and arrows. In this picture, two cells are drawn with a curved arrow from the cdr of the first to the car of the second:
let val cells = (namePic cell "left") hseq (hspace 1.0) hseq (namePic cell "right") val source = cells pic "left" pic "cdr" pt "c" val target = cells pic "right" pic "car" pt "w" in cells seq (bezier source (source ++ (1.0,0.0)) (target -- (1.0,0.0)) target withArrowStyle "->") end;
1. Appendix A gives a concise overview of ML syntax. Appendix B lists all the FPIC primitives used in the examples in this paper, indicating the types of their arguments, whether or not they are infix, and what they return.