-
Notifications
You must be signed in to change notification settings - Fork 11
/
rpn.kl
135 lines (121 loc) · 3.87 KB
/
rpn.kl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#lang "prelude.kl"
(import (shift "prelude.kl" 1))
(import "define-syntax-rule.kl")
(import "list.kl")
-- An example custom #lang, inspired from [1].
--
-- [1] https://beautifulracket.com/stacker/
--
-- Programs written in this #lang look like this:
--
-- #lang "rpn.kl"
-- 4
-- 8
-- +
-- 3
-- *
--
-- This is a stack based language, so the above program pushes 4 and 8 onto the
-- stack, then + pops them and pushes 12 in their place, then 3 is pushed on
-- top, then * pops 12 and 3 and pushes 36. The overall program prints 36. If
-- there was more than one number on the stack at the end of the program, they
-- would all be printed on a separate line, from the bottom to the top of the
-- stack.
--
-- In order to do so, we take advantage of the fact that the above program gets
-- desugared to the following macro call:
--
-- (#%module 4 8 + 3 *)
--
-- We define a custom macro named #%module (well, we define a custom macro
-- named my-module, which we rename to #%module in the export declaration at
-- the end of this file) which rewrites a module in the rpn #lang to a module
-- in the prelude #lang:
--
-- (prelude.#%module
-- (prelude.run
-- (print-stack
-- (* (3 (+ (8 (4 empty-stack))))))))
(define empty-stack
(nil))
(defun putStrLn (str)
(write stdout (string-append str "\n")))
(defun putIntLn (int)
(putStrLn (integer->string int)))
(defun print-stack (stack)
(case stack
[(nil)
(pure-IO (unit))]
[(:: x xs)
(bind-IO
(print-stack xs)
(lambda (_)
(putIntLn x)))]))
(define-macros
([my-module
(lambda (stx)
-- (go (list '3 '*)
-- '(+ (8 (4 empty-stack))))
-- =>
-- (print-stack
-- (* (3 (+ (8 (4 empty-stack))))))
(flet [go (inputs compute-stack)
(case inputs
[(nil)
(pure `(#%module
(run
(print-stack ,compute-stack))))]
[(:: x xs)
(go xs `(,x ,compute-stack))])]
(case (open-syntax stx)
-- drop the first argument, which is always #%module
[(list-contents (:: _ xs))
(go xs 'empty-stack)])))]))
-- In the expression
--
-- (print-stack
-- (* (3 (+ (8 (4 empty-stack))))))
--
-- the idea is that *, +, and the numbers are intended to be functions which
-- take the stack as input and return the modified stack.
(defun add (stk)
(case stk
[(:: x1 (:: x2 xs))
(:: (+ x1 x2) xs)]))
(defun mul (stk)
(case stk
[(:: x1 (:: x2 xs))
(:: (* x1 x2) xs)]))
-- When a number is evaluated, either as a function or as an argument, the
-- #%integer-literal macro is automatically called. Thus,
--
-- (print-stack
-- (* (3 (+ (8 (4 empty-stack))))))
--
-- is automatically expanded to
--
-- (print-stack
-- (* ((#%integer-literal 3)
-- (+ ((#%integer-literal 8)
-- ((#%integer-literal 4)
-- empty-stack))))))
--
-- We can thus define a macro named #%integer-literal which pushes the number
-- onto the stack. We need to delegate to the prelude's #%integer-literal,
-- otherwise including an integer literal in the output will cause
-- my-integer-literal to be called again.
(define-macro (my-integer-literal n)
(pure `(lambda (stk)
(:: (#%integer-literal ,n) stk))))
-- Similarly, "foo" expands to (#%string-literal "foo"), and (f x y) expands to
-- (#%app f x y) when f is _not_ a macro. Our language does not use either
-- syntax, so we do not export macros with those names, and this has the effect
-- of causing an error if a user attempts to use those syntactic forms.
(export (rename ([my-module #%module]
[add +]
[mul *]
[my-integer-literal #%integer-literal])
my-module
add
mul
my-integer-literal))