Welcome to the Tsonnet series!
In the previous post, we added arithmetic operations:
Tsonnet #3 - Supporting arithmetic operators
Hercules Lemke Merscher ・ Feb 6
The interpret_bin_op function was a bit ugly, plus we had a catch-all case on the pattern matching raising an exception for non-numeric expr.
Let's get going and wrap numerical types into a Number type -- just like JSON.
diff --git a/lib/ast.ml b/lib/ast.ml
index 326c6db..55ddd52 100644
--- a/lib/ast.ml
+++ b/lib/ast.ml
@@ -4,9 +4,12 @@ type bin_op =
| Multiply
| Divide
-type expr =
+type number =
| Int of int
| Float of float
+
+type expr =
+ | Number of number
| Null
| Bool of bool
| String of string
The lexer does not need to change. The parser only needs to wrap INT and FLOAT in a Number:
diff --git a/lib/parser.mly b/lib/parser.mly
index 7d6ea28..2b6db25 100644
--- a/lib/parser.mly
+++ b/lib/parser.mly
@@ -27,8 +27,8 @@ prog:
;
expr:
- | i = INT { Int i }
- | f = FLOAT { Float f }
+ | i = INT { Number (Int i) }
+ | f = FLOAT { Number (Float f) }
| NULL { Null }
| b = BOOL { Bool b }
| s = STRING { String s }
The print function needs to match the Number too -- it is getting annoying, but let's roll with it for now and sort it out next.
The interpret_bin_op is where the bulk of the changes are:
diff --git a/lib/tsonnet.ml b/lib/tsonnet.ml
index 69858a3..832b56c 100644
--- a/lib/tsonnet.ml
+++ b/lib/tsonnet.ml
@@ -7,8 +7,10 @@ let parse (s: string) : expr =
ast
let rec print = function
- | Int i -> Printf.sprintf "%d" i
- | Float f -> Printf.sprintf "%f" f
+ | Number n ->
+ (match n with
+ | Int i -> Printf.sprintf "%d" i
+ | Float f -> Printf.sprintf "%f" f)
| Null -> Printf.sprintf "null"
| Bool b -> Printf.sprintf "%b" b
| String s -> Printf.sprintf "\"%s\"" s
@@ -22,27 +24,33 @@ let rec print = function
)
| _ -> failwith "not implemented"
-let interpret_bin_op op n1 n2 =
- let float_op =
- match op with
- | Add -> (+.)
- | Subtract -> (-.)
- | Multiply -> ( *. )
- | Divide -> (/.)
- in match (n1, n2) with
- | Int i1, Int i2 -> Float (float_op (Float.of_int i1) (Float.of_int i2))
- | Float f1, Float f2 -> Float (float_op f1 f2)
- | Float f1, Int e2 -> Float (float_op f1 (Float.of_int e2))
- | Int e1, Float f2 -> Float (float_op (Float.of_int e1) f2)
- | _ -> failwith "invalid operation"
+let interpret_bin_op (op: bin_op) (n1: number) (n2: number) : expr =
+ match op, n1, n2 with
+ | Add, (Int a), (Int b) -> Number (Int (a + b))
+ | Add, (Float a), (Int b) -> Number (Float (a +. (float_of_int b)))
+ | Add, (Int a), (Float b) -> Number (Float ((float_of_int a) +. b))
+ | Add, (Float a), (Float b) -> Number (Float (a +. b))
+ | Subtract, (Int a), (Int b) -> Number (Int (a - b))
+ | Subtract, (Float a), (Int b) -> Number (Float (a -. (float_of_int b)))
+ | Subtract, (Int a), (Float b) -> Number (Float ((float_of_int a) -. b))
+ | Subtract, (Float a), (Float b) -> Number (Float (a -. b))
+ | Multiply, (Int a), (Int b) -> Number (Int (a * b))
+ | Multiply, (Float a), (Int b) -> Number (Float (a *. (float_of_int b)))
+ | Multiply, (Int a), (Float b) -> Number (Float ((float_of_int a) *. b))
+ | Multiply, (Float a), (Float b) -> Number (Float (a *. b))
+ | Divide, (Int a), (Int b) -> Number (Float ((float_of_int a) /. (float_of_int b)))
+ | Divide, (Float a), (Int b) -> Number (Float (a /. (float_of_int b)))
+ | Divide, (Int a), (Float b) -> Number (Float ((float_of_int a) /. b))
+ | Divide, (Float a), (Float b) -> Number (Float (a /. b))
-(** [interpret expr] interprets the intermediate AST [expr] into an AST. *)
+(** [interpret expr] interprets and reduce the intermediate AST [expr] into a result AST. *)
let rec interpret (e: expr) : expr =
match e with
- | Null | Bool _ | String _ | Int _ | Float _ | Array _ | Object _ -> e
+ | Null | Bool _ | String _ | Number _ | Array _ | Object _ -> e
| BinOp (op, e1, e2) ->
- let n1, n2 = interpret e1, interpret e2
- in interpret_bin_op op n1 n2
+ match (interpret e1, interpret e2) with
+ | (Number v1), (Number v2) -> interpret_bin_op op v1 v2
+ | _ -> failwith "invalid binary operation"
let run (s: string) : expr =
let ast = parse s in
Now, I know what you're thinking -- "That's a lot of pattern matching!" and yeah, it's not the prettiest code I've ever written. But here's why it's actually pretty cool:
- We've isolated arithmetic operations into interpret_bin_op and got rid of the exception handling for non-numerical values
- The pattern matching makes the code explicit (even if verbose)
- Most importantly, we're following the single-responsibility principle -- our numeric operations are now completely separate from other binary operations
This refactoring might seem like a lot of work for little gain, but trust me, it'll pay off when we start adding other binary operations like string concatenation. We can now pattern-match BinOp
without touching our numeric operation logic at all. Clean separation of concerns!
In the next post, we'll tackle improving our JSON output presentation. But for now, pour yourself a drink and admire those clean-type boundaries we just created!
Thanks for reading Bit Maybe Wise! Subscribe to receive new posts about Tsonnet.
Top comments (0)