A website in OCaml

Hello world

I needed somewhere to write things. LinkedIn felt too weird and I don't use twitter. I also had some requirements for the posts I wanted to create. Nothing advanced, but I wanted it to be so easy to include code, math or visualizations that I didn't really have to think about it. I ended up creating my own markdown inspired language.

Why OCaml?

I've long been fascinated with functional programming. Haskell never really clicked, but OCaml seemed like a practical alternative. Since I had decided I needed my own custom language I was essentially going to write a very simple compiler. OCaml turned out to be a perfect choice for this, it's great for parsing.

How it works

It's not advanced, it's actually the opposite. Very simple and boring. But systems you use tend to be simple and boring.

The whole pipeline is just a translation layer. We take in my markdown-esque syntax and we spit out static html files.

Lexer

type token =
    | Text of string
    | Star
    | Backtick
    | Dollar
    | Open_bracket
    | Close_bracket
    | Open_paren
    | Close_paren
    | Bang
    | Gt
    | Heading_marker of int
    | Triple_dash
    | At_block of string * string option * string option
    | Newline
    | Blank_line
    | Eof

The starting point is just parsing tokens. Each token is a variant and the lexer produces a list of them from the source. let tokenize source: string : token list.

Parser

The token list from the lexer is then consumed by the parser. It's a recursive descent parser. We look at the current token, consume, recurse. This is very pleasant in OCaml, we just pattern match on the token list:

let rec parse_blocks (tokens : token list) : Ast.block list * token list =
  match tokens with
  | Eof :: _ -> ([], tokens)
  | Heading_marker n :: rest ->
    let (content, rest2) = parse_inlines rest in
    let (more, rest3) = parse_blocks rest2 in
    (Ast.Heading { level = n; content } :: more, rest3)
  | At_block(kind, args, body) :: rest ->
    ...
  | _ -> 
    let (content, rest) = parse_inlines tokens in
    let (more, rest2) = parse_blocks rest in
    (Ast.Paragraph content :: more, rest2)

Every parser returns the parsed result and the remaining tokens. That's how parsers chain without shared state. This feels a bit new if you have not touched functional programming before.

From the parser we return an AST and with that just string replaces to create our HTML document.

All the code is on github

this page was written in the language it describes :D