Outlet

Semantics

Outlet is made up of S-expressions, like Lisp. S-expressions use parenthesis to dictate structure, like (one (two three)). In that statement, two and three are part of the same element which is a child of one. You can program with this by applying a few semantics to the structure. The result is a really simple but powerful language which can parse itself, and do a lot of neat things.

Outlet’s semantics are documented here. Outlet is Scheme-inspired but adds a lot on top of it for practicality.

Atoms

An atom is a basic building block of the language. It can be a:

A number can be in integer or real form with a + or – sign prefixed to it. Extended syntaxes TBD.

A string is surrounded by double-quotes. Use the backslash escape character to include double-quotes or other special characters in the string, such as \n (newline) and \t (tab).

A boolean is simply a constant that significies truthiness. #t means true and #f means false.

atoms
1
2
3
4
5
6
7
8
9
10
11
12
13
;; numbers
3
4.5
-5.0

;; strings
"hello"
"I read \"a book\" yesterday"
"See below:\n foo bar baz"

;; booleans
#f
#t

A symbol is simply an identifier that is always equal to another symbol with the same characters. It can contain the following characters in addition to the alphanumeric set: -, ?, !. A symbol can be created by “quoting” it like 'foo.

The apostrophe character (’) quotes the term foo and Outlet reads it as a symbol. Without the quote, Outlet would read foo as a variable reference. In this way, variable references are basically evaluated symbols.

Symbols are similar to strings but provide different semantics which are very helpful. For instance, symbols that are spelled exactly the same way are always equal to other, and are quick to use.

symbols
1
2
3
'foo
'bar-baz
'hello?

Lists and Functions

A list is a simply several atoms surrounded by parentheses:

1
'(1 2 "three" four)

Lists are “quoted” with the apostrophe character meaning that Outlet reads the list in literal form. What you see if what you get. That means that “four” is not a variable reference but rather just a symbol.

Function calls look similar to lists, but they aren’t quoted:

1
(foo 1 2 "three" four)

Unquoted lists call the function in the first element. Here, foo would be called with the 4 arguments following it. Also, four is a variable reference in this example because it is unquoted. All arguments are eagerly evaluted, so whatever four references is what would be passed.

If you want to make a list and include four as a variable, simply use list:

1
(list 1 2 "three" four)

This is similar to the first example except that four is eagerly evaluated and the value it references is passed as an argument, rather than the symbol “four”. The first example expressed in list form would be:

1
(list 1 2 "three" 'four)

Creating/Defining Functions

To define a new function, use define.

1
2
(define (foo x y z)
  (+ x y z))

The name of the function and arguments are passed as a list, and the the body of the function afterwards. The value of the last statement in the function is returned.

1
(foo 1 2 3) ; -> 5

In addition to define, use lambda to create an anonymous function.

1
2
(lambda (x y z)
  (* (+ x y) z))

lambda takes an argument list and returns a function that can be called normally.

Assigning variables (side effects)

To assign a value to a variable, use set!. The exclamation point implies that the function has a side effect, which is important to keep track of. Side effects are very practical but they can have bad consequences too (even if it’s just messy code).

1
2
(set! foo 5)
(set! foo (lambda () (* 1 2 3)))

The return value of set! is undefined, so do not use it as an expression.

Conditional Operators (and/or/etc.)

To combine boolean values, use and, or, and not:

1
2
3
(and #t #f) ; -> #f
(or #t #f) ; -> #t
(not #f) ; -> #t

and and or always return the value the last expression, and are short-circuited so that evaluation stops at the first failure.

1
2
3
4
5
(and x (do-something x)) ; returns whatever @do-something@ returns

(define x #t)
(define y #f)
(and x y (do-something x y)) ; returns #f, @do-something@ doesn't execute

Conditional Constructs (if/cond/etc.)

There are a few constructs to controlling the flow of the program based on conditions. if evaluates a condition and if true, executes the second expression and if false, executes the third expression.

1
2
3
(if (some-condition)
    (do-this)
    (do-that-instead))

The third expression (the “else”) is not required.

cond is an extended form of if that lets you quickly test against several conditions.

1
2
3
4
5
6
(define x 3)
(cond ((eq? x 0) 'zero)
      ((eq? x 1) 'one)
      ((eq? x 2) 'two)
      ((eq? x 3) 'three))
; -> 'three

cond takes a list of of statements, where the first element is the condition and the rest of the elements are what to execute if the condition is true. You can run multiple expressions after the condition. else is special keyword that indicates code to run if none of the conditions are met; it is optional.

Expressions with begin

begin simply takes a list of expressions and allows you to run them in places that only accept one expression.

1
2
3
4
5
6
(if (some-condition)
    (begin
      (define x 5)
      (foo x)
      (set! x 10)
      (bar x)))

Comments

Use the semicolon character (;) to indicate comments.

1
2
;; this is a comment
(foo 1 2 3)

Todo

These are undocumented features/semantics that I need to document and test: