gram: a declarative drawing language
Type your gram program in the main constant in the "main.js" file.
gram is inspired by logo, the drawing language which introduced a generation of programmers to the wonderful world of computation. gram's drawing paradigm fundamentally differs from logo’s strict procedural turtle-state based technique though.
In gram all drawing methods operate on the current shape. This allows users to create declarative style descriptions of drawings.
The original motivation for creating gram was to enable crafting parametric line drawings for digital fabrication technologies like laser cutters and CNC mills. Since then it's expanded to a general purpose drawing tool and an accessible introduction to programming.
We decided to host the language on the web to support the sharing and remixing of modular designs created with it.
gram has extremely lightweight and minimal syntax. Functions have fixed arity and gather arguments by collecting proceeding expression statements. In gram everything is an expression. Arrays and bodies are simply sub-programs.
Even though gram is a complete programming language (we think a fairly accessible one) the primary purpose of the language is to create declarative drawings. gram accomplishes this with a collection of built in drawing functions. They operate on shapes with drawing heads (similar to logo's turtle). The last shape is implicitly referenced whenever a command is called. New shapes are created with "new". Shapes can be grouped together by placing them in functions.
The language structure is as follows.
to name param0: param1: …
Creates a reusable code block. The name is required, parameters are specified with a symbol immediately proceeded by “:”. "new" shapes in the function are grouped into one shape.
if ... then ...
if ... then ... else ...
Where each ... represents 1 expression.
Assignments don't have to be declared with special token like "let" or "var".
number: all floats
string: signified with ""
boolean: true | false
for i in array
"i in" is optional
Arrays are interpreted as subprograms and return the values of each separate expression they contain.
For example [0 1 2*3 4] returns [0 1 6 4]
Parenthesis generally operate as expected but also can be used to contain expression blocks.
x = (1 2)
x == 2
Returns the result of the previous expression. It can be used to enable piping or chaining like behavior.
Comments are created with # and run till the end of the current line.
3: "<" ">" "<=" ">=" "==" "!="
4: "+" "-"
5: "*" "/" "%"
They have the precedence of the order provided.
The operators are fairly standard with the exception of ".."
".." is a range operator for example 0..4 produces [0 1 2 3]
Most built in commands operate on the current shape in scope.
snapPoint will refer to a string which is "min" "center" "max" or some combination eg "min center"
new: takes no arguments -> creates a new shape to operate on
show: takes no arguments -> draws the current shape
set: takes name -> names the current shape
forward: takes number -> moves forward by number
turn: takes number -> turns by number of degrees
right: takes number -> turns right by number of degrees
left: takes number -> turns left by number of degrees
turnforward: takes number0 number1 -> turns by number0 of degrees and moves forward by number1
color: takes string -> sets shape fill to color of string
strokewidth: takes number -> sets stroke width to thickness of number
strokecolor: takes string -> sets stroke color to color of string
rotate: takes number snapPoint -> rotates shape by number of degrees around snapPoint of shape
translate: takes number0 number1 -> moves shape number0 in x direction and number1 in y direction
scale: takes number0 number1 -> scales shape number0 in x direction and number1 in y direction
moveto: takes snapPoint number0 number1 -> moves snapPoint of shape number0 x coordinate and number1 y coordinate
goto: takes number number -> moves head to number0 x coordinate and number1 y coordinate
closepath: takes no arguments -> returns to start
setangle: takes number -> sets head angle to number degrees
reverse: takes no arguments -> reverses paths of shape
arc: takes number0 number1 -> creates arc with number0 degree sweep and number1 radius
circle: takes number -> creates circle with radius of number
flip: takes string -> flips shape over x center line and/or y center line if string contains x and/or y respectively
rectangle: takes number0 number1 -> creates rectangle with number0 width and number1 height
placealong: takes shape -> copies current shape to each point along argument shape
movetopoint: takes snapPoint point -> moves snapPoint of shape to point
gotopoint: takes point -> moves head of shape to point
neg: takes number -> returns negative of number
pick: takes array -> returns random value from array
xof: takes point -> returns x value
yof: takes point -> returns y value
center: takes shape -> returns center of shape
min: takes shape -> returns bottom left point of shape
max: takes shape -> returns top right point of shape
end: takes shape -> returns end point of shape
start: takes shape -> returns start point of shape
width: takes shape -> returns width of shape as number
height: takes shape -> returns height of shape as number
# move a shape's head to draw new forward 100 right 40 forward 34 # perform transformation operations like scale, rotate, translate rotate 90 "center" # name current shape to reference it late set "shape0" # let's thicken the shape strokewidth 3 #create a new shape new for i in 0..300 forward i/10 right 91 end movetopoint "center" end shape0 # let's color the shape color "white" strokecolor "blue" strokewidth 0.2 #to group shapes put them into a function, let's group the above shapes by rewriting them to flower col: new forward 100 right 40 forward 34 rotate 90 "center" set "shape0" strokewidth 3 new for i in 0..300 forward i/10 right 91 end movetopoint "center" end shape0 color "white" strokecolor col strokewidth 0.2 end # let's draw it again and move it over so we have two flower "blue" set "x" translate width x 0 # now let's make an pattern with them to field for i in 0..10 c = pick ["green" "red" "blue" "yellow" "purple" "cyan"] flower c set "x" translate (width x)*i pick 0..20 end end field # let's not cover up the first flowers translate 0 neg 200
The above program generate this image with random color "petals":
Thanks to Brian Silverman and Artemis Papert for their advice while crafting gram and for their continued support.
Type your gram program in the main constant in the "main.js" file.
This is neat. I can use the syntax to replace G-code.
Now I'm wondering if it's possible translating G-code into this syntax too 🤔
@drsensor I'm glad that's a use case you think of right away. I've frequently used the predecessors to the project to generate G-code and have designed the underlying data structures with that in mind (meaning pretty much everything is polylines). I think the reverse is definitely manageable too. I'm also planning on developing some systems to integrate CAM operations. Let me know if you get a chance to play around with it and/or need any help doing so! It's still a bit rough but I'll work on ironing bugs out and finalizing design decisions over the next months.