1 Beginning Student Language (BSL)
1.1 BSL Grammar
1.1.1 Literals Numeric literals String literals
1.1.2 Indentation
1.2 Pre-defined variables
1.3 Template Variables
1.4 BSL Syntax
1.4.1 def
1.4.2 fun
1.4.3 struct
1.4.4 if
1.4.5 test
1.4.6 big_ bang
1.5 BSL Primitive Operations
1.6 Infix operators
1.7 Sequences


William R. Turtle

Pyret is a new programming language designed to let learners use functional programming to build interesting and powerful programs with an economy of expression. It borrows liberally from many languages—as such, it should be instantly recognizable.

In the vein of the How to Design Programs languages, Pyret has been broken up into three different language levels.

Currently, only the first language level (Beginning Student Language) is available.

1 Beginning Student Language (BSL)

 #lang wrturtle/pyret/bsl

1.1 BSL Grammar

This is the full BSL grammar. For a gentler introduction to the language, please see the programs in the examples folder.

NOTE: braces are not part of the grammar – they are used to indicate groupings of tokens. Also, <nl> refers to <newline>. Similarly, <s-e> refers to <simple-expr>, <e> refers to <expr>, <t-e> refers to <test-expr>, and <id-l> refers to <id-list>.


<program>                ::= <def-expr-test-bang-nl>*

<def-expr-test-bang-nl>  ::= <def-expr-test-bang>

                          |  <newline>

<def-expr-test-bang>     ::= <definition>

                          |  <expr>

                          |  <test-case>

                          |  <big-bang>



<definition>             ::= <def-style-definition>

                          |  <fun-style-definition>

                          |  struct: <id> (<id-list>?) <newline>

<def-style-definition>   ::= def <id>: <expr>

<fun-style-definition>   ::= fun <id>(<id-list>?): <test-expr> <nl>

                          |  fun <id>(<id-l>?):<nl> <local-defs>* <e>

<id-list>                ::= <id> {, <id>}*

<local-defs>             ::= <def-style-definition>

                          |  <fun-style-definition>



<expr>                   ::= <test-expr> <newline>

                          |  <compound-expr>

<test-expr>              ::= <simple-expr>

                          |  <boolean-comparison>

                          |  <test-expr> and <test-expr>

                          |  <test-expr> or <test-expr>

                          |  not <test-expr>

<boolean-comparison>     ::= <simple-expr> <boolean-comp-simple>+

<boolean-comp-simple>    ::= <boolean-comp-op> <simple-expr>

<boolean-comp-op>        ::= < | <= | = | != | >= | > | in

                          |  not in

<simple-expr>            ::= <value-expr>

                          |  + <simple-expr>

                          |  - <simple-expr>

                          |  <simple-expr> + <simple-expr>

                          |  <simple-expr> - <simple-expr>

                          |  <simple-expr> * <simple-expr>

                          |  <simple-expr> / <simple-expr>

                          |  <simple-expr> % <simple-expr>

                          |  <simple-expr> ** <simple-expr>

                          |  <value-expr>[<index-expr>]

                          |  <template-expr>

<index-expr>             ::= <s-e>

                          |  <s-e>:<s-e>

                          |  <s-e>:<s-e>:<s-e>

<template-expr>          ::= ..

                          |  ...

                          |  ....

                          |  .....

                          |  ......

<value-expr>             ::= empty

                          |  True

                          |  False

                          |  <id>

                          |  <number>

                          |  <image>

                          |  <string>

                          |  <list-expr>

                          |  (<test-expr>)

                          |  <id> (<id-list>?)

<list-expr>              ::= [ ]

                          |  [<simple-expr>{, <simple-expr>}*]

<compound-expr>          ::= <conditional>

<conditional>            ::= <if-expr> <else-expr>

                          |  <if-expr> <elif-expr>+ <if-end-marker>

<if-end-marker>          ::= <else-expr>

                          |  :done

<if-expr>                ::= if <test-expr>: <newline>? <expr>

<elif-expr>              ::= elif <test-expr>: <newline>? <expr>

<else-expr>              ::= else: <newline>? <expr>




<test-case>              ::= test: <t-e> is: <t-e> <nl>

                          |  test: <t-e> is: <t-e> within: <t-e> <nl>

                          |  test_error: <t-e> <nl>

                          |  test_error: <t-e> matches: <t-e> <nl>

                          |  test_range: <t-e> from: <t-e> to: <t-e>




<big-bang>               ::= big_bang(<bb-clause>*) <newline>

<bb-clause>              ::= <id> = <test-expr> {, <bb-clause>}*


1.1.1 Literals Numeric literals

There are four types of numeric literals: integers, floating point, imaginary, and inexact.

Integer literals come in four varieties: decimal integers, binary integers, octal integers, and hexadecimal integers. Binary integers start with the prefix 0b or 0B, octal integers start with the prefix 0o or 0O, and hexadecimal integers start with the prefix 0x or 0X.

Some examples of integer literals:


Floating point numbers are lexed just as they are in Racket.


Imaginary numbers are decimal integers or floating point numbers suffixed with the letter i. (Note that a number of the form a+bi is parsed as the addition of a real number and an imaginary number with a real part of 0.)


Finally, a literal can be prefixed with 0nx, to indicate that it is inexact. (Here, a complex number such as 0nx2+3i parses as the inexact number 2+3i, NOT the inexact number 2 + 3i).

0nx4i String literals

String literals are read in exactly the same manner as they are in Racket. See the Racket reference for more information.

1.1.2 Indentation

Pyret enforces indentation. A valid piece of code will not run if it is not indented properly. Most of the time, this will not be a problem, since the majority of expressions can be typed in just one line. Some syntaxes, such as fun and if, require multiple lines, which must follow one of two indentation rules.

The first rule is same-line/greater-column (SLGC). Two pieces of code obey this rule when the second piece of code is either on the same line as the first, or is indented at least 2 spaces. For example, both

fun plus1(x): x + 1


fun plus1(x):

  x + 1

are well-indented. However,

fun plus1(x):


is not well-indented, and will result in an error. The documentation of each syntactic form specifies all indentation rules that it follows.

The second rule is same-line/same-column (SLSC). Either the two pieces of code must be on the same line, or they must be indented by the same amount. For example, in an if statement, all branches must be lined up at the same level:

fun sgn(x):

  if x < 0: -1

  elif x = 0: 0

  else: 1

1.2 Pre-defined variables

empty : is_empty

The empty list.

True : is_true

The boolean value corresponding to truth.

False : is_false

The boolean value corresponding to falsehood.

1.3 Template Variables

A placeholder for indicating that a definition is a template
A placeholder for indicating that a definition is a template
A placeholder for indicating that a definition is a template
A placeholder for indicating that a definition is a template
A placeholder for indicating that a definition is a template

1.4 BSL Syntax

1.4.1 def

def <id>: <expr>

Evaluates <expr>, and binds it to the given identifier. The <id> cannot be the same as that of another function or variable, and it cannot appear in the expression.

1.4.2 fun

fun <id>(<id> {, <id>}*): <test-expr> <newline>

fun <id>(<id> {, <id>}*): <newline> <local-defs>* <expr>

This piece of syntax defines a function. For example, fun plus1(x): x + 1 defines a function called plus1, which consumes one variable (x), and evaluates to x + 1. The second form allows local definitions, so, for example,

fun outer(x):

  fun inner(y): y


defines two functions, one called outer, and one called inner. The function inner is only available in the scope of outer.

Functions are required to consume at least one argument. Furthermore, functions are first-order in BSL – after defining a function called outer, the expression outer would result in an error.

Function application looks like it does in algebra:

fun plus1(x): x + 1


> plus1(1)


fun follows the same-line/greater-column rule. So, typing

fun plus1(x):

x + 1

results in an indentation error.

However, if local definitions are present, all such definitions must follow the same-line/same-column rule. So,

fun add3(x):

  def one: 1

  def two: 2

  x + one + two

is indented correctly, but

fun add3(x):

  def one: 1

def two: 2

  x + one + two

is not.

1.4.3 struct

Struct has the following form:

struct: <id>(<id-list>?) <newline>

struct provides the ability to define structures. As an example,

struct: point3d(x,y,z)

defines two functions, point3d, and is_point3d. The function point3d will take three arguments, which are the values for the three fields x, y, and z. At the BSL level, these values cannot be changed.

def my_point: point3d(1,2,3)

The function is_point3d returns True if and only if its argument was constructed with point3d.

> is_point3d(my_point)


> is_point3d("hello world")


To access the fields, use the dot operator.

> my_point.x


> my_point.y


1.4.4 if

If statements in Pyret look very similar to if statements in Python.

fun sgn(x):

  if x < 0: -1

  elif x = 0: 0

  else: 1

If statements can only have one expression after the colon. Nested if statements require a newline, and follow the SLGC indent rule.

Else statements are not required, but when not using one, the :done keyword is required to let Pyret know that you have finished the if statement.

fun sgn(x):

  if x < 0: -1

  elif x = 0: 0

  elif x > 0: 1


Pyret will signal an error if you fall through the if statement.

1.4.5 test

There are five different forms of test cases.

test: <test-expr> is: <test-expr>

This test case succeeds if the two given <test-expr> clauses are structurally equal.

test: <test-expr> is: <test-expr> within: <test-expr>

test_error: <test-expr>

Succeeds when the given expressions results in an error that is called by user code.

test_error: <test-expr> matches: <test-expr>

This is the same as the other form of test_error, but the error messaged that is raised needs to match the second expression.

test_range: <test-expr> from: <test-expr> to: <test-expr>

Checks that the first expression is a number in between the second and third expressions.

1.4.6 big_bang

Here is an example of a well-formed big_bang clause.

big_bang(init = 2, to_draw = draw_function)

Semantically, it is identical to the HtDP big-bang. Clause names which formerly contained hyphens have been replaced with names containing underscores (e.g. to-draw become to_draw). New clauses have been added whenever an HtDP clause accepts multiple expressions. For example, the HtDP big-bang’s to-draw clause could take one expression (draw-expr), or three expressions (draw-expr, width-expr, height-expr). To enable this in Pyret, the to_draw clause recognizes two dependent clauses, draw_width, and draw_height. Thus, the equivalent of

(big-bang 2

          (to-draw draw_expr width_expr height_expr))


big_bang(init = 2, to_draw = draw_expr, draw_width = width_expr, \

         draw_height = height_expr)

Here is a list of the new clause names.


tick_rate (dependent on `on_tick`)

tick_limit (dependent on `on_tick`)






draw_width (dependent on `to_draw`)

draw_height (dependent on `to_draw`)


last_scene (dependent on `stop_when`)



1.5 BSL Primitive Operations

Pyret’s BSL provides almost all of the same primitive operations that htdp/bsl+ (i.e., Beginning Student Language with list abbreviations) provides. Additionally, since Pyret does not have require, it also provides all functions found in the 2htdp/image, 2htdp/batch-io, and 2htdp/universe teachpacks.

Some of these function names cannot be parsed properly in Pyret (e.g., string-length would parse as (- string length)). Therefore, there are some renaming conventions to be aware of.

Hyphens and slashes are converted to underscores. So, the function string-length becomes string_length. Predicates go from being foo? to is_foo, so number? becomes is_number. Functions like number->string become number_to_string. The only functions that are outliers are shown below:

string<?           => string_lt

string<=?          => string_leq

string=?           => string_equal

string>=?          => string_geq

string>?           => string_lt


string-ci<?        => string_ci_lt

string-ci<=?       => string_ci_leq

string-ci=?        => string_ci_equal

string-ci>=?       => string_ci_geq

string-ci>?        => string_ci_gt


string-lower-case? => string_is_lower_case

string-upper-case? => string_is_upper_case

string-alphabetic? => string_is_alphabetic

string-whitespace? => string_is_whitespace

string-numeric?    => string_is_numeric


string-contains?   => string_contains


=~                 => approx_equal

equal?             => equal

The car and cdr functions are not provided (use first and rest instead), and there is no is_cons function (use is_list). This is because Pyret does not support dotted pairs. Additionally, there is no such thing as a `character` in Pyret, so there are no function involving characters (except for string_ref, which returns a one-character string).

Finally, with the exception of images, structs no longer have accessor functions, so there is no posn_x and posn_y function (use `.` instead). The dot operator will work with images, but functions might be more readable.

The eq and eqv functions are not available (and therefore neither are memq, memv, remq, and remv).

1.6 Infix operators

The arithmetic operators +, -, *, /, % (modulo), and ** (exponentiation) are provided, as well as the boolean operators <, <=, =, >=, >, and != (not equal). Boolean operators can be chained as in Python:

> 2 < 3 <= 4


(here the `>` represents the prompt, not the greater-than sign). Note that we use = for equality testing, not ==.

1.7 Sequences

Strings and lists are sequence-types, in the Python sense. All of Python’s sequence operations work on these two datatypes, except for the `index` and `count` methods (there are no objects in Pyret’s BSL). This means that, in Pyret, as in Python, the expression "hello" + "world" produces "helloworld", for example.