https://github.com/akkartik/mu/blob/main/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 -> _/ebx: int {
 35   enable-keyboard-immediate-mode
 36   var look/esi: grapheme <- 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-decimal 0, n
 50     print-string 0, "\n"
 51     #
 52     loop
 53   }
 54   enable-keyboard-type-mode
 55   return 0
 56 }
 57 
 58 fn simplify -> _/eax: int, _/esi: grapheme {
 59   # prime the pump
 60   var look/esi: grapheme <- get-char
 61   # do it
 62   var result/eax: int <- copy 0
 63   result, look <- expression look
 64   return result, look
 65 }
 66 
 67 fn expression _look: grapheme -> _/eax: int, _/esi: grapheme {
 68   var look/esi: grapheme <- copy _look
 69   # read arg
 70   var result/eax: int <- copy 0
 71   result, look <- term look
 72   $expression:loop: {
 73     # while next non-space char in ['+', '-']
 74     look <- skip-spaces look
 75     {
 76       var continue?/eax: boolean <- is-add-or-sub? look
 77       compare continue?, 0  # false
 78       break-if-= $expression:loop
 79     }
 80     # read operator
 81     var op/ecx: grapheme <- copy 0
 82     op, look <- operator look
 83     # read next arg
 84     var second/edx: int <- copy 0
 85     look <- skip-spaces look
 86     {
 87       var tmp/eax: int <- copy 0
 88       tmp, look <- term look
 89       second <- copy tmp
 90     }
 91     # reduce
 92     $expression:perform-op: {
 93       {
 94         compare op, 0x2b  # '+'
 95         break-if-!=
 96         result <- add second
 97         break $expression:perform-op
 98       }
 99       {
100         compare op, 0x2d  # '-'
101         break-if-!=
102         result <- subtract second
103         break $expression:perform-op
104       }
105     }
106     loop
107   }
108   look <- skip-spaces look
109   return result, look
110 }
111 
112 fn term _look: grapheme -> _/eax: int, _/esi: grapheme {
113   var look/esi: grapheme <- copy _look
114   # read arg
115   look <- skip-spaces look
116   var result/eax: int <- copy 0
117   result, look <- factor look
118   $term:loop: {
119     # while next non-space char in ['*', '/']
120     look <- skip-spaces look
121     {
122       var continue?/eax: boolean <- is-mul-or-div? look
123       compare continue?, 0  # false
124       break-if-= $term:loop
125     }
126     # read operator
127     var op/ecx: grapheme <- copy 0
128     op, look <- operator look
129     # read next arg
130     var second/edx: int <- copy 0
131     look <- skip-spaces look
132     {
133       var tmp/eax: int <- copy 0
134       tmp, look <- factor look
135       second <- copy tmp
136     }
137     # reduce
138     $term:perform-op: {
139       {
140         compare op, 0x2a  # '*'
141         break-if-!=
142         result <- multiply second
143         break $term:perform-op
144       }
145 #?       {
146 #?         compare op, 0x2f  # '/'
147 #?         break-if-!=
148 #?         result <- divide second  # not in Mu yet
149 #?         break $term:perform-op
150 #?       }
151     }
152     loop
153   }
154   return result, look
155 }
156 
157 fn factor _look: grapheme -> _/eax: int, _/esi: grapheme {
158   var look/esi: grapheme <- copy _look  # should be a no-op
159   look <- skip-spaces look
160   # if next char is not '(', parse a number
161   compare look, 0x28  # '('
162   {
163     break-if-=
164     var result/eax: int <- copy 0
165     result, look <- num look
166     return result, look
167   }
168   # otherwise recurse
169   look <- get-char  # '('
170   var result/eax: int <- copy 0
171   result, look <- expression look
172   look <- skip-spaces look
173   look <- get-char  # ')'
174   return result, look
175 }
176 
177 fn is-mul-or-div? c: grapheme -> _/eax: boolean {
178   compare c, 0x2a  # '*'
179   {
180     break-if-!=
181     return 1  # true
182   }
183   compare c, 0x2f  # '/'
184   {
185     break-if-!=
186     return 1  # true
187   }
188   return 0  # false
189 }
190 
191 fn is-add-or-sub? c: grapheme -> _/eax: boolean {
192   compare c, 0x2b  # '+'
193   {
194     break-if-!=
195     return 1  # true
196   }
197   compare c, 0x2d  # '-'
198   {
199     break-if-!=
200     return 1  # true
201   }
202   return 0  # false
203 }
204 
205 fn operator _look: grapheme -> _/ecx: grapheme, _/esi: grapheme {
206   var op/ecx: grapheme <- copy _look
207   var look/esi: grapheme <- get-char
208   return op, look
209 }
210 
211 fn num _look: grapheme -> _/eax: int, _/esi: grapheme {
212   var look/esi: grapheme <- copy _look
213   var result/edi: int <- copy 0
214   {
215     var first-digit/eax: int <- to-decimal-digit look
216     result <- copy first-digit
217   }
218   {
219     look <- get-char
220     # done?
221     var digit?/eax: boolean <- is-decimal-digit? look
222     compare digit?, 0  # false
223     break-if-=
224     # result *= 10
225     {
226       var ten/eax: int <- copy 0xa
227       result <- multiply ten
228     }
229     # result += digit(look)
230     var digit/eax: int <- to-decimal-digit look
231     result <- add digit
232     loop
233   }
234   return result, look
235 }
236 
237 fn skip-spaces _look: grapheme -> _/esi: grapheme {
238   var look/esi: grapheme <- copy _look  # should be a no-op
239   {
240     compare look, 0x20
241     break-if-!=
242     look <- get-char
243     loop
244   }
245   return look
246 }
247 
248 fn get-char -> _/esi: grapheme {
249   var look/eax: grapheme <- read-key-from-real-keyboard
250   print-grapheme-to-real-screen look
251   compare look, 4
252   {
253     break-if-!=
254     print-string 0, "^D\n"
255     syscall_exit
256   }
257   return look
258 }