###
# Modern Albufeira Prolog Interpreter
#
# Warranty & Liability
# To the extent permitted by applicable law and unless explicitly
# otherwise agreed upon, XLOG Technologies AG makes no warranties
# regarding the provided information. XLOG Technologies AG assumes
# no liability that any problems might be solved with the information
# provided by XLOG Technologies AG.
#
# Rights & License
# All industrial property rights regarding the information - copyright
# and patent rights in particular - are the sole property of XLOG
# Technologies AG. If the company was not the originator of some
# excerpts, XLOG Technologies AG has at least obtained the right to
# reproduce, change and translate the information.
#
# Reproduction is restricted to the whole unaltered document. Reproduction
# of the information is only allowed for non-commercial uses. Selling,
# giving away or letting of the execution of the library is prohibited.
# The library can be distributed as part of your applications and libraries
# for execution provided this comment remains unchanged.
#
# Restrictions
# Only to be distributed with programs that add significant and primary
# functionality to the library. Not to be distributed with additional
# software intended to replace any components of the library.
#
# Trademarks
# Jekejeke is a registered trademark of XLOG Technologies AG.
##
from nova.store import (set, is_variable, Compound, deref,
stack_push, stack_pop, stack_peek, is_structure,
VAR_MASK_SERNO, variable_serno, Item)
import nova.machine as machine
from nova.machine import (cont, exec_build, int32,
exec_eval, exec_unify, Choice, more, is_compound,
union_find, union_add, is_integer, is_frozen,
make_error, unbind, union_undo, exec_deref, object_hash_code,
is_pending, unify, object_equals, is_special)
from nova.special import (check_integer, MAX_ARITY, make_check,
check_nil, check_atom, narrow_float, make_special,
make_arithmetic, norm_float)
import math
import sys
################################################################
# is/2, (-)/2, abs/2 and sign/2 #
################################################################
###
# X is Y: [ISO 8.6.1]
# The predicate succeeds in X with the evaluation of Y.
##
def test_eval(args):
try:
res = exec_eval(args[1])
return exec_unify(args[0], res)
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))
except ValueError:
raise make_error(Compound("evaluation_error", ["undefined"]))
###
# -(A, B): [ISO 9.1.7]
# The predicate succeeds in B with the negation of A.
# This implements NaN propagation IEEE 754-2019 §6.2.3.
##
def arit_neg(args):
alpha = exec_eval(args[0])
if is_integer(alpha):
return -alpha
else:
return 0 - alpha
###
# abs(A, B): [ISO 9.1.7]
# The predicate succeeds in B with the absolute value of A.
##
def arit_abs(args):
alpha = exec_eval(args[0])
if is_integer(alpha):
return abs(alpha)
else:
return math.fabs(narrow_float(alpha))
###
# sign(A, B): [ISO 9.1.4]
# The predicate succeeds in B with the sign of A.
##
def arit_sign(args):
alpha = exec_eval(args[0])
if is_integer(alpha):
if alpha < 0:
return -1
if alpha == 0:
return 0
return 1
else:
alpha = narrow_float(alpha)
if alpha < 0.0:
return -1.0
if alpha == 0.0:
return 0.0
return 1.0
################################################################
# (+)/3, (-)/3, (*)/3, (/)/3, (//)/3 and (rem)/3. #
################################################################
###
# +(A, B, C): [ISO 9.1.7]
# The predicate succeeds in C with the sum of A and B.
##
def arit_add(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
if is_integer(alpha) and is_integer(beta):
return alpha + beta
else:
return norm_float(narrow_float(alpha) + narrow_float(beta))
###
# -(A, B, C):
# The predicate succeeds in C with A subtracted by B.
##
def arit_sub(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
if is_integer(alpha) and is_integer(beta):
return alpha - beta
else:
return norm_float(narrow_float(alpha) - narrow_float(beta))
###
# *(A, B, C): [ISO 9.1.7]
# The predicate succeeds in C with the product of A and B.
##
def arit_mul(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
if is_integer(alpha) and is_integer(beta):
return alpha * beta
else:
return norm_float(narrow_float(alpha) * narrow_float(beta))
###
# /(A, B, C): [ISO 9.1.7]
# The predicate succeeds in C with A float divided by B.
##
def arit_quot(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
beta = narrow_float(beta)
if beta == 0.0:
raise make_error(Compound("evaluation_error", ["zero_divisor"]))
return norm_float(narrow_float(alpha) / beta)
###
# //(A, B, C): [ISO 9.1.7]
# The predicate succeeds in C with A truncate divided by B.
##
def arit_intquot(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
if beta == 0:
raise make_error(Compound("evaluation_error", ["zero_divisor"]))
res = abs(alpha) // abs(beta)
if (alpha < 0) != (beta < 0):
return -res
else:
return res
###
# rem(A, B, C): [ISO 9.1.7]
# The predicate succeeds in C with A remainder B.
##
def arit_rem(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
if beta == 0:
raise make_error(Compound("evaluation_error", ["zero_divisor"]))
res = abs(alpha) % abs(beta)
if alpha < 0:
return -res
else:
return res
################################################################
# float/2, ^/3, div/3 and mod/3 #
################################################################
###
# float(A, B): [ISO 9.17]
# The predicate succeeds in B with the approximated A.
##
def arit_float(args):
alpha = exec_eval(args[0])
return narrow_float(alpha)
###
# ^(A, B, C): [TC2 9.3.10]
# The predicate succeeds in C with A int power by B.
##
def arit_intpow(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
if is_integer(alpha) and is_integer(beta):
if beta < 0:
raise make_error(Compound("domain_error",
["not_less_than_zero", beta]))
return alpha ** beta
else:
return norm_float(math.pow(narrow_float(alpha), narrow_float(beta)))
###
# div(A, B, C): [TC2 9.1.3]
# The predicate succeeds in C with A floor divided by B.
##
def arit_div(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
if beta == 0:
raise make_error(Compound("evaluation_error", ["zero_divisor"]))
return alpha // beta
###
# mod(A, B, C): [ISO 9.1.7]
# The predicate succeeds in C with A modulus B.
##
def arit_mod(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
if beta == 0:
raise make_error(Compound("evaluation_error", ["zero_divisor"]))
return alpha % beta
################################################################
# min/3, max/3, inf/1 and nan/1 #
################################################################
###
# min(A, B, C): [TC2 9.3.9]
# The predicate succeeds in C with the minimum of A and B.
# This implements minimumNumber IEEE 754-2019 §9.6.
##
def arit_min(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
if is_special(alpha) or is_special(beta):
if math.isnan(alpha):
return beta
if math.isnan(beta):
return alpha
if alpha == math.inf:
return beta
if beta == math.inf:
return alpha
if alpha == -math.inf:
return alpha
return beta
elif is_integer(alpha) and is_integer(beta):
return min(alpha, beta)
else:
return min(narrow_float(alpha), narrow_float(beta))
###
# max(A, B, C): [TC2 9.3.8]
# The predicate succeeds in C with the maximum of A and B.
# This implements maximumNumber IEEE 754-2019 §9.6.
##
def arit_max(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
if is_special(alpha) or is_special(beta):
if math.isnan(alpha):
return beta
if math.isnan(beta):
return alpha
if alpha == -math.inf:
return beta
if beta == -math.inf:
return alpha
if alpha == math.inf:
return alpha
return beta
elif is_integer(alpha) and is_integer(beta):
return max(alpha, beta)
else:
return max(narrow_float(alpha), narrow_float(beta))
###
# inf(A):
# The predicate succeeds in A with positive infinity.
##
def arit_inf(args):
return math.inf
###
# nan(A):
# The predicate succeeds in A with not a number.
##
def arit_nan(args):
return math.nan
################################################################
# truncate/2, floor/2, ceiling/2 and round/2. #
################################################################
###
# truncate(A, B): [ISO 9.1.7]
# The predicate succeeds in B with the truncate of A.
##
def arit_truncate(args):
alpha = exec_eval(args[0])
if is_integer(alpha):
return alpha
else:
return math.trunc(narrow_float(alpha))
###
# floor(A, B): [ISO 9.1.7]
# The predicate succeeds in B with the floor of A.
##
def arit_floor(args):
alpha = exec_eval(args[0])
if is_integer(alpha):
return alpha
else:
return math.floor(narrow_float(alpha))
###
# ceiling(A, B): [ISO 9.1.7]
# The predicate succeeds in B with the ceiling of A.
##
def arit_ceiling(args):
alpha = exec_eval(args[0])
if is_integer(alpha):
return alpha
else:
return math.ceil(narrow_float(alpha))
###
# round(A, B): [ISO 9.1.7]
# The predicate succeeds in B with the round of A.
##
def arit_round(args):
alpha = exec_eval(args[0])
if is_integer(alpha):
return alpha
else:
return (math.floor(2*narrow_float(alpha))+1) >> 1
################################################################
# =:=/2, =\=/2, </2, >=/2, >/2 and =</2 #
################################################################
###
# X =:= Y: [ISO 8.7.1]
# The predicate succeeds when X number equals Y, otherwise fails.
##
def test_numberequal(args):
try:
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
return number_equal(alpha, beta)
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))
except ValueError:
raise make_error(Compound("evaluation_error", ["undefined"]))
###
# X =\= Y: [ISO 8.7.1]
# The predicate succeeds when X does not number equal Y, otherwise fails.
##
def test_numbernotequal(args):
try:
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
return not number_equal(alpha, beta)
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))
except ValueError:
raise make_error(Compound("evaluation_error", ["undefined"]))
###
# Determine whether two Prolog numbers are equal.
# This implement compareQuietEqual IEEE 754-2019 §5.6.1.
#
# @param alpha The first Prolog number.
# @param beta The second Prolog number.
# @return True if the two Prolog numbers are equal.
##
def number_equal(alpha, beta):
if is_special(alpha) or is_special(beta):
if math.isnan(alpha):
return False
if math.isnan(beta):
return False
return alpha == beta
elif is_integer(alpha) and is_integer(beta):
return alpha == beta
else:
return narrow_float(alpha) == narrow_float(beta)
###
# X < Y: [ISO 8.7.1]
# The predicate succeeds when X is number less than Y, otherwise fails.
##
def test_numberless(args):
try:
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
return number_less(alpha, beta)
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))
except ValueError:
raise make_error(Compound("evaluation_error", ["undefined"]))
###
# X >= Y: [ISO 8.7.1]
# The predicate succeeds when X is number greater or equal to Y, otherwise fails.
##
def test_numbergreaterequal(args):
try:
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
return number_lessequal(beta, alpha)
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))
except ValueError:
raise make_error(Compound("evaluation_error", ["undefined"]))
###
# X > Y: [ISO 8.7.1]
# The predicate succeeds when X is number greater than Y, otherwise fails.
##
def test_numbergreater(args):
try:
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
return number_less(beta, alpha)
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))
except ValueError:
raise make_error(Compound("evaluation_error", ["undefined"]))
###
# X =< Y: [ISO 8.7.1]
# The predicate succeeds when X is number less or equal to Y, otherwise fails.
##
def test_numberlessequal(args):
try:
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
return number_lessequal(alpha, beta)
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))
except ValueError:
raise make_error(Compound("evaluation_error", ["undefined"]))
###
# Determine whether a Prolog numbers is less than a Prolog number.
# This implement compareQuietLess IEEE 754-2019 §5.6.1.
#
# @param alpha The first Prolog number.
# @param beta The second Prolog number.
# @return True if the first is less than the second, otherwise false.
##
def number_less(alpha, beta):
if is_special(alpha) or is_special(beta):
if math.isnan(alpha):
return False
if math.isnan(beta):
return False
if alpha == beta:
return False
if beta == math.inf:
return True
return alpha == -math.inf
if is_integer(alpha) and is_integer(beta):
return alpha < beta
else:
return narrow_float(alpha) < narrow_float(beta)
###
# Determine whether a Prolog numbers is less or equal than a Prolog number.
# This implement compareQuietLessEqual IEEE 754-2019 §5.6.1.
#
# @param alpha The first Prolog number.
# @param beta The second Prolog number.
# @return True if the first is less or equal than the second, otherwise false.
##
def number_lessequal(alpha, beta):
if is_special(alpha) or is_special(beta):
if math.isnan(alpha):
return False
if math.isnan(beta):
return False
if alpha == beta:
return True
if beta == math.inf:
return True
return alpha == -math.inf
if is_integer(alpha) and is_integer(beta):
return alpha <= beta
else:
return narrow_float(alpha) <= narrow_float(beta)
################################################################
# sin/3, cos/2, tan/2, asin/2, acos/2, atan/2 and pi/1. #
################################################################
###
# sin(A, B): [ISO 9.3.2]
# The predicate succeeds in B with the sine of A.
##
def arit_sin(args):
alpha = exec_eval(args[0])
return norm_float(math.sin(narrow_float(alpha)))
###
# cos(A, B): [ISO 9.3.3]
# The predicate succeeds in B with the cosine of A.
##
def arit_cos(args):
alpha = exec_eval(args[0])
return norm_float(math.cos(narrow_float(alpha)))
###
# tan(A, B): [TC2 9.3.14]
# The predicate succeeds in B with the tangent of A.
##
def arit_tan(args):
alpha = exec_eval(args[0])
return norm_float(math.tan(narrow_float(alpha)))
###
# asin(A, B): [TC2 9.3.11]
# The predicate succeeds in B with the arcus sine of A.
##
def arit_asin(args):
alpha = exec_eval(args[0])
return norm_float(math.asin(narrow_float(alpha)))
###
# acos(A, B): [TC2 9.3.12]
# The predicate succeeds in B with the arcus cosine of A.
##
def arit_acos(args):
alpha = exec_eval(args[0])
return norm_float(math.acos(narrow_float(alpha)))
###
# atan(A, B): [ISO 9.3.4]
# The predicate succeeds in B with the arcus tangent of A.
##
def arit_atan(args):
alpha = exec_eval(args[0])
return norm_float(math.atan(narrow_float(alpha)))
###
# pi(A): [TC2 9.3.15]
# The predicate succeeds in A with π.
##
def arit_pi(args):
return math.pi
################################################################
# (**)/3, exp/2, log/2, sqrt/2, e/1, epsilon/1 and atan2/3 #
################################################################
###
# **(A, B, C): [ISO 9.3.1]
# The predicate succeeds in C with A float power by B.
##
def arit_pow(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
return norm_float(math.pow(narrow_float(alpha), narrow_float(beta)))
###
# exp(A, B): [ISO 9.3.5]
# The predicate succeeds in B with e power by A.
##
def arit_exp(args):
alpha = exec_eval(args[0])
return norm_float(math.exp(narrow_float(alpha)))
###
# log(A, B): [ISO 9.3.6]
# The predicate succeeds in B with the natural logarithm of A.
##
def arit_log(args):
alpha = exec_eval(args[0])
return norm_float(math.log(narrow_float(alpha)))
###
# sqrt(A, B): [ISO 9.3.7]
# The predicate succeeds in B with the square root of A.
##
def arit_sqrt(args):
alpha = exec_eval(args[0])
return norm_float(math.sqrt(narrow_float(alpha)))
###
# e(A): [N208 9.7.2]
# The predicate succeeds in A with the Euler number.
##
def arit_e(args):
return math.e
###
# epsilon(A): [N208 9.7.3]
# The predicate succeeds in A with the machine epsilon.
##
def arit_epsilon(args):
return sys.float_info.epsilon
###
# atan2(A, B, C): [TC2 9.3.13]
# The predicate succeeds in C with the arc tangent of A and B.
##
def arit_atan2(args):
alpha = exec_eval(args[0])
beta = exec_eval(args[1])
alpha = narrow_float(alpha)
beta = narrow_float(beta)
if alpha == 0.0 and beta == 0.0:
raise make_error(Compound("evaluation_error", ["undefined"]))
else:
return norm_float(math.atan2(alpha, beta))
################################################################
# (\)/2, (/\)/3, (\/)/3, (xor)/3, (>>)/3 and (<</3) #
################################################################
###
# \(A, B): [ISO 9.4.5]
# The predicate succeeds in B with the bitwise not of A.
##
def arit_not(args):
alpha = exec_eval(args[0])
check_integer(alpha)
return ~alpha
###
# /\(A, B, C): [ISO 9.4.3]
# The predicate succeeds in C with the bitwise and of A and B.
##
def arit_and(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
return alpha & beta
###
# \/(A, B, C): [ISO 9.4.4]
# The predicate succeeds in C with the bitwise or of A and B.
##
def arit_or(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
return alpha | beta
###
# xor(A, B, C): [TC2 9.4.6]
# The predicate succeeds in C with the bitwise xor of A and B.
##
def arit_xor(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
return alpha ^ beta
###
# >>(A, B, C): [ISO 9.4.1]
# The predicate succeeds in C with A shift right by B.
##
def arit_shiftright(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
if beta >= 0:
return alpha >> beta
else:
return alpha << (- beta)
###
# <<(A, B, C): [ISO 9.4.2]
# The predicate succeeds in C with A shift left by B.
##
def arit_shiftleft(args):
alpha = exec_eval(args[0])
check_integer(alpha)
beta = exec_eval(args[1])
check_integer(beta)
if beta >= 0:
return alpha << beta
else:
return alpha >> (- beta)
################################################################
# ==/2 and \==/2 #
################################################################
###
# S == T: [ISO 8.4.1]
# The built-in succeeds when S and T are syntactically equivalent
# Prolog terms, otherwise the built-in fails.
##
def test_equal(args):
alpha = exec_build(args[0])
beta = exec_build(args[1])
return equal_term(alpha, beta)
###
# S \== T: [ISO 8.4.1]
# The built-in succeeds when S and T are not syntactically equivalent
# Prolog terms, otherwise the built-in fails.
##
def test_notequal(args):
alpha = exec_build(args[0])
beta = exec_build(args[1])
return not equal_term(alpha, beta)
###
# Determine whether two Prolog terms are syntactically equivalent.
# Can handle cyclic terms and deep recursion.
#
# @param first The first Prolog term.
# @param second The second Prolog term.
# @return True if they are syntactically equivalent, otherwise false.
##
def equal_term(first, second):
stack = None
log = None
try:
while True:
first = deref(first)
second = deref(second)
if not is_structure(first):
if not object_equals(first, second):
break
elif not is_structure(second):
break
elif len(first.args) != len(second.args):
break
else:
first = union_find(first)
second = union_find(second)
if first is not second:
if (is_frozen(first) and is_frozen(second) and
first.hash != second.hash):
break
if first.functor != second.functor:
break
log = union_add(log, first, second)
if 0 != len(first.args) - 1:
item = Item(first, second, 0)
stack = stack_push(stack, item)
first = first.args[0]
second = second.args[0]
continue
item = stack_peek(stack)
if item is None:
return True
else:
item.idx += 1
first = item.first.args[item.idx]
second = item.second.args[item.idx]
if item.idx == len(item.first.args) - 1:
stack_pop(stack)
return False
finally:
union_undo(log)
################################################################
# Visitor Triple #
################################################################
###
# Create a visitor triple.
#
# @param backup The backup functor.
# @param accum The accumlated value.
# @param children The argument children.
##
class Triple:
def __init__(self, backup, accum, children):
self.backup = backup
self.accum = accum
self.children = children
###
# Check whether an object is a visitor triple.
#
# @param obj The object.
# @return True if the object is a triple, otherwise false.
##
def is_triple(obj):
return isinstance(obj, Triple)
################################################################
# test_term_hash/2 #
################################################################
###
# term_hash(X, H):
# The predicate succeeds in H with the term hash of the term X.
##
def test_term_hash(args):
alpha = exec_build(args[0])
try:
res = walk_compute(alpha, init_hash, union_hash)
finally:
walk_uncompute(alpha)
return exec_unify(args[1], res)
def init_hash(first):
if is_compound(first):
return object_hash_code(first.functor)
elif is_variable(first):
return variable_serno(first)
elif is_frozen(first):
return first.hash
else:
return object_hash_code(first)
def union_hash(first, second):
return int32(first * 31 + second)
def walk_compute(first, init, reduce):
stack = None
while True:
first = deref(first)
if is_compound(first):
if not is_triple(first.functor):
first.functor = Triple(first.functor, init(first), None)
first.walk &= ~VAR_MASK_SERNO
stack = stack_push(stack, first)
first = first.args[0]
continue
else:
first = first.functor.accum
else:
first = init(first)
item = stack_peek(stack)
while (item is not None and
(item.walk & VAR_MASK_SERNO) == len(item.args) - 1):
first = reduce(item.functor.accum, first)
item.functor.accum = first
stack_pop(stack)
item = stack_peek(stack)
if item is None:
return first
else:
first = reduce(item.functor.accum, first)
item.functor.accum = first
item.walk += 1
first = item.args[item.walk & VAR_MASK_SERNO]
def walk_uncompute(first):
stack = None
while True:
first = deref(first)
if is_compound(first):
if is_triple(first.functor):
first.functor = first.functor.backup
if 0 != len(first.args) - 1:
first.walk &= ~VAR_MASK_SERNO
stack = stack_push(stack, first)
first = first.args[0]
continue
item = stack_peek(stack)
if item is None:
return
else:
item.walk += 1
first = item.args[item.walk & VAR_MASK_SERNO]
if (item.walk & VAR_MASK_SERNO) == len(item.args) - 1:
stack_pop(stack)
################################################################
# atom_codes/2 and char_code/2 #
################################################################
###
# atom_codes(A, L): [ISO 8.16.5]
# If A is a variable, the built-in succeeds in A with the atom
# for the Prolog list L. Otherwise the built-in succeeds in L
# with the Prolog list from the atom A.
##
def test_atom_codes(args):
text = exec_deref(args[0])
if is_variable(text) or is_pending(text):
res = exec_build(args[1])
res = atom_codes_pack(res)
return exec_unify(text, res)
else:
text = exec_build(text)
check_atom(text)
text = atom_codes_unpack(text)
return exec_unify(args[1], text)
def atom_codes_pack(peek):
temp = peek
i = 0
while (is_structure(temp) and
temp.functor == "." and
len(temp.args) == 2 and
i < MAX_ARITY):
ch = deref(temp.args[0])
check_integer(ch)
if ch < 0 or ch > 0x10FFFF:
raise make_error(Compound("domain_error", ["character_code", ch]))
i += 1
temp = deref(temp.args[1])
check_nil(temp)
res = [NotImplemented] * i
temp = peek
i = 0
while (is_structure(temp) and
temp.functor == "." and
len(temp.args) == 2):
ch = deref(temp.args[0])
res[i] = chr(ch)
i += 1
temp = deref(temp.args[1])
return ''.join(res)
def atom_codes_unpack(text):
back = None
res = None
i = 0
while i < len(text):
ch = ord(text[i])
peek = Compound(".", [ch, NotImplemented])
if back is not None:
back.args[1] = peek
else:
res = peek
back = peek
i += 1
if back is not None:
back.args[1] = "[]"
else:
res = "[]"
return res
###
# char_code(C, N): [ISO 8.16.6]
# If C is a variable, the built-in succeeds in C with the
# character for the code N. Otherwise the built-in succeeds
# in N with the code from character C.
##
def test_char_code(args):
text = exec_deref(args[0])
if is_variable(text) or is_pending(text):
ch = exec_build(args[1])
check_integer(ch)
if ch < 0 or ch > 0x10FFFF:
raise make_error(Compound("domain_error", ["character_code", ch]))
return exec_unify(text, chr(ch))
else:
text = exec_build(text)
check_atom(text)
if len(text) != 1:
raise make_error(Compound("type_error",
["character", text]))
return exec_unify(args[1], ord(text[0]))
#######################################################################
# atom_length/2 and atom_join/2 #
#######################################################################
###
# atom_length(X, Y): [ISO 8.16.1]
# The predicate succeeds in Y with the length of the atom X.
##
def test_atom_length(args):
text = exec_build(args[0])
check_atom(text)
return exec_unify(args[1], len(text))
###
# atom_join(L, A):
# The built-in succeeds in A with the join of the atoms L.
##
def test_atom_join(args):
alpha = exec_build(args[0])
res = atom_join(alpha)
return exec_unify(args[1], res)
def atom_join(res):
peek = res
i = 0
while is_structure(peek) and \
peek.functor == "." and \
len(peek.args) == 2 and \
i < MAX_ARITY:
i += 1
peek = deref(peek.args[1])
check_nil(peek)
elems = [NotImplemented] * i
peek = res
i = 0
while is_structure(peek) and \
peek.functor == "." and \
len(peek.args) == 2:
val = deref(peek.args[0])
check_atom(val)
elems[i] = val
i += 1
peek = deref(peek.args[1])
return "".join(elems)
#######################################################################
# atom_concat/3 #
#######################################################################
###
# atom_concat(X, Y, Z): [ISO 8.16.2]
# The built-in succeeds when Z is the concatenation of X and Y.
##
def special_atom_concat(args):
first = deref(args[0])
second = deref(args[1])
third = deref(args[2])
if is_variable(second):
if is_variable(first):
check_atom(third)
return solve2_concat(args, None, 0, None)
else:
check_atom(first)
check_atom(third)
if not third.startswith(first):
return False
if not unify(second, third[len(first):]):
return False
elif is_variable(first):
check_atom(second)
check_atom(third)
if not third.endswith(second):
return False
if not unify(first, third[:len(third)-len(second)]):
return False
else:
check_atom(first)
check_atom(second)
if not unify(third, first+second):
return False
cont(machine.call.args[1])
return True
def solve_concat(rope, at, choice):
goal = deref(machine.call.args[0])
return solve2_concat(goal.args, rope, at, choice)
def solve2_concat(args, rope, at, choice):
text = deref(args[2])
mark = machine.trail
while at <= len(text):
if unify(args[0], text[0:at]) and \
unify(args[1], text[at:]):
at += 1
if at <= len(text):
if choice is None:
choice = Choice(solve_concat, None, at, mark)
else:
choice.at = at
more(choice)
cont(machine.call.args[1])
return True
unbind(mark)
at += 1
return False
#######################################################################
# sys_atom_match/3 and sys_atom_part/4 #
#######################################################################
###
# sys_atom_match(X, Y, Z):
# The built-in succeeds if X has substring Y at Z.
##
def special_sys_atom_match(args):
text = deref(args[0])
check_atom(text)
part = deref(args[1])
check_atom(part)
alpha = deref(args[2])
if is_variable(alpha):
return solve2_match(args, None, 0, None)
else:
check_integer(alpha)
if alpha < 0 or alpha > len(text):
return False
if not text.startswith(part, alpha):
return False
cont(machine.call.args[1])
return True
def solve_match(rope, at, choice):
goal = deref(machine.call.args[0])
return solve2_match(goal.args, rope, at, choice)
def solve2_match(args, rope, at, choice):
text = deref(args[0])
part = deref(args[1])
mark = machine.trail
while at + len(part) <= len(text):
at = text.find(part, at)
if at < 0:
return False
if unify(args[2], at):
at += 1
if at + len(part) <= len(text):
if choice is None:
choice = Choice(solve_match, None, at, mark)
else:
choice.at = at
more(choice)
cont(machine.call.args[1])
return True
unbind(mark)
at += 1
return False
###
# sys_atom_part(X, Y, Z, T):
# The built-in succeeds in T with the substring at
# offset Y and with length Z from X.
##
def special_sys_atom_part(args):
text = deref(args[0])
check_atom(text)
alpha = deref(args[1])
if is_variable(alpha):
beta = deref(args[2])
check_integer(beta)
if beta < 0 or beta > len(text):
return False
return solve2_part(args, 0, beta, None)
else:
check_integer(alpha)
beta = deref(args[2])
check_integer(beta)
if alpha < 0 or alpha > len(text):
return False
if beta < 0 or alpha+beta > len(text):
return False
if not unify(args[3], text[alpha:alpha+beta]):
return False
cont(machine.call.args[1])
return True
def solve_part(rope, at, choice):
goal = deref(machine.call.args[0])
return solve2_part(goal.args, rope, at, choice)
def solve2_part(args, alpha, beta, choice):
text = deref(args[0])
mark = machine.trail
while beta <= len(text):
if unify(args[1], alpha) and \
unify(args[3], text[alpha:beta]):
alpha += 1
beta += 1
if beta <= len(text):
if choice is None:
choice = Choice(solve_part, alpha, beta, mark)
else:
choice.data = alpha
choice.at = beta
more(choice)
cont(machine.call.args[1])
return True
unbind(mark)
alpha += 1
beta += 1
return False
#######################################################################
# sys_last_atom_match/3 and sys_last_atom_part/4 #
#######################################################################
###
# sys_last_atom_match(X, Y, Z):
# The built-in succeeds if X has substring Y at Z.
##
def special_sys_last_atom_match(args):
text = deref(args[0])
check_atom(text)
part = deref(args[1])
check_atom(part)
alpha = deref(args[2])
if is_variable(alpha):
return solve2_last_match(args, None, len(text)-len(part), None)
else:
check_integer(alpha)
if alpha < 0 or alpha > len(text):
return False
if not text.startswith(part, alpha):
return False
cont(machine.call.args[1])
return True
def solve_last_match(rope, at, choice):
goal = deref(machine.call.args[0])
return solve2_last_match(goal.args, rope, at, choice)
def solve2_last_match(args, rope, at, choice):
text = deref(args[0])
part = deref(args[1])
mark = machine.trail
while at >= 0:
at = text.rfind(part, 0, at+len(part))
if at < 0:
return False
if unify(args[2], at):
at -= 1
if at >= 0:
if choice is None:
choice = Choice(solve_last_match, None, at, mark)
else:
choice.at = at
more(choice)
cont(machine.call.args[1])
return True
unbind(mark)
at -= 1
return False
###
# sys_last_atom_part(X, Y, Z, T):
# The built-in succeeds in T with the substring at
# offset Y and with length Z from X.
##
def special_sys_last_atom_part(args):
text = deref(args[0])
check_atom(text)
alpha = deref(args[1])
if is_variable(alpha):
beta = deref(args[2])
check_integer(beta)
if beta < 0 or beta > len(text):
return False
return solve2_last_part(args, len(text)-beta, len(text), None)
else:
check_integer(alpha)
beta = deref(args[2])
check_integer(beta)
if alpha < 0 or alpha > len(text):
return False
if beta < 0 or alpha+beta > len(text):
return False
if not unify(args[3], text[alpha:alpha+beta]):
return False
cont(machine.call.args[1])
return True
def solve_last_part(rope, at, choice):
goal = deref(machine.call.args[0])
return solve2_last_part(goal.args, rope, at, choice)
def solve2_last_part(args, alpha, beta, choice):
text = deref(args[0])
mark = machine.trail
while alpha >= 0:
if unify(args[1], alpha) and \
unify(args[3], text[alpha:beta]):
alpha -= 1
beta -= 1
if alpha >= 0:
if choice is None:
choice = Choice(solve_last_part, alpha, beta, mark)
else:
choice.data = alpha
choice.at = beta
more(choice)
cont(machine.call.args[1])
return True
unbind(mark)
alpha -= 1
beta -= 1
return False
#######################################################################
# Special Init #
#######################################################################
# number specials, basic predicates
set("is", 2, make_check(test_eval))
set("-", 2, make_arithmetic(arit_neg))
set("abs", 2, make_arithmetic(arit_abs))
set("sign", 2, make_arithmetic(arit_sign))
# number specials, basic operations
set("+", 3, make_arithmetic(arit_add))
set("-", 3, make_arithmetic(arit_sub))
set("*", 3, make_arithmetic(arit_mul))
set("/", 3, make_arithmetic(arit_quot))
set("//", 3, make_arithmetic(arit_intquot))
set("rem", 3, make_arithmetic(arit_rem))
# number specials, more operations
set("float", 2, make_arithmetic(arit_float))
set("^", 3, make_arithmetic(arit_intpow))
set("div", 3, make_arithmetic(arit_div))
set("mod", 3, make_arithmetic(arit_mod))
# number specials, magnitude operations
set("min", 3, make_arithmetic(arit_min))
set("max", 3, make_arithmetic(arit_max))
set("inf", 1, make_arithmetic(arit_inf))
set("nan", 1, make_arithmetic(arit_nan))
# number specials, rounding operations
set("truncate", 2, make_arithmetic(arit_truncate))
set("floor", 2, make_arithmetic(arit_floor))
set("ceiling", 2, make_arithmetic(arit_ceiling))
set("round", 2, make_arithmetic(arit_round))
# number specials, magnitude predicates
set("=:=", 2, make_check(test_numberequal))
set("=\\=", 2, make_check(test_numbernotequal))
set("<", 2, make_check(test_numberless))
set(">=", 2, make_check(test_numbergreaterequal))
set(">", 2, make_check(test_numbergreater))
set("=<", 2, make_check(test_numberlessequal))
# number specials, trigonometric operations
set("sin", 2, make_arithmetic(arit_sin))
set("cos", 2, make_arithmetic(arit_cos))
set("tan", 2, make_arithmetic(arit_tan))
set("asin", 2, make_arithmetic(arit_asin))
set("acos", 2, make_arithmetic(arit_acos))
set("atan", 2, make_arithmetic(arit_atan))
set("pi", 1, make_arithmetic(arit_pi))
# number specials, exponential operations
set("**", 3, make_arithmetic(arit_pow))
set("exp", 2, make_arithmetic(arit_exp))
set("log", 2, make_arithmetic(arit_log))
set("sqrt", 2, make_arithmetic(arit_sqrt))
set("e", 1, make_arithmetic(arit_e))
set("epsilon", 1, make_arithmetic(arit_epsilon))
set("atan2", 3, make_arithmetic(arit_atan2))
# number specials, bitwise operations
set("\\", 2, make_arithmetic(arit_not))
set("/\\", 3, make_arithmetic(arit_and))
set("\\/", 3, make_arithmetic(arit_or))
set("xor", 3, make_arithmetic(arit_xor))
set(">>", 3, make_arithmetic(arit_shiftright))
set("<<", 3, make_arithmetic(arit_shiftleft))
# term specials, miscellaneous equality
set("==", 2, make_check(test_equal))
set("\\==", 2, make_check(test_notequal))
set("term_hash", 2, make_check(test_term_hash))
# atom specials, deterministic
set("atom_codes", 2, make_check(test_atom_codes))
set("char_code", 2, make_check(test_char_code))
set("atom_length", 2, make_check(test_atom_length))
set("atom_join", 2, make_check(test_atom_join))
# atom specials, non-deterministic
set("atom_concat", 3, make_special(special_atom_concat))
set("sys_atom_match", 3, make_special(special_sys_atom_match))
set("sys_atom_part", 4, make_special(special_sys_atom_part))
set("sys_last_atom_match", 3, make_special(special_sys_last_atom_match))
set("sys_last_atom_part", 4, make_special(special_sys_last_atom_part))