Language tutorial

Go to the index

The language is not meant to be a replacement for HTML, it's just a tool for rendering HTML to style a blog post. There's still a lot of work to do, but it's already pretty useful, at least in my view.


The syntax of the language is based on S-expressions in lisp: to call a function f with arguments x, y, z, you write (f x y z). For example, 2 + (3 * 5) would be written as (+ 2 (* 3 5)). Apart from that, you have names (like porlist-unordered), strings ( like "hello") and integers (like 42).

The entire document is just one big expression.

Some examples of markup, just to give you an idea:


A string is a valid document:

"Hello, world"

Hello, world

You can concatenate things with the $ function:

($ "Hello, " "world!")

Hello, world!

Some functions represent HTML tags:


(p              ; paragraph
  (bf "Hello")  ; boldface
  (it "World")  ; italics
  (tt "Aaaaa")  ; monospaces text

  ; heading:
  ((h 3) "To do:")
  (list-unordered
    "Buy milk"
    "Submit a pull request"
    "Solve fizzbuzz"))

HelloWorldAaaaa

To do:

  • Buy milk
  • Submit a pull request
  • Solve fizzbuzz


Strong typing

Some languages are powerful not because of something you can do with them, but rather because of something you can't. Strict languages like Haskell or Rust are a good example of that.


(b (p "text"))

This results in an error: Cannot call (λ ...inline . inline) with (block) (line 71, column 3)

Why can't you do that? It seems perfectly fine, but it isn't: p is a 'block' element, while b is an 'inline' element, and you can't put a 'block' element inside an 'inline' element. You can go to the W3C validator and see for yourself that <b><p>text</p></b> is a no-no.


Inspecting types

You can inspect the type of an object by using the type function:


(list-unordered
  (tt "\"hello\": " (type "hello"))
  (tt "5: " (type 5))
  (tt "p: " (type p))
  (tt "$: " (type $))
  (tt "h: " (type h))
  (tt "nobr: " (type nobr))
  (tt "horizontal-rule: " (type nobr))
)
  • "hello": str
  • 5: int
  • p: (λ ...inline|block . block)
  • $: (λ ...inline . inline)|(λ ...inline|block . inline|block)
  • h: (λ int . (λ ...inline . block))
  • nobr: (λ inline . inline)|(λ block . block)
  • horizontal-rule: (λ . block)

Let's break down the function types:


Higher-order functions

There's a type that seems strange: h: (λ int . (λ ...Inl . block))

Just like in Python or in many other computer languages, functions are first-class values: they can be created, returned, and passed as arguments to other functions. So h is a function that takes an integer and returns a new function, which in turn accepts any number of inline-ish elements and returns a block HTML element.

map is another example of a higher-order function. its type is (λ (λ inline . inline) . (λ  ...inline . inline))|(λ (λ  ...inline . inline) . (λ  ...inline . inline))|(λ (λ str . inline) . (λ  ...str . inline))|(λ (λ  ...str . inline) . (λ  ...str . inline))|(λ (λ block|inline . block) . (λ  ...block|inline . block))|(λ (λ  ...block|inline . block) . (λ  ...block|inline . block)). That's a very long type, but basically, map allows you to apply a function to a list of values.


((map p) "First paragraph" "Second paragraph" "Third paragraph")

First paragraph

Second paragraph

Third paragraph

sep allows you to separate a list of values with a separator you give it.


(p
  ((sep ", ") "int" "str" "list"))

int, str, list

Ah, but what if you need to sep and map? In that case you can use sepmap!


(p
  ((sepmap ", " tt) "int" "str" "list"))

int, str, list


Documentation for all built-in functions

bf : (λ ...inline . inline)

Boldface text. Represents the <b> HTML tag.

it : (λ ...inline . inline)

Italic text. Represents the <i> HTML tag.

tt : (λ ...inline . inline)

Monospace text. Represents the <tt> HTML tag.

mono : (λ ...inline . inline)

Like tt, but prevents text inside from line-wrapping.

e : (λ str . inline)

Creates an HTML entity. (e "mdash") renders as &mdash;.

$ : (λ ...inline . inline)|(λ ...inline|block . inline|block)

Concatenate multiple elements.

h : (λ int . (λ ...inline . block))

Heading. Represents the <h1>..<h6> HTML tags.

style : (λ str . (λ ...inline . inline))

Applies CSS styling to an inline element. Use sparingly.

list-unordered : (λ ...inline . block)

Represents the <ul> HTML tag.

list-ordered : (λ ...inline . block)

Represents the <ol> HTML tag.

p : (λ ...inline|block . block)

Represents the <p> HTML tag.

a : (λ str inline . inline)

Represents the <a> HTML tag.

horizontal-rule : (λ . block)

Represents the <hr/> HTML tag.

-- : (λ . inline)

The "em dash" (&mdash;).

pre : (λ ...inline . block)

Represents the <pre> HTML tag. Make sure to use a multiline ("""...""") string here.

map : (λ (λ inline . inline) . (λ ...inline . inline))|(λ (λ ...inline . inline) . (λ ...inline . inline))|(λ (λ str . inline) . (λ ...str . inline))|(λ (λ ...str . inline) . (λ ...str . inline))|(λ (λ block|inline . block) . (λ ...block|inline . block))|(λ (λ ...block|inline . block) . (λ ...block|inline . block))

Map a function onto a list of values. The overloads are messy and will soon be refactored.

sepmap : (λ inline (λ inline . inline) . (λ ...inline . inline))|(λ inline (λ ...inline . inline) . (λ ...inline . inline))|(λ str (λ str . inline) . (λ ...str . inline))|(λ str (λ ...str . inline) . (λ ...str . inline))

Combination of sep and map.

sep : (λ inline . (λ ...inline . inline))

Concatenate inline elements, separating them with a given string.

nobr : (λ inline . inline)|(λ block . block)

Replaces spaces in text with &nbsp; so that it's not subject to text wrapping. Does not represent the nobr HTML TAG.

type : (λ any . inline)

Renders the type of a value as a string.

doc : (λ any . inline)

Render the documentation for a function.

+ : (λ ...&[name]|&[(name str)]|inline|block . block)

Part of the 'fnl.x' module. Shortcut for b &div

b : (λ str|&[name] ...&[str]|&[name]|&[(name str)]|inline|block . block)

Part of the 'fnl.x' module. Creates a block element. See the 'Quoted expressions' tutorial for more info.

i : (λ str|&[name] ...&[name]|&[(name str)]|inline . inline)

Part of the 'fnl.x' module. Creates an inline element. See the 'Quoted expressions' tutorial for more info.

var : (λ &[name] . any)

Part of the 'bindings' module. (var &x) is used to look up the binding x defined in a 'let' or 'for' clause.

let : (λ ...&[(name any)] . (λ &[any] . any))|(λ &[name] any &[any] . any)

Part of the 'bindings' module. Defines a scope in which certain bindings refer to certain expressions. Examples: (let &answer 42 &(bf (var &answer))), ((let &(answer 42) &(pi 3)) &(bf (var &answer) "..." (var &pi)))

foreach : (λ &[name] &[any] &[any] . any)

Part of the 'bindings' module. For each element of a 'list', render some expression while binding the element to a specific name. (foreach &i &(1 2 3 4) &($ (bf (var &i)) " ") will render boldface numbers 1, 2, 3 and 4 separated by spaces.

unquote : (λ &[any] . any)

Part of the 'bindings' module. Extract and evaluate the expression from under the &

extract-name : (λ &[name] . str)

Part of the 'bindings' module. Convert a quoted name to a string

documented-names : (λ . &[any])

Part of the 'bindings' module. Get a list of all the documented names as a quoted s-expression: &(&bf &it &tt ...)

debug : (λ any . str)

Part of the 'bindings' module. Render an expresion for debugging purposes

Source:

($docs $filename $source "Language tutorial"
  (p
    "The language is not meant to be a replacement for HTML,
    it's just a tool for rendering HTML to style a blog post. There's still a
    lot of work to do, but it's already pretty useful, at least in my view.")

  (horizontal-rule)

  (p
    "The syntax of the language is based on S-expressions in lisp: to call
    a function " (mono "f") " with arguments " ((sepmap ", " mono) "x" "y" "z")
    ", you write " (mono "(f x y z)") ". For example, " (mono "2 + (3 * 5)")
    " would be written as " (mono "(+ 2 (* 3 5))") ". Apart from that, you have
    names (like " (mono "p") "or" (mono "list-unordered") "), strings ( like "
    (mono "\"hello\"") ") and integers (like " (mono "42") ").")

  (p
    "The entire document is just one big expression.")

  (p
    "Some examples of markup, just to give you an idea:")

  (horizontal-rule)
  ((h 3)
    "A string is a valid document:")
  (p (mono "\"Hello, world\""))
  ($box
    "Hello, world")

  (horizontal-rule)
  ((h 3)
    "You can concatenate things with the " (mono "$") " function:")
  (p (mono "($ \"Hello, \" \"world!\")"))
  ($box
    ($ "Hello, " "world!"))

  (horizontal-rule)
  ((h 3)
    "Some functions represent HTML tags:")
  (pre ($fnl """
    (p              ; paragraph
      (bf "Hello")  ; boldface
      (it "World")  ; italics
      (tt "Aaaaa")  ; monospaces text

      ; heading:
      ((h 3) "To do:")
      (list-unordered
        "Buy milk"
        "Submit a pull request"
        "Solve fizzbuzz"))
  """))
  ($box
    (p
        (bf "Hello")
        (it "World")
        (tt "Aaaaa")

        ((h 3) "To do:")
        (list-unordered
          "Buy milk"
          "Submit a pull request"
          "Solve fizzbuzz")))

  (horizontal-rule)
  ((h 3)
    "Strong typing")
  (p
    "Some languages are powerful not because of something you can do with them,
    but rather because of something you " (it "can't") ". Strict languages like
    Haskell or Rust are a good example of that.")
  (p (pre ($fnl """
    (b (p "text"))
  """)))
  (p
    "This results in an error: "
    (bf (mono "Cannot call (λ  ...inline . inline) with (block) (line 71, column 3)")))
  (p
    "Why can't you do that? It seems perfectly fine, but it isn't: "
    (tt "p") " is a 'block' element, while " (tt "b") " is an 'inline' element,
    and you can't put a 'block' element inside an 'inline' element.
    You can go to " (a "https://validator.w3.org/" "the W3C validator")
    " and see for yourself that " (mono "<b><p>text</p></b>") " is a no-no.")

  (horizontal-rule)
  ((h 3)
    "Inspecting types")
  (p
    "You can inspect the type of an object by using the " (tt "type")
    " function:")
  (pre ($fnl
  """
  (list-unordered
    (tt "\"hello\": " (type "hello"))
    (tt "5: " (type 5))
    (tt "p: " (type p))
    (tt "$: " (type $))
    (tt "h: " (type h))
    (tt "nobr: " (type nobr))
    (tt "horizontal-rule: " (type nobr))
  )
  """
  ))
  ($box
    (list-unordered
      (tt "\"hello\": " (type "hello"))
      (tt "5: " (type 5))
      (tt "p: " (type p))
      (tt "$: " (type $))
      (tt "h: " (type h))
      (tt "nobr: " (type nobr))
      (tt "horizontal-rule: " (type horizontal-rule))))

  (p
    "Let's break down the function types:"
    (list-unordered
      "The lambda (λ) just means that it's a function"
      ($ (tt "(λ a . b)") " is a function from type 'a' to type 'b'")
      ($ (tt "(λ ...a . b)") " is a function that accepts any number of 'a's an returns a 'b'")
      ($ (tt "a | b") " means \"either an 'a' or a 'b'\"")
      ($ (tt "inline") " is an inline HTML element, which also includes strings and ints.")
      ($ (tt "block") " is a block HTML element")
    )
  )

  (horizontal-rule)
  ((h 3)
    "Higher-order functions")

  (p
    "There's a type that seems strange: " (mono "h: (λ int . (λ ...Inl . block))"))
  (p
    "Just like in Python or in many other computer languages, functions are
    first-class values: they can be created, returned, and passed as arguments
    to other functions. So " (tt "h") " is a function that takes an integer
    and returns a new function, which in turn accepts any number of inline-ish
    elements and returns a block HTML element.")

  (p
    (tt "map") " is another example of a higher-order function. its type
    is " (mono (type map)) ". That's a very long type, but basically, "
    (tt "map") " allows you to apply a function to a list of values.")
  (pre ($fnl """
    ((map p) "First paragraph" "Second paragraph" "Third paragraph")
  """))
  ((map p) "First paragraph" "Second paragraph" "Third paragraph")

  (p
    (tt "sep") " allows you to separate a list of values with a separator
    you give it.")
  (pre ($fnl """
    (p
      ((sep ", ") "int" "str" "list"))
  """))
  ($box
    (p
      ((sep ", ") "int" "str" "list")))

  (p
    "Ah, but what if you need to " (mono "sep") (it " and ") (mono "map") "?
    In that case you can use " (mono "sepmap") "!")
  (pre ($fnl """
    (p
      ((sepmap ", " tt) "int" "str" "list"))
  """))
  ($box
    (p
      ((sepmap ", " tt) "int" "str" "list")))

  (horizontal-rule)
  ((h 2)
    "Documentation for all built-in functions")
  (foreach &name (documented-names)
    &($
      ((h 3) (tt (bf (extract-name (var &name))) " : " (type (unquote (var &name)))))
      (doc (unquote (var &name)))
    ))

  (horizontal-rule)
  ((h 2) "Source:")
  (pre ($fnl $source))
)