Skip to content

Instantly share code, notes, and snippets.

@naartjie
Last active May 19, 2022 14:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save naartjie/87beed5f4cbc0c7aba2d4360ae1c9429 to your computer and use it in GitHub Desktop.
Save naartjie/87beed5f4cbc0c7aba2d4360ae1c9429 to your computer and use it in GitHub Desktop.
open System
open System.Text.RegularExpressions
type Expr =
| Int of int
| Symbol of string
| Exprs of Expr list
(* convert a string into a list of tokens (include parentheses, remove whitespace) *)
let tokenize (str: string) =
(* split on parentheses (capture them), spaces and newliness (but remove those) *)
Regex.Split(str, "(\(|\))| +|\n+")
|> Array.filter (fun x -> x <> "")
|> Array.toList
(* parse a list of tokens into an AST *)
let parse (tokens: string list) =
let parseToken (token: string) : Expr =
match Int32.TryParse token with
| true, i -> Int i
| false, _ -> Symbol token
let rec aux (tokens: string list) (depth: int) =
match tokens with
| [] when depth > 0 -> failwith ("balance your parentheses, missing some `)`")
| [] -> [], []
| "(" :: xs ->
let parsed, remaining = aux xs (depth + 1)
let r', r'' = aux remaining depth
Exprs(parsed) :: r', r''
| ")" :: xs ->
if depth <= 0 then
failwith ("balance your parentheses, too many `)`")
[], xs
| x :: xs ->
let parsed, remaining = aux xs depth
parseToken x :: parsed, remaining
let parsed, remaining = aux tokens 0
assert (remaining.IsEmpty)
parsed
(* allows for more than one top-level expression *)
let lastExpression =
"""
hello
()
(1)
(2 3)
(first (list 1 (+ 2 3) 9))
"""
|> tokenize
|> parse
|> Seq.last
let expected =
Exprs [ Symbol "first"
Exprs [ Symbol "list"
Int 1
Exprs [ Symbol "+"; Int 2; Int 3 ]
Int 9 ] ]
if lastExpression <> expected then
failwith
$"""
actual:
{lastExpression}"
expected:
{expected}
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment