Skip to content

Instantly share code, notes, and snippets.

@osa1
Last active June 2, 2024 22:51
Show Gist options
  • Save osa1/3de1dd44a3656ebabead0de0d9e6f60c to your computer and use it in GitHub Desktop.
Save osa1/3de1dd44a3656ebabead0de0d9e6f60c to your computer and use it in GitHub Desktop.
Syntax question
-- An evaluator for the language "Untyped Arithmetic Expressions" described in
-- Types and Programming Langauges section 3.
-- Below I have a few different syntax for starting indentation blocks.
--
-- I'm looking for the syntax that is (in priority order)
--
-- (1) Most readable (subjective, I know)
-- (2) Easiest to implement tooling for (e.g. indentatation plugin in text
-- editors)
--
-- Error recorvery and incremental parsing is not a concern: in an indentation
-- sensitive language you can easily split files into top-level definitions
-- based on indentation. A syntax error in one top-level definition, no matter
-- how bad, can't affect other definitions. In the worst case you skip the
-- whole top-level definitions and parse the rest.
--
-- Not using any token for starting indentation blocks is a possibility, but I
-- think that would make tooling difficult.
--
-- Ideally I also want one token that starts all indentation blocks. E.g. we
-- can't have 'do', 'with', 'of', 'begin' etc. ideally there should be only one
-- that works for all kinds of blocks.
--
-- Option (1): without any tokens
--
-- I think this looks good, but implementing indentation support in editors may
-- be tricky?
--
type Term
True
False
Zero
If
cond: Term
then: Term
else_: Term
Succ(Term)
Pred(Term)
IsZero(Term)
type Value
Bool(Bool)
Num(Int)
fn eval(term: Term): Value
match term
Term.True
Value.Bool(Bool.True)
Term.False
Value.Bool(Bool.False)
Term.Zero
Value.Num(0)
Term.If(cond, then, else_)
let cond = match eval(cond)
Value.Bool(bool)
bool
Value.Num(num)
panic("If expression condition evaluated to number")
if cond
eval(then)
else
eval(else_)
Term.Succ(term)
match eval(term)
Value.Bool(_)
panic("Succ argument evaluated to bool")
Value.Num(value)
Value.Num(value + 1)
Term.Pred(term)
match eval(term)
Value.Bool(_)
panic("Pred argument evaluated to bool")
Value.Num(value)
Value.Num(value - 1)
Term.IsZero(term)
match eval(term)
Value.Bool(_)
panic("IsZero argument evaluated to bool")
Value.Num(value)
Value.Bool(value == 0)
--
-- Option (2): ':'.
--
-- This is the most familiar syntax (Python), but I feel like it overloads ':'
-- a bit too much. It's used for both starting an indentation and for type
-- annotations.
--
-- In the example below there are only two type annotations, but you can
-- imagine more complicated top-level functions with lots of arguments..
--
-- Also, with this syntax we probably want '=' for starting function bodies.
-- Example:
--
-- fn test() : <type> : <expr> -- confusing
-- fn test() : <type> = <expr> -- better
--
type Term:
True
False
Zero
If:
cond: Term
then: Term
else_: Term
Succ(Term)
Pred(Term)
IsZero(Term)
type Value:
Bool(Bool)
Num(Int)
fn eval(term: Term): Value =
match term:
Term.True:
Value.Bool(Bool.True)
Term.False:
Value.Bool(Bool.False)
Term.Zero:
Value.Num(0)
Term.If(cond, then, else_):
let cond = match eval(cond):
Value.Bool(bool):
bool
Value.Num(num):
panic("If expression condition evaluated to number")
if cond:
eval(then)
else:
eval(else_)
Term.Succ(term):
match eval(term):
Value.Bool(_):
panic("Succ argument evaluated to bool")
Value.Num(value):
Value.Num(value + 1)
Term.Pred(term):
match eval(term):
Value.Bool(_):
panic("Pred argument evaluated to bool")
Value.Num(value):
Value.Num(value - 1)
Term.IsZero(term):
match eval(term):
Value.Bool(_):
panic("IsZero argument evaluated to bool")
Value.Num(value):
Value.Bool(value == 0)
--
-- Option (3): '>'
--
-- I think this looks ugly..
--
type Term >
True
False
Zero
If >
cond: Term
then: Term
else_: Term
Succ(Term)
Pred(Term)
IsZero(Term)
type Value >
Bool(Bool)
Num(Int)
fn eval(term: Term): Value >
match term >
Term.True >
Value.Bool(Bool.True)
Term.False >
Value.Bool(Bool.False)
Term.Zero >
Value.Num(0)
Term.If(cond, then, else_) >
let cond = match eval(cond) >
Value.Bool(bool) >
bool
Value.Num(num) >
panic("If expression condition evaluated to number")
if cond >
eval(then)
else >
eval(else_)
Term.Succ(term) >
match eval(term) >
Value.Bool(_) >
panic("Succ argument evaluated to bool")
Value.Num(value) >
Value.Num(value + 1)
Term.Pred(term) >
match eval(term) >
Value.Bool(_) >
panic("Pred argument evaluated to bool")
Value.Num(value) >
Value.Num(value - 1)
Term.IsZero(term) >
match eval(term) >
Value.Bool(_) >
panic("IsZero argument evaluated to bool")
Value.Num(value) >
Value.Bool(value == 0)
--
-- Option (4): '::'
--
-- Somehow this looks better than (3) to me, but it's still noisy
--
type Term ::
True
False
Zero
If ::
cond: Term
then: Term
else_: Term
Succ(Term)
Pred(Term)
IsZero(Term)
type Value ::
Bool(Bool)
Num(Int)
fn eval(term: Term): Value ::
match term ::
Term.True ::
Value.Bool(Bool.True)
Term.False ::
Value.Bool(Bool.False)
Term.Zero ::
Value.Num(0)
Term.If(cond, then, else_) ::
let cond = match eval(cond) ::
Value.Bool(bool) ::
bool
Value.Num(num) ::
panic("If expression condition evaluated to number")
if cond ::
eval(then)
else ::
eval(else_)
Term.Succ(term) ::
match eval(term) ::
Value.Bool(_) ::
panic("Succ argument evaluated to bool")
Value.Num(value) ::
Value.Num(value + 1)
Term.Pred(term) ::
match eval(term) ::
Value.Bool(_) ::
panic("Pred argument evaluated to bool")
Value.Num(value) ::
Value.Num(value - 1)
Term.IsZero(term) ::
match eval(term) ::
Value.Bool(_) ::
panic("IsZero argument evaluated to bool")
Value.Num(value) ::
Value.Bool(value == 0)
--
-- Option (5): ???
--
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment