mu/linux/apps/arith.mu

259 lines
5.6 KiB
Forth

# Integer arithmetic using conventional precedence.
#
# Follows part 2 of Jack Crenshaw's "Let's build a compiler!"
# https://compilers.iecc.com/crenshaw
#
# Limitations:
# No division yet.
#
# To build:
# $ ./translate apps/arith.mu
#
# Example session:
# $ ./a.elf
# press ctrl-c or ctrl-d to exit
# > 1
# 1
# > 1+1
# 2
# > 1 + 1
# 2
# > 1+2 +3
# 6
# > 1+2 *3
# 7
# > (1+2) *3
# 9
# > 1 + 3*4
# 13
# > ^D
# $
#
# Error handling is non-existent. This is just a prototype.
fn main -> _/ebx: int {
enable-keyboard-immediate-mode
var look/esi: code-point-utf8 <- copy 0 # lookahead
var n/eax: int <- copy 0 # result of each expression
print-string 0/screen, "press ctrl-c or ctrl-d to exit\n"
# read-eval-print loop
{
# print prompt
print-string 0/screen, "> "
# read and eval
n, look <- simplify # we explicitly thread 'look' everywhere
# if (look == 0) break
compare look, 0
break-if-=
# print
print-int32-decimal 0/screen, n
print-string 0/screen, "\n"
#
loop
}
enable-keyboard-type-mode
return 0
}
fn simplify -> _/eax: int, _/esi: code-point-utf8 {
# prime the pump
var look/esi: code-point-utf8 <- get-char
# do it
var result/eax: int <- copy 0
result, look <- expression look
return result, look
}
fn expression _look: code-point-utf8 -> _/eax: int, _/esi: code-point-utf8 {
var look/esi: code-point-utf8 <- copy _look
# read arg
var result/eax: int <- copy 0
result, look <- term look
$expression:loop: {
# while next non-space char in ['+', '-']
look <- skip-spaces look
{
var continue?/eax: boolean <- add-or-sub? look
compare continue?, 0/false
break-if-= $expression:loop
}
# read operator
var op/ecx: code-point-utf8 <- copy 0
op, look <- operator look
# read next arg
var second/edx: int <- copy 0
look <- skip-spaces look
{
var tmp/eax: int <- copy 0
tmp, look <- term look
second <- copy tmp
}
# reduce
$expression:perform-op: {
{
compare op, 0x2b/+
break-if-!=
result <- add second
break $expression:perform-op
}
{
compare op, 0x2d/minus
break-if-!=
result <- subtract second
break $expression:perform-op
}
}
loop
}
look <- skip-spaces look
return result, look
}
fn term _look: code-point-utf8 -> _/eax: int, _/esi: code-point-utf8 {
var look/esi: code-point-utf8 <- copy _look
# read arg
look <- skip-spaces look
var result/eax: int <- copy 0
result, look <- factor look
$term:loop: {
# while next non-space char in ['*', '/']
look <- skip-spaces look
{
var continue?/eax: boolean <- mul-or-div? look
compare continue?, 0/false
break-if-= $term:loop
}
# read operator
var op/ecx: code-point-utf8 <- copy 0
op, look <- operator look
# read next arg
var second/edx: int <- copy 0
look <- skip-spaces look
{
var tmp/eax: int <- copy 0
tmp, look <- factor look
second <- copy tmp
}
# reduce
$term:perform-op: {
{
compare op, 0x2a/*
break-if-!=
result <- multiply second
break $term:perform-op
}
#? {
#? compare op, 0x2f/slash
#? break-if-!=
#? result <- divide second # not in Mu yet
#? break $term:perform-op
#? }
}
loop
}
return result, look
}
fn factor _look: code-point-utf8 -> _/eax: int, _/esi: code-point-utf8 {
var look/esi: code-point-utf8 <- copy _look # should be a no-op
look <- skip-spaces look
# if next char is not '(', parse a number
compare look, 0x28/open-paren
{
break-if-=
var result/eax: int <- copy 0
result, look <- num look
return result, look
}
# otherwise recurse
look <- get-char # '('
var result/eax: int <- copy 0
result, look <- expression look
look <- skip-spaces look
look <- get-char # ')'
return result, look
}
fn mul-or-div? c: code-point-utf8 -> _/eax: boolean {
compare c, 0x2a/*
{
break-if-!=
return 1/true
}
compare c, 0x2f/slash
{
break-if-!=
return 1/true
}
return 0/false
}
fn add-or-sub? c: code-point-utf8 -> _/eax: boolean {
compare c, 0x2b/+
{
break-if-!=
return 1/true
}
compare c, 0x2d/minus
{
break-if-!=
return 1/true
}
return 0/false
}
fn operator _look: code-point-utf8 -> _/ecx: code-point-utf8, _/esi: code-point-utf8 {
var op/ecx: code-point-utf8 <- copy _look
var look/esi: code-point-utf8 <- get-char
return op, look
}
fn num _look: code-point-utf8 -> _/eax: int, _/esi: code-point-utf8 {
var look/esi: code-point-utf8 <- copy _look
var result/edi: int <- copy 0
{
var first-digit/eax: int <- to-decimal-digit look
result <- copy first-digit
}
{
look <- get-char
# done?
var digit?/eax: boolean <- decimal-digit? look
compare digit?, 0/false
break-if-=
# result *= 10
{
var ten/eax: int <- copy 0xa
result <- multiply ten
}
# result += digit(look)
var digit/eax: int <- to-decimal-digit look
result <- add digit
loop
}
return result, look
}
fn skip-spaces _look: code-point-utf8 -> _/esi: code-point-utf8 {
var look/esi: code-point-utf8 <- copy _look # should be a no-op
{
compare look, 0x20
break-if-!=
look <- get-char
loop
}
return look
}
fn get-char -> _/esi: code-point-utf8 {
var look/eax: code-point-utf8 <- read-key-from-real-keyboard
print-code-point-utf8-to-real-screen look
compare look, 4
{
break-if-!=
print-string 0/screen, "^D\n"
syscall_exit
}
return look
}