regulus 0.0.14

A simple, interpreted language with very simple syntax and zero dependencies
Documentation
import(type_id),

def(__stl_arith_err, op, error(
    "Arithmetic",
    +("Unsupported ", op),
)),

# Adds the two values together.
# If they are both integers, `lhs + rhs` is returned.
# If they are both lists, their concatenation is returned.
# If they are both objects, this calls the `+` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(+, lhs, rhs, ifelse(
    &&(is_int(lhs), is_int(rhs)),
    __builtin_int_math(0, lhs, rhs),
    ifelse(
        &&(is_list(lhs), is_list(rhs)),
        extend(lhs, rhs),
        ifelse(
            &&(is_object(lhs), is_object(rhs)),
            @(lhs, +, rhs),
            __stl_arith_err("addition"),
        )
    )
)),

# Subtracts the second value from the first.
# If they are both integers, `lhs - rhs` is returned.
# If they are both objects, this calls the `-` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(-, lhs, rhs, ifelse(
    &&(is_int(lhs), is_int(rhs)),
    __builtin_int_math(1, lhs, rhs),
    ifelse(
        &&(is_object(lhs), is_object(rhs)),
        @(lhs, -, rhs),
        __stl_arith_err("subtraction"),
    )
)),

# Multiplies the two values together.
# If they are both integers, `lhs * rhs` is returned.
# If they are both objects, this calls the `*` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(*, lhs, rhs, ifelse(
    &&(is_int(lhs), is_int(rhs)),
    __builtin_int_math(2, lhs, rhs),
    ifelse(
        &&(is_object(lhs), is_object(rhs)),
        @(lhs, *, rhs),
        __stl_arith_err("multiplication"),
    )
)),

# Divides the first value through the second.
# If they are both integers, `lhs / rhs` is returned (rounded to an integer), raising an error if `rhs` is 0.
# If they are both objects, this calls the `/` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(/, lhs, rhs, ifelse(
    &&(is_int(lhs), is_int(rhs)),
    __builtin_int_math(3, lhs, rhs),
    ifelse(
        &&(is_object(lhs), is_object(rhs)),
        @(lhs, /, rhs),
        __stl_arith_err("division"),
    )
)),

# Calculates the remainder when dividing the first value through the second.
# If they are both integers, `lhs % rhs` is returned, raising an error if `rhs` is 0.
# If they are both objects, this calls the `%` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(%, lhs, rhs, ifelse(
    &&(is_int(lhs), is_int(rhs)),
    __builtin_int_math(4, lhs, rhs),
    ifelse(
        &&(is_object(lhs), is_object(rhs)),
        @(lhs, %, rhs),
        __stl_arith_err("remainder"),
    )
)),

# Calculates the XOR of the two given values.
# If they are both integers, `lhs ^ rhs` is returned.
# If they are both objects, this calls the `^` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(^, lhs, rhs, ifelse(
    &&(is_int(lhs), is_int(rhs)),
    __builtin_int_math(5, lhs, rhs),
    ifelse(
        &&(is_object(lhs), is_object(rhs)),
        @(lhs, ^, rhs),
        __stl_arith_err("XOR"),
    )
)),

# If both arguments are integers, `lhs << rhs` is returned by
# shifting the first integer to the left by the second amount of digits,
# causing an exception in case of overflow or a negative shift amount.
# If they are both objects, this calls the `<<` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(<<, lhs, rhs, ifelse(
    &&(is_int(lhs), is_int(rhs)),
    __builtin_int_shift(0, lhs, rhs),
    ifelse(
        &&(is_object(lhs), is_object(rhs)),
        @(lhs, <<, rhs),
        __stl_arith_err("left shift"),
    )
)),

# If both arguments are integers, `lhs >> rhs` is returned by
# shifting the first integer to the right by the second amount of digits,
# causing an exception in case of overflow or a negative shift amount.
# If they are both objects, this calls the `>>` method of `lhs` with `rhs` as the only argument.
# Otherwise, this raises an error.
def(>>, lhs, rhs, _(
    ifelse(
        &&(is_int(lhs), is_int(rhs)),
        __builtin_int_shift(1, lhs, rhs),
        ifelse(
            &&(is_object(lhs), is_object(rhs)),
            @(lhs, >>, rhs),
            __stl_arith_err("right shift"),
        )
    )
)),

# Compares the two values and returns whether they are equal.
# If they are both objects, this calls the `==` method of `lhs` with `rhs` as the only argument.
# If they are both functions, this always returns `false`.
# If they are something else, but both are of the same type, this compares them naturally.
# (Lists are compared element-wise).
# If they are of different types, this always returns `false`.
def(==, lhs, rhs, ifelse(
    &&(is_object(lhs), is_object(rhs)),
    @(lhs, ==, rhs),
    __builtin_atom_eq(lhs, rhs),
)),

# Compares the two values and returns whether they are not equal.
# This is just a short form for `!(==(lhs, rhs))`.
# See the documentation of `==` for the precise behavior.
def(!=, lhs, rhs, !(==(lhs, rhs))),

# Negates the given value.
# If it is a boolean, this maps `true` to `false` and `false` to `true`.
# If it is an object, this calls the `!` method of `val` with no arguments.
# Otherwise, this raises an error.
def(!, val, ifelse(
    is_object(val),
    @(val, !),
    ifelse(val, false, true)
)),

# Returns whether `lhs` is less than `rhs`.
# This function, as well as `<=`, `>=` and ">", compare by the following rules:
#
# If `lhs` and `rhs` are both objects, this calls the `<` (or `<=`, `>=`, `>`) method of `lhs` with `rhs` as the only argument.
# If they are both integers, this compares them naturally.
# If they are both booleans, this compares them according to `false < true`.
# If they are both null, this compares them according to `null <= null`.
# Otherwise, this raises an exception.
def(<, lhs, rhs, ifelse(
    &&(is_object(lhs), is_object(rhs)),
    @(lhs, <, rhs),
    __builtin_atom_cmp(0, lhs, rhs),
)),

# Returns whether `lhs` is less than or equal to `rhs`.
# See the documentation of `<` for more details.
def(<=, lhs, rhs, ifelse(
    &&(is_object(lhs), is_object(rhs)),
    @(lhs, <=, rhs),
    __builtin_atom_cmp(1, lhs, rhs)
)),

# Returns whether `lhs` is greater than or equal to `rhs`.
# See the documentation of `<` for more details.
def(>=, lhs, rhs, ifelse(
    &&(is_object(lhs), is_object(rhs)),
    @(lhs, >=, rhs),
    __builtin_atom_cmp(2, lhs, rhs)
)),

# Returns whether `lhs` is greater than `rhs`.
# See the documentation of `<` for more details.
def(>, lhs, rhs, ifelse(
    &&(is_object(lhs), is_object(rhs)),
    @(lhs, >, rhs),
    __builtin_atom_cmp(3, lhs, rhs)
)),

# TODO: change these methods so they do not need op(obj, obj) but just op(obj, any_atom) instead