Share your Programming Language Jam submissions here!

← Back to all posts
gram: a declarative drawing language
apexedison (1)

gram: a declarative drawing language

team: @gramlang
members: @apexedison @picobrian

Usage

Type your gram program in the main constant in the "main.js" file.

Description

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.

Function Declarations

to name param0: param1: …
...
end

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.

Conditionals

if ... then ...
if ... then ... else ...

Where each ... represents 1 expression.

Assignment

=

Assignments don't have to be declared with special token like "let" or "var".

Literals

number: all floats
string: signified with ""
boolean: true | false

Loops

for i in array
...
end

"i in" is optional

Arrays

[]
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]

Expressions

()
Parenthesis generally operate as expected but also can be used to contain expression blocks.

x = (1 2)
x == 2

Previous

_

Returns the result of the previous expression. It can be used to enable piping or chaining like behavior.

Comments

#

Comments are created with # and run till the end of the current line.

Operators

0: "="
1: "||"
2: "&&"
3: "<" ">" "<=" ">=" "==" "!="
4: "+" "-"
5: "*" "/" "%"
6: "**"
7: ".."

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]

Built-Ins

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

Example Program

# 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":

Acknowledgments

Thanks to Brian Silverman and Artemis Papert for their advice while crafting gram and for their continued support.

Usage (Again)

Type your gram program in the main constant in the "main.js" file.

Commentshotnewtop
drsensor (16)

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 🤔

apexedison (1)

@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.