https://github.com/akkartik/mu/blob/master/apps/arith.mu
  1 # Integer arithmetic using conventional precedence.
  2 #
  3 # Follows part 2 of Jack Crenshaw's "Let's build a compiler!"
  4 #   https://compilers.iecc.com/crenshaw
  5 #
  6 # Limitations:
  7 #   Reads numbers in decimal, but prints numbers in hex :(
  8 #   No division yet.
  9 #
 10 # To build:
 11 #   $ ./translate_mu apps/arith.mu
 12 #
 13 # Example session:
 14 #   $ ./a.elf
 15 #   press ctrl-c or ctrl-d to exit
 16 #   > 1
 17 #   0x00000001
 18 #   > 1+1
 19 #   0x00000002
 20 #   > 1 + 1
 21 #   0x00000002
 22 #   > 1+2 +3
 23 #   0x00000006
 24 #   > 1+2 *3
 25 #   0x00000007
 26 #   > (1+2) *3
 27 #   0x00000009
 28 #   > 1 + 3*4
 29 #   0x0000000d
 30 #   > ^D
 31 #   $
 32 #
 33 # Error handling is non-existent. This is just a prototype.
 34 
 35 fn main -> exit-status/ebx: int {
 36   var look/esi: byte <- copy 0  # lookahead
 37   var n/eax: int <- copy 0  # result of each expression
 38   print-string 0, "press ctrl-c or ctrl-d to exit\n"
 39   # read-eval-print loop
 40   {
 41     # print prompt
 42     print-string 0, "> "
 43     # read and eval
 44     n, look <- simplify  # we explicitly thread 'look' everywhere
 45     # if (look == 0) break
 46     compare look, 0
 47     break-if-=
 48     # print
 49     print-int32-hex 0, n
 50     print-string 0, "\n"
 51     #
 52     loop
 53   }
 54   exit-status <- copy 0
 55 }
 56 
 57 fn simplify -> result/eax: int, look/esi: byte {
 58   # prime the pump
 59   look <- get-char
 60   # do it
 61   result, look <- expression look
 62 }
 63 
 64 fn expression _look: byte -> result/eax: int, look/esi: byte {
 65   look <- copy _look  # should be a no-op
 66   # read arg
 67   result, look <- term look
 68   $expression:loop: {
 69     # while next non-space char in ['+', '-']
 70     look <- skip-spaces look
 71     {
 72       var continue?/eax: boolean <- is-add-or-sub? look
 73       compare continue?, 0  # false
 74       break-if-= $expression:loop
 75     }
 76     # read operator
 77     var op/ecx: byte <- copy 0
 78     op, look <- operator look
 79     # read next arg
 80     var second/edx: int <- copy 0
 81     look <- skip-spaces look
 82     {
 83       var tmp/eax: int <- copy 0
 84       tmp, look <- term look
 85       second <- copy tmp
 86     }
 87     # reduce
 88     $expression:perform-op: {
 89       {
 90         compare op, 0x2b  # '+'
 91         break-if-!=
 92         result <- add second
 93         break $expression:perform-op
 94       }
 95       {
 96         compare op, 0x2d  # '-'
 97         break-if-!=
 98         result <- subtract second
 99         break $expression:perform-op
100       }
101     }
102     loop
103   }
104   look <- skip-spaces look
105 }
106 
107 fn term _look: byte -> result/eax: int, look/esi: byte {
108   look <- copy _look  # should be a no-op
109   # read arg
110   look <- skip-spaces look
111   result, look <- factor look
112   $term:loop: {
113     # while next non-space char in ['*', '/']
114     look <- skip-spaces look
115     {
116       var continue?/eax: boolean <- is-mul-or-div? look
117       compare continue?, 0  # false
118       break-if-= $term:loop
119     }
120     # read operator
121     var op/ecx: byte <- copy 0
122     op, look <- operator look
123     # read next arg
124     var second/edx: int <- copy 0
125     look <- skip-spaces look
126     {
127       var tmp/eax: int <- copy 0
128       tmp, look <- factor look
129       second <- copy tmp
130     }
131     # reduce
132     $term:perform-op: {
133       {
134         compare op, 0x2a  # '*'
135         break-if-!=
136         result <- multiply second
137         break $term:perform-op
138       }
139 #?       {
140 #?         compare op, 0x2f  # '/'
141 #?         break-if-!=
142 #?         result <- divide second  # not in Mu yet
143 #?         break $term:perform-op
144 #?       }
145     }
146     loop
147   }
148 }
149 
150 fn factor _look: byte -> result/eax: int, look/esi: byte {
151 $factor:body: {
152   look <- copy _look  # should be a no-op
153   look <- skip-spaces look
154   # if next char is not '(', parse a number
155   compare look, 0x28  # '('
156   {
157     break-if-=
158     result, look <- num look
159     break $factor:body
160   }
161   # otherwise recurse
162   look <- get-char  # '('
163   result, look <- expression look
164   look <- skip-spaces look
165   look <- get-char  # ')'
166 }  # $factor:body
167 }
168 
169 fn is-mul-or-div? c: byte -> result/eax: boolean {
170 $is-mul-or-div?:body: {
171   compare c, 0x2a  # '*'
172   {
173     break-if-!=
174     result <- copy 1  # true
175     break $is-mul-or-div?:body
176   }
177   compare c, 0x2f  # '/'
178   {
179     break-if-!=
180     result <- copy 1  # true
181     break $is-mul-or-div?:body
182   }
183   result <- copy 0  # false
184 }  # $is-mul-or-div?:body
185 }
186 
187 fn is-add-or-sub? c: byte -> result/eax: boolean {
188 $is-add-or-sub?:body: {
189   compare c, 0x2b  # '+'
190   {
191     break-if-!=
192     result <- copy 1  # true
193     break $is-add-or-sub?:body
194   }
195   compare c, 0x2d  # '-'
196   {
197     break-if-!=
198     result <- copy 1  # true
199     break $is-add-or-sub?:body
200   }
201   result <- copy 0  # false
202 }  # $is-add-or-sub?:body
203 }
204 
205 fn operator _look: byte -> op/ecx: byte, look/esi: byte {
206   op <- copy _look
207   look <- get-char
208 }
209 
210 fn num _look: byte -> result/eax: int, look/esi: byte {
211   look <- copy _look  # should be a no-op
212   var out/edi: int <- copy 0
213   {
214     var first-digit/eax: int <- to-decimal-digit look
215     out <- copy first-digit
216   }
217   {
218     look <- get-char
219     # done?
220     var digit?/eax: boolean <- is-decimal-digit? look
221     compare digit?, 0  # false
222     break-if-=
223     # out *= 10
224     {
225       var ten/eax: int <- copy 0xa
226       out <- multiply ten
227     }
228     # out += digit(look)
229     var digit/eax: int <- to-decimal-digit look
230     out <- add digit
231     loop
232   }
233   result <- copy out
234 }
235 
236 fn skip-spaces _look: byte -> look/esi: byte {
237   look <- copy _look  # should be a no-op
238   {
239     compare look, 0x20
240     break-if-!=
241     look <- get-char
242     loop
243   }
244 }
245 
246 fn get-char -> look/esi: byte {
247   var tmp/eax: byte <- read-key
248   look <- copy tmp
249   compare look, 0
250   {
251     break-if-!=
252     print-string 0, "^D\n"
253     syscall_exit
254   }
255 }