⛏️ index : haiku.git

# Rules providing basic arithmetic operations

HAIKU_ZERO = + 0 ;
HAIKU_ONE = + 1 ;

HAIKU_PAD_9 = 0 1 2 3 4 5 6 7 8 ;

# a > b <==> $(HAIKU_DIGIT_GREATER_$(a)[1$(b)])
HAIKU_DIGIT_GREATER_0 = $(HAIKU_PAD_9) ;
HAIKU_DIGIT_GREATER_1 = $(HAIKU_PAD_9) 1 ;
HAIKU_DIGIT_GREATER_2 = $(HAIKU_PAD_9) 1 1 ;
HAIKU_DIGIT_GREATER_3 = $(HAIKU_PAD_9) 1 1 1 ;
HAIKU_DIGIT_GREATER_4 = $(HAIKU_PAD_9) 1 1 1 1 ;
HAIKU_DIGIT_GREATER_5 = $(HAIKU_PAD_9) 1 1 1 1 1 ;
HAIKU_DIGIT_GREATER_6 = $(HAIKU_PAD_9) 1 1 1 1 1 1 ;
HAIKU_DIGIT_GREATER_7 = $(HAIKU_PAD_9) 1 1 1 1 1 1 1 ;
HAIKU_DIGIT_GREATER_8 = $(HAIKU_PAD_9) 1 1 1 1 1 1 1 1 ;
HAIKU_DIGIT_GREATER_9 = $(HAIKU_PAD_9) 1 1 1 1 1 1 1 1 1 ;

# a + b == $(HAIKU_DIGIT_ADD_$(a)[1$(b)]) (carry ignored)
HAIKU_DIGIT_ADD_0 = $(HAIKU_PAD_9) 0 1 2 3 4 5 6 7 8 9 ;
HAIKU_DIGIT_ADD_1 = $(HAIKU_PAD_9) 1 2 3 4 5 6 7 8 9 0 ;
HAIKU_DIGIT_ADD_2 = $(HAIKU_PAD_9) 2 3 4 5 6 7 8 9 0 1 ;
HAIKU_DIGIT_ADD_3 = $(HAIKU_PAD_9) 3 4 5 6 7 8 9 0 1 2 ;
HAIKU_DIGIT_ADD_4 = $(HAIKU_PAD_9) 4 5 6 7 8 9 0 1 2 3 ;
HAIKU_DIGIT_ADD_5 = $(HAIKU_PAD_9) 5 6 7 8 9 0 1 2 3 4 ;
HAIKU_DIGIT_ADD_6 = $(HAIKU_PAD_9) 6 7 8 9 0 1 2 3 4 5 ;
HAIKU_DIGIT_ADD_7 = $(HAIKU_PAD_9) 7 8 9 0 1 2 3 4 5 6 ;
HAIKU_DIGIT_ADD_8 = $(HAIKU_PAD_9) 8 9 0 1 2 3 4 5 6 7 ;
HAIKU_DIGIT_ADD_9 = $(HAIKU_PAD_9) 9 0 1 2 3 4 5 6 7 8 ;

# a - b == $(HAIKU_DIGIT_SUB_$(a)[1$(b)]) (carry ignored)
HAIKU_DIGIT_SUB_0 = $(HAIKU_PAD_9) 0 9 8 7 6 5 4 3 2 1 ;
HAIKU_DIGIT_SUB_1 = $(HAIKU_PAD_9) 1 0 9 8 7 6 5 4 3 2 ;
HAIKU_DIGIT_SUB_2 = $(HAIKU_PAD_9) 2 1 0 9 8 7 6 5 4 3 ;
HAIKU_DIGIT_SUB_3 = $(HAIKU_PAD_9) 3 2 1 0 9 8 7 6 5 4 ;
HAIKU_DIGIT_SUB_4 = $(HAIKU_PAD_9) 4 3 2 1 0 9 8 7 6 5 ;
HAIKU_DIGIT_SUB_5 = $(HAIKU_PAD_9) 5 4 3 2 1 0 9 8 7 6 ;
HAIKU_DIGIT_SUB_6 = $(HAIKU_PAD_9) 6 5 4 3 2 1 0 9 8 7 ;
HAIKU_DIGIT_SUB_7 = $(HAIKU_PAD_9) 7 6 5 4 3 2 1 0 9 8 ;
HAIKU_DIGIT_SUB_8 = $(HAIKU_PAD_9) 8 7 6 5 4 3 2 1 0 9 ;
HAIKU_DIGIT_SUB_9 = $(HAIKU_PAD_9) 9 8 7 6 5 4 3 2 1 0 ;

# a * b == $(HAIKU_DIGIT_MULT_CARRY_$(a)[1$(b)]) $(HAIKU_DIGIT_MULT_$(a)[1$(b)])
HAIKU_DIGIT_MULT_0 = $(HAIKU_PAD_9) 0 0 0 0 0 0 0 0 0 0 ;
HAIKU_DIGIT_MULT_1 = $(HAIKU_PAD_9) 0 1 2 3 4 5 6 7 8 9 ;
HAIKU_DIGIT_MULT_2 = $(HAIKU_PAD_9) 0 2 4 6 8 0 2 4 6 8 ;
HAIKU_DIGIT_MULT_3 = $(HAIKU_PAD_9) 0 3 6 9 2 5 8 1 4 7 ;
HAIKU_DIGIT_MULT_4 = $(HAIKU_PAD_9) 0 4 8 2 6 0 4 8 2 6 ;
HAIKU_DIGIT_MULT_5 = $(HAIKU_PAD_9) 0 5 0 5 0 5 0 5 0 5 ;
HAIKU_DIGIT_MULT_6 = $(HAIKU_PAD_9) 0 6 2 8 4 0 6 2 8 4 ;
HAIKU_DIGIT_MULT_7 = $(HAIKU_PAD_9) 0 7 4 1 8 5 2 9 6 3 ;
HAIKU_DIGIT_MULT_8 = $(HAIKU_PAD_9) 0 8 6 4 2 0 8 6 4 2 ;
HAIKU_DIGIT_MULT_9 = $(HAIKU_PAD_9) 0 9 8 7 6 5 4 3 2 1 ;

HAIKU_DIGIT_MULT_CARRY_0 = $(HAIKU_PAD_9) 0 0 0 0 0 0 0 0 0 0 ;
HAIKU_DIGIT_MULT_CARRY_1 = $(HAIKU_PAD_9) 0 0 0 0 0 0 0 0 0 0 ;
HAIKU_DIGIT_MULT_CARRY_2 = $(HAIKU_PAD_9) 0 0 0 0 0 1 1 1 1 1 ;
HAIKU_DIGIT_MULT_CARRY_3 = $(HAIKU_PAD_9) 0 0 0 0 1 1 1 2 2 2 ;
HAIKU_DIGIT_MULT_CARRY_4 = $(HAIKU_PAD_9) 0 0 0 1 1 2 2 2 3 3 ;
HAIKU_DIGIT_MULT_CARRY_5 = $(HAIKU_PAD_9) 0 0 1 1 2 2 3 3 4 4 ;
HAIKU_DIGIT_MULT_CARRY_6 = $(HAIKU_PAD_9) 0 0 1 1 2 3 3 4 4 5 ;
HAIKU_DIGIT_MULT_CARRY_7 = $(HAIKU_PAD_9) 0 0 1 2 2 3 4 4 5 6 ;
HAIKU_DIGIT_MULT_CARRY_8 = $(HAIKU_PAD_9) 0 0 1 2 3 4 4 5 6 7 ;
HAIKU_DIGIT_MULT_CARRY_9 = $(HAIKU_PAD_9) 0 0 1 2 3 4 5 6 7 8 ;


# pragma mark - Number Operations


rule NormalizeNum number
{
	# NormalizeNum <number> ;

	local sign ;
	if $(number[1]) = "-" || $(number[1] = "+" {
		sign = $(number[1]) ;
		number = $(number[2-]) ;
	} else {
		sign = "+" ;
	}

	# cut off leading zeros
	local number = [ FReverse $(number) ] ;
	while $(number[1]) = 0 {
		number = $(number[2-]) ;
	}
	number = [ FReverse $(number) ] ;

	# zero is respresented as + 0
	if ! $(number) {
		number = 0 ;
		sign = "+" ;
	}

	return $(sign) $(number) ;
}

rule Num string : dontExit
{
	# Num <number string> [ : <dontExit> ] ;

	# split the string into three parts: sign, digits, and remainder
	local temp = [ Match ([^0-9]*)([0-9]+)(.*) : $(string) ] ;

	# there must be digits, but there must not be a remainder
	if ! $(temp[2]) || $(temp[3]) {
		if $(dontExit) {
			return ;
		}
		Exit "Not a number" ;
	}

	# get the sign
	local number ;
	if ! $(temp[1]) {
		number = "+" ;
	} else if $(temp[1]) = "+" || $(temp[1]) = "-" {
		number = $(temp[1]) ;
	} else {
		if $(dontExit) {
			return ;
		}
		Exit "Not a number (sign)" ;
	}

	# split the digits
	temp = $(temp[2]) ;
	while $(temp[1]) {
		# split off the last digit
		temp = [ Match (.*)([0-9]) : $(temp[1]) ] ;
		number += $(temp[2]) ;
	}

	# normalize
	return [ NormalizeNum $(number) ] ;
}

rule Num2String number
{
	# Num2String <number> ;

	local sign = $(number[1]) ;
	if $(sign) = "+" {
		sign = "" ;
	}

	number = [ FReverse $(number[2-]) ] ;

	return $(sign)$(number:J=) ;
}

rule NumGreaterAbs a : b
{
	# NumGreaterAbs <a> : <b> ;

	# we compare from least to most significant digit
	local cmp = 0 ;
	while $(a) && $(b) {
		# chop off the first digit
		local da = $(a[1]:E=0) ;
		local db = $(b[1]:E=0) ;
		a = $(a[2-]) ;
		b = $(b[2-]) ;

		if $(da) != $(db) {
			if $(HAIKU_DIGIT_GREATER_$(da)[1$(db)]) {
				cmp = 1 ;
			} else {
				cmp = -1 ;
			}
		}
	}

	# a is greater, if b is not longer and a is either longer or equally long
	# and greater according to the digits comparison
	if ! $(b) && ( $(a) || $(cmp) = 1 ) {
		return 1 ;
	}
	return ;
}

rule AddNumAbs a : b
{
	# AddNum <a> : <b> ;

	local result ;
	local carry ;
	while $(a) || $(b) || $(carry) {
		# chop off the first digit
		local da = $(a[1]:E=0) ;
		local db = $(b[1]:E=0) ;
		a = $(a[2-]) ;
		b = $(b[2-]) ;

		# add carry to the first digit
		if $(carry) {
			local daa = $(HAIKU_DIGIT_ADD_1[1$(da)]) ;
			carry = $(HAIKU_DIGIT_GREATER_$(da)[1$(daa)]) ;
			da = $(daa) ;
		}

		# add digits
		local dr = $(HAIKU_DIGIT_ADD_$(da)[1$(db)]) ;
		if $(HAIKU_DIGIT_GREATER_$(da)[1$(dr)]) {
			carry = 1 ;
		}

		result += $(dr) ;
	}

	return $(result) ;
}

rule SubNumAbs a : b
{
	# SubNum <a> : <b> ;

	local result ;
	local carry ;
	while $(a) && ( $(b) || $(carry) ) {
		# chop off the first digit
		local da = $(a[1]:E=0) ;
		local db = $(b[1]:E=0) ;
		a = $(a[2-]) ;
		b = $(b[2-]) ;

		# sub carry from the first digit
		if $(carry) {
			local daa = $(HAIKU_DIGIT_SUB_$(da)[11]) ;
			carry = $(HAIKU_DIGIT_GREATER_$(daa)[1$(da)]) ;
			da = $(daa) ;
		}

		# sub digits
		local dr = $(HAIKU_DIGIT_SUB_$(da)[1$(db)]) ;
		if $(HAIKU_DIGIT_GREATER_$(dr)[1$(da)]) {
			carry = 1 ;
		}

		result += $(dr) ;
	}

	if $(b) || $(carry) {
		Exit "Error: SubNumAbs: Can't subtract greater from smaller number." ;
	}

	return $(result) ;
}

rule NegNum a
{
	# NegNum <a> ;

	if $(a[1]) = "+" {
		if $(a) = $(HAIKU_ZERO) {
			return $(a) ;
		}
		return "-" $(a[2-]) ;
	} else {
		return "+" $(a[2-]) ;
	}
}

rule AddNum a : b
{
	# AddNum <a> : <b> ;

	local signa = $(a[1]) ;
	local signb = $(b[1]) ;
	a = $(a[2-]) ;
	b = $(b[2-]) ;

	if $(signa) = $(signb) {
		return $(signa) [ AddNumAbs $(a) : $(b) ] ;
	} else {
		local result ;
		if [ NumGreaterAbs $(a) : $(b) ] {
			result = $(signa) [ SubNumAbs $(a) : $(b) ] ;
		} else {
			result = $(signb) [ SubNumAbs $(b) : $(a) ] ;
		}
		return [ NormalizeNum $(result) ] ;
	}
}

rule SubNum a : b
{
	# SubNum <a> : <b> ;
	return [ AddNum $(a) : [ NegNum $(b) ] ] ;
}

rule MultNumAbsDigit a : digit
{
	# MultNumAbsDigit <a> : <digit> ;

	local digitMultiples = $(HAIKU_DIGIT_MULT_$(digit)) ;
	local digitCarries = $(HAIKU_DIGIT_MULT_CARRY_$(digit)) ;

	local result ;
	local carry = 0 ;
	while $(a) || $(carry) != 0 {
		# chop off the first digit
		local da = $(a[1]:E=0) ;
		a = $(a[2-]) ;

		local dr = $(digitMultiples[1$(da)]) ;

		# add carry to the resulting digit
		if $(carry) {
			local dra = $(HAIKU_DIGIT_ADD_$(dr)[1$(carry)]) ;
			carry = $(HAIKU_DIGIT_GREATER_$(dr)[1$(dra)]:E=0) ;
			dr = $(dra) ;
		}

		# new carry
		carry = $(HAIKU_DIGIT_ADD_$(carry:E=0)[1$(digitCarries[1$(da)])]) ;

		result += $(dr) ;
	}

	return $(result) ;
}

rule MultNum a : b
{
	# MultNum <a> : <b> ;

	# If one of the factors is 0, we save us computation and normalization.
	if $(a) = $(HAIKU_ZERO) || $(b) = $(HAIKU_ZERO) {
		return $(HAIKU_ZERO) ;
	}

	# get the sign for the result
	local sign = "+" ;
	if $(a[1]) != $(b[1]) {
		sign = "-" ;
	}

	a = $(a[2-]) ;
	b = $(b[2-]) ;

	# multiply the individual digits of b with a and add up the result
	local result = 0 ;
	local prefix ;
	while $(b) {
		local db = $(b[1]) ;
		b = $(b[2-]) ;

		local adb = $(prefix) [ MultNumAbsDigit $(a) : $(db) ] ;
		result = [ AddNumAbs $(result) : $(adb) ] ;

		prefix += 0 ;
	}

	return $(sign) $(result) ;
}

rule NumGreater a : b
{
	local signa = $(a[1]) ;
	local signb = $(b[1]) ;
	a = $(a[2-]) ;
	b = $(b[2-]) ;

	if $(signa) = $(signb) {
		if $(signa) = "+" {
			return [ NumGreaterAbs $(a) : $(b) ] ;
		} else {
			return [ NumGreaterAbs $(b) : $(a) ] ;
		}
	} else {
		if $(signa) = "+" {
			return 1 ;
		} else {
			return ;
		}
	}
}


# pragma mark - Number String Operations


rule Inc a
{
	# Inc <number string a> ;
	return [ Num2String [ AddNum [ Num $(a) ] : + 1 ] ] ;
}

rule Dec a
{
	# Dec <number string a> ;
	return [ Num2String [ AddNum [ Num $(a) ] : - 1 ] ] ;
}

rule Neg a
{
	# Neg <number string a> ;
	return [ Num2String [ NegNum [ Num $(a) ] ] ] ;
}

rule Add a : b
{
	# Add <number string a> : <number string b> ;
	return [ Num2String [ AddNum [ Num $(a) ] : [ Num $(b) ] ] ] ;
}

rule Sub a : b
{
	# Sub <number string a> : <number string b> ;
	return [ Num2String [ SubNum [ Num $(a) ] : [ Num $(b) ] ] ] ;
}

rule Mult a : b
{
	# Mult <number string a> : <number string b> ;
	return [ Num2String [ MultNum [ Num $(a) ] : [ Num $(b) ] ] ] ;
}

rule Equal a : b
{
	# Equal <number string a> : <number string b> ;
	if [ Num $(a) ] = [ Num $(b) ] {
		return 1 ;
	}

	return ;
}

rule Less a : b
{
	# Less <number string a> : <number string b> ;
	return [ NumGreater [ Num $(b) ] : [ Num $(a) ] ] ;
}

rule Greater a : b
{
	# Greater <number string a> : <number string b> ;
	return [ NumGreater [ Num $(a) ] : [ Num $(b) ] ] ;
}

rule LessOrEqual a : b
{
	# LessOrEqual <number string a> : <number string b> ;
	if [ NumGreater [ Num $(a) ] : [ Num $(b) ] ] {
		return ;
	}
	return 1 ;
}

rule GreaterOrEqual a : b
{
	# GreaterOrEqual <number string a> : <number string b> ;
	if [ NumGreater [ Num $(b) ] : [ Num $(a) ] ] {
		return ;
	}
	return 1 ;
}


# pragma mark - Expression Parser


rule ParseAtom expression
{
	# ParseAtom <expression> ;

	# expression: '(' expression ')' | number

	if $(expression[1]) = "(" {
		local result = [ ParseExpression $(expression[2-]) ] ;
		if $(result[2]) != ")" {
			Exit "ParseAtom: Parse error: Expected \")\"." ;
		}
		return $(result[1]) $(result[3-]) ;
	} else {
		if ! $(expression) {
			Exit "ParseAtom: Parse error: Unexpected end of expression." ;
		}

		local num = [ Num $(expression[1]) : 1 ] ;
		if ! $(num) {
			Exit "ParseAtom: Parse error: Expected number instead of:"
				$(expression[1]) ;
		}

		return [ Num2String $(num) ] $(expression[2-]) ;
	}
}

rule ParseUnary expression
{
	# ParseUnary <expression> ;

	# expression: ('+'/'-')* atom

	if ! $(expression) {
		Exit "ParseUnary: Parse error: Unexpected end of expression." ;
	}

	# eat all unary "+" and "-" operations
	local neg ;
	while $(expression[1]) = "+" || $(expression[1]) = "-" {
		if $(expression[1]) = "-" {
			if $(neg) {
				neg = ;
			} else {
				neg = 1 ;
			}
		}

		expression = $(expression[2-]) ;
	}

	local result = [ ParseAtom $(expression) ] ;

	if $(neg) {
		return [ Neg $(result[1]) ] $(result[2-]) ;
	}
	return $(result) ;
}

rule ParseTerm expression
{
	# ParseTerm <expression> ;

	# expression: unary ('*' unary)*

	local result = [ ParseUnary $(expression) ] ;
	local product = $(result[1]) ;
	expression = $(result[2-]) ;

	while $(expression[1]) = "*" {
		# get the operation
		local operation ;
		operation = Mult ;

		# parse the next operand
		result = [ ParseUnary $(expression[2-]) ] ;
		expression = $(result[2-]) ;

		# compute the product
		product = [ $(operation) $(product) : $(result[1]) ] ;
	}

	return $(product) $(expression) ;
}

rule ParseExpression expression
{
	# ParseExpression <expression> ;

	# expression: term ('+'/'-' term)*

	local result = [ ParseTerm $(expression) ] ;
	local sum = $(result[1]) ;
	expression = $(result[2-]) ;

	while $(expression[1]) = "+" || $(expression[1]) = "-" {
		# get the operation
		local operation ;
		if $(expression[1]) = "+" {
			operation = Add ;
		} else {
			operation = Sub ;
		}

		# parse the next operand
		result = [ ParseTerm $(expression[2-]) ] ;
		expression = $(result[2-]) ;

		# compute the sum
		sum = [ $(operation) $(sum) : $(result[1]) ] ;
	}

	return $(sum) $(expression) ;
}


rule Expr expression
{
	# Expr <expression> ;

	# tokenize the expression
	local tokens ;
	local string ;
	for string in $(expression) {
		while $(string) {
			local split = [ Match "[ \t]*(-|[()+*]|[0-9]*)(.*)" : $(string) ] ;
			if ! $(split[1]) {
				Exit "Expr: Syntax error: Invalid token: \"$(string)\"." ;
			}

			tokens += $(split[1]) ;
			string = $(split[2]) ;
		}
	}

	local result = [ ParseExpression $(tokens) ] ;
	if $(result[2-]) {
		Exit "Expr: Garbage at end of expression:" $(result[2-]) ;
	}

	return $(result[1]) ;
}