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