local type = type
local pairs = pairs
local ffi = require('ffi')

ffi.cdef [[
unsigned int sleep(unsigned int seconds);
]]

--Helper functions
local meta_index = function(table, key)
    if key == "_prototype" then
        table._prototype = new_brat(object)
        return table._prototype
    end

    local parent = table:parent()
    if parent then
        if rawget(parent, "_prototype") then
            return (rawget(parent._prototype, key) or parent[key])
        else
            return parent[key]
        end
    else
        return nil
    end
end

local meta_string = function(table)
    return table:to_unders()._lua_string
end

local meta_parent = function(table)
    return table._parent_object
end

local brat_meta_table = {}
brat_meta_table["__index"] = meta_index
brat_meta_table["__tostring"] = meta_string

new_brat = function(parent_object)
    local nb = {}
    nb._parent_object = parent_object
    nb.parent = meta_parent

    local mt = getmetatable(nb)

    if mt == nil then
        mt = brat_meta_table
    else
        mt["__index"] = meta_index
        mt["__tostring"] = meta_string
    end

    setmetatable(nb, mt)

    return nb
end

--Used for table.sort. Must return false when lhs < rhs
local compare = function(lhs, rhs)
    if type(lhs) == "table" and type(rhs) == "table" then
        if lhs._less_equal_greater ~= nil then
            return lhs:_less_equal_greater(rhs) == -1
        end
    elseif type(lhs) == "number" and type(rhs) == "number" then
        local lhs = number:new(lhs)
        return lhs:_less_equal_greater(rhs) == -1
    end

    return false
end

--Object for storing lifted closures

function _call_it(self, _self, ...)
    return self.block(self.arg_table, _self, ...)
end

local _call_table = { ["__call"] = _call_it }

function _lifted_call(block, arg_table)
    local _call_thing = {}
    _call_thing.block = block
    _call_thing.arg_table = arg_table
    _call_thing.__call_thing = true
    setmetatable(_call_thing, _call_table)

    return _call_thing
end

function is_callable(thing)
    return type(thing) == "function" or (type(thing) == "table" and rawget(thing, "__call_thing"))
end

--Functions for identifier conversion

local orex = require "rex_onig"

local escape_ops = {
    ["!"] = "_bang",
    ["*"] = "_star",
    ["-"] = "_minus",
    ["+"] = "_plus",
    ["|"] = "_or",
    ["&"] = "_and",
    ["@"] = "_at",
    ["~"] = "_tilde",
    ["^"] = "_up",
    ["/"] = "_forward",
    ["\\"] = "_back",
    ["?"] = "_question",
    ["<"] = "_less",
    [">"] = "_greater",
    ["="] = "_equal",
    ["%"] = "_percent",
    ["_"] = "_under",
    ["$"] = "_dollar"
}

local unescape_ops = {
    ["bang"] = "!",
    ["star"] = "*",
    ["minus"] = "-",
    ["plus"] = "+",
    ["or"] = "|",
    ["and"] = "&",
    ["at"] = "@",
    ["tilde"] = "~",
    ["up"] = "^",
    ["forward"] = "/",
    ["back"] = "\\",
    ["question"] = "?",
    ["less"] = "<",
    ["greater"] = ">",
    ["notequal"] = "!=",
    ["equal"] = "=",
    ["percent"] = "%",
    ["under"] = "_",
    ["dollar"] = "$"
}

local esc_symbols = orex.new("!=|>=|<=|\\||[!?\\-*+^@~\\/\\\\><$_%|&=]")
local esc_keywords = orex.new(
"\\b(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\\b")
local underscore = function(word)
    return "__" .. word
end

local id_cache = {}
local id_cache_size = 0

local escape_identifier = function(id_name)
    if id_cache[id_name] then
        return id_cache[id_name]
    end

    -- Avoid DoS by clearing cache if it's too big
    if id_cache_size > 500 then
        id_cache = {}
        id_cache_size = 0
    end

    if string.match(id_name, "^%w+$") then
        id_cache[id_name] = id_name
        id_cache_size = id_cache_size + 1

        return id_name
    else
        local name = orex.gsub(id_name, esc_symbols, escape_ops)
        name = orex.gsub(name, esc_keywords, underscore)

        id_cache[id_name] = name
        id_cache_size = id_cache_size + 1

        return name
    end
end

local unesc_symbols = orex.new(
"_(bang|star|minus|plus|or|and|at|tilde|up|forward|back|question|less|greater|equal|percent|under|dollar)")
local unesc_keywords = orex.new(
"__(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)")
local ununderscore = function(word)
    string.sub(word, 1)
end

local unescape_identifier = function(name)
    name = orex.gsub(name, unesc_keywords, ununderscore)
    name = orex.gsub(name, unesc_symbols, unescape_ops)

    return name
end

local to_identifier = function(str)
    if type(str) == "table" and str._lua_string ~= nil then
        str = str._lua_string
    end

    if type(str) ~= "string" then
        error(exception:new("Cannot convert " .. tostring(str) .. " to an identifier"))
    end

    return escape_identifier(str)
end


--Object, the basis of all things in Brat

object = {}

init_object = function(o)
    local mt = getmetatable(o)

    if mt == nil then
        mt = {}
    end

    local to_s = function(table)
        return table:to_unders()._lua_string
    end

    mt["__tostring"] = to_s

    setmetatable(o, mt)
end

init_object(object)

object._prototype = object

object._is_an_object = true

object._is_callable = is_callable

function object:_lua_value()
    return self
end

-- Object: object instance
-- Call: object.parent
--
-- Return parent object if it exists.
function object:parent()
    return object.__null
end

-- Object: object instance
-- Call: object.prototype
-- Call: object.prototype block
-- Call: object.prototype method_hash
-- Returns: prototype
--
-- Set functions for the prototype of the object. The prototype is used when
-- constructing a child of the object (via _new_).
--
-- This function can either take a block which will execute in context of the
-- prototype, or a hash table of method names and the methods themselves.
--
-- Alternatively, calling object.prototype returns the prototype object, so
-- methods can be defined directly on that object.
--
-- Example:
--
-- person = object.new
--
-- person.init = { name |
--   my.name = name
--   my.status = "nuttin'"
-- }
--
-- person.prototype walk: { my.status = "walking" }
--
-- person.prototype {
--   my.talk = { phrase | p "#{my.name} says, '#{phrase}'" }
-- }
--
-- person.prototype.sit = {
--   my.status = "sitting"
-- }
function object:prototype(methods_or_block)
    if methods_or_block then
        if is_callable(methods_or_block) then
            self._prototype:with_underthis(methods_or_block)
        else
            local proto = self._prototype

            methods_or_block:each(function(self, name, meth)
                if is_callable(meth) then
                    proto:add_undermethod(name, meth)
                else
                    proto:add_undermethod(name, function(self)
                        return meth
                    end)
                end
            end)
        end
    end

    return self._prototype
end

function object:new(...)
    local nb

    nb = new_brat(self)


    if nb.init then
        nb:init(...)
    end

    return nb
end

-- Object: object instance
-- Call: object.array?
-- Returns: boolean
--
-- Returns true if object is an array, false otherwise.
function object:array_question()
    return object.__false
end

-- Object: object instance
-- Call: object.hash?
-- Returns: boolean
--
-- Returns true if object is a hash, false otherwise.
function object:hash_question()
    return object.__false
end

-- Object: object instance
-- Call: object.number?
-- Returns: boolean
--
-- Returns true if object is a number, false otherwise.
function object:number_question()
    return object.__false
end

-- Object: object instance
-- Call: object.regex?
-- Returns: boolean
--
-- Returns true if object is a number, false otherwise.
function object:regex_question()
    return object.__false
end

-- Object: object instance
-- Call: object.string?
-- Returns: boolean
--
-- Returns true if object is a string, false otherwise.
function object:string_question(rhs)
    return object.__false
end

-- Object: object instance
-- Call: object.exception?
-- Returns: boolean
--
-- Returns true if object is an exception, false otherwise.
function object:exception_question(rhs)
    return object.__false
end

-- Object: object
-- Call: sleep seconds
-- Returns: number
--
-- Sleep for a given number of seconds.
function object:sleep(secs)
    return ffi.C.sleep(secs)
end

-- Object: object instance
-- Call: object.to_s
-- Returns: string
--
-- Return a string representing the object.
function object:to_unders()
    local meths = self:local_undermethods()
    return base_string:new("object" .. tostring(meths:sort_bang()))
end

-- Object: object instance
-- Call: object.methods
-- Returns: array
--
-- Returns an array containing the names of the methods available on the
-- object, including those inherited from parent objects.
function object:methods()
    local m = {}
    local i = 1
    if self:parent() ~= object.__null then
        m = self:parent():methods()._lua_array
        i = #m + 1
    else
        m = {}
    end

    for k, v in pairs(self) do
        k = unescape_identifier(k)
        if k:find("_", 1, true) ~= 1 then
            m[i] = base_string:new(k)
            i = i + 1
        end
    end

    for k, v in pairs(self:parent()._prototype) do
        k = unescape_identifier(k)
        if k:find("_", 1, true) ~= 1 then
            m[i] = base_string:new(k)
            i = i + 1
        end
    end

    return array:new(m):unique()
end

-- Object: object instance
-- Call: object.local_methods
-- Returns: array
--
-- Returns an array containing the names of the methods available on the
-- object, not including those inherited from parent objects.
function object:local_undermethods()
    local m = {}

    local i = 1
    for k, v in pairs(self) do
        k = unescape_identifier(k)
        if k:find("_", 1, true) ~= 1 then
            m[i] = base_string:new(k)
            i = i + 1
        end
    end

    return array:new(m)
end

object.__null = object:new()
function object.__null:to_unders()
    return base_string:new("null")
end

object.__true = object:new()
function object.__true:to_unders()
    return base_string:new("true")
end

object.__false = object:new()
function object.__false:to_unders()
    return base_string:new("false")
end

function object:null()
    return object.__null
end

function object:_false()
    return object.__false
end

function object:_true()
    return object.__true
end

--This function is for determining if a Brat value is true or false
--and returns a Lua true or false
is_true = function(bool)
    if bool == object.__false or bool == object.__null then
        return false
    else
        return true
    end
end

object._is_true = is_true

-- Object: object
-- Call: print *args
-- Returns: null
--
-- Prints out any number of arguments, with no new line.
function object:print(first, ...)
    if first ~= nil then
        io.write(tostring(first))

        if select('#', ...) > 0 then
            local input = { ... }
            for k, v in pairs(input) do
                input[k] = tostring(v)
            end
            io.write(unpack(input))
        end
    end

    io.flush()
    return object.__null
end

-- Object: object
-- Call: p *args
-- Returns: null
--
-- Prints out any number of arguments, with an added new line.
function object:p(...)
    self:print(...)
    io.write("\n")
    io.flush()
    return object.__null
end

local dont_squish = {
    parent = true,
    prototype = true,
    _prototype = true,
    _parent_object = true,
}

-- Object: object instance
-- Call: object.squish other_object
-- Returns: self
--
-- Squishes the methods of the given object into the current object.
--
-- Also useful for bringing external libraries into the current context.
function object:squish(obj)
    for k, v in pairs(obj) do
        if dont_squish[k] == nil then
            self[k] = v
        end
    end

    return self
end

-- Object: object instance
-- Call: my
-- Returns: self
--
-- Returns the current object.
function object:my()
    return self
end

-- Object: object instance
-- Call: object.parent
-- Returns: object
--
-- Returns the parent of the object.
function object:parent()
    if object["_parent"] then
        return object["_parent"]
    else
        return object.__null
    end
end

-- Object: object
-- Call: function? variable
-- Returns: boolean
--
-- Returns true if given variable is a function, false otherwise.
function object:function_question(obj)
    if obj == nil then
        return false
    elseif is_callable(obj) then
        return object.__true
    else
        return object.__false
    end
end

-- Object: object
-- Call: object? variable
-- Returns: boolean
--
-- Returns true if given variable is an object, false otherwise.
function object:object_question(obj)
    if obj == nil then
        return self:object_question(self)
    elseif type(obj) == "table" then
        return object.__true
    else
        return object.__false
    end
end

-- Object: object
-- Call: random
-- Call: random maximum
-- Returns: number
--
-- With no arguments, returns a number between 0 and 1.
--
-- With a max argument, returns a number _i_, where 0 <= i < _max_.
function object:random(...)
    if not object._random_seed then
        math.randomseed(os.time())
        object._random_seed = true
    end

    local len = select("#", ...)
    if len == 1 then
        local first = select(1, ...)
        if first == 0 then
            return 0
        elseif type(first) == "number" and first < 1 then
            error(exception:argument_error("random", "number > 0", tostring(first)))
        end
    elseif len > 2 then
        error(exception:argument_error("random", "at most 2", len))
    end

    return math.random(...)
end

-- Object: object
-- Call: ask prompt
-- Returns: string
--
-- Prints out the given prompt first, then returns input from standard input.
function object:ask(prompt)
    if prompt == nil then
        return self:g()
    elseif type(prompt) ~= "table" or prompt._lua_string == nil then
        error(exception:argument_error("ask", "string", tostring(prompt)))
    end

    self:print(prompt);

    return self:g()
end

-- Object: object
-- Call: g
-- Returns: string
--
-- Read a string from standard input.
function object:g()
    return base_string:new(io.stdin:read())
end

-- Object: object instance
-- Call: object1 == object2
-- Returns: boolean
--
-- Compare two objects. If the target of the call has a method called <=>
-- that will be used to compare the objects.
function object:_equal_equal(rhs)
    if self == rhs then
        return object.__true
    elseif self["_less_equal_greater"] then
        if self:_less_equal_greater(rhs) == 0 then
            return object.__true
        else
            return object.__false
        end
    else
        return object.__false
    end
end

-- Object: object instance
-- Call: object1 != object2
-- Returns: boolean
--
-- Compares two objects, then negates the result.
function object:_bang_equal(rhs)
    return self:_not(self:_equal_equal(rhs))
end

-- Object: object instance
-- Call: object.true?
-- Call: true? condition
-- Call: true? condition, then_value
-- Call: true? condition, then_value, else_value
--
-- Tests if an object or condition is true. If the condition is true, returns
-- true or the then_value if one is given. If the condition is false, returns
-- false or the else_value if one is given.
--
-- If they are functions, they will be called with the original condition value
-- (before it was converted to a boolean) as a first argument, but the argument can be ignored.
--
-- Typically, then_value and else_value should be functions, to allow for
-- delayed evaluation.
--
-- Example:
--
--   true? 2 + 2 == 5
--     { p "Are you such a dreamer?" }
--     { p "No it doesn't!" }
function object:true_question(...)
    local len = select('#', ...)

    if len == 3 then
        return self:_3_true_question(...)
    elseif len == 2 then
        return self:_2_true_question(...)
    elseif len == 1 then
        return self:_1_true_question(...)
    elseif len == 0 then
        return self:_0_true_question()
    else
        error("Too many arguments for true?")
    end
end

function object:_0_true_question()
    if is_true(self) then
        return object.__true
    else
        return object.__false
    end
end

function object:_1_true_question(obj)
    local condition
    if is_callable(obj) then
        condition = obj(self)
    else
        condition = obj
    end

    if is_true(condition) then
        return object.__true
    else
        return object.__false
    end
end

function object:_2_true_question(condition, true_branch)
    if is_callable(condition) then
        condition = condition(self)
    end

    if is_true(condition) then
        if is_callable(true_branch) then
            return true_branch(self, condition)
        else
            return true_branch
        end
    else
        return object.__false
    end
end

function object:_3_true_question(condition, true_branch, false_branch)
    if is_callable(condition) then
        condition = condition(self)
    end

    if is_true(condition) then
        if is_callable(true_branch) then
            return true_branch(self, condition)
        else
            return true_branch
        end
    else
        if is_callable(false_branch) then
            return false_branch(self, condition)
        else
            return false_branch
        end
    end
end

-- Object: object instance
-- Call: object.false?
-- Call: false? condition
-- Call: false? condition, then_value
-- Call: false? condition, then_value, else_value
--
-- Tests if an object or condition is false. If the condition is false, returns
-- true or the then_value if one is given. If the condition is true, returns
-- false or the else_value if one is given.
--
-- Typically, then_value and else_value should be functions, to allow for
-- delayed evaluation.
--
-- If they are functions, they will be called with the original condition value
-- (before it was converted to a boolean) as a first argument, but the argument can be ignored.
--
-- Example:
--
--   false? 2 + 2 == 5
--     { p "Definitely false." }
--     { p "War is Peace" }
function object:false_question(...)
    local len = select('#', ...)

    if len == 3 then
        return self:_3_false_question(...)
    elseif len == 2 then
        return self:_2_false_question(...)
    elseif len == 1 then
        return self:_1_false_question(...)
    elseif len == 0 then
        return self:_0_false_question()
    else
        error("Too many arguments for false?")
    end
end

function object:_0_false_question()
    if is_true(self) then
        return object.__false
    else
        return object.__true
    end
end

function object:_1_false_question(obj)
    local condition
    if is_callable(obj) then
        condition = obj(self)
    else
        condition = obj
    end

    if is_true(condition) then
        return object.__false
    else
        return object.__true
    end
end

function object:_2_false_question(condition, true_branch)
    if is_callable(condition) then
        condition = condition(self)
    end

    if not is_true(condition) then
        if is_callable(true_branch) then
            return true_branch(self, condition)
        else
            return true_branch
        end
    else
        return object.__false
    end
end

function object:_3_false_question(condition, true_branch, false_branch)
    if is_callable(condition) then
        condition = condition(self)
    end

    if not is_true(condition) then
        if is_callable(true_branch) then
            return true_branch(self, condition)
        else
            return true_branch
        end
    else
        if is_callable(false_branch) then
            return false_branch(self, condition)
        else
            return false_branch
        end
    end
end

-- Object: object instance
-- Call: object.null?
-- Call: null? condition
-- Call: null? condition, then_value
-- Call: null? condition, then_value, else_value
--
-- Tests if an object or condition is null. If the condition is null, returns
-- true or the then_value if one is given. If the condition is not null, returns
-- false or the else_value if one is given.
--
-- Typically, then_value and else_value should be functions, to allow for
-- delayed evaluation.
--
-- If they are functions, they will be called with the original condition value
-- (before it was converted to a boolean) as a first argument, but the argument can be ignored.
--
-- Example:
--
--   null? x
--     { p "x is null" }
--     { p "x is not null" }
function object:null_question(...)
    local len = select('#', ...)

    if len == 3 then
        return self:_3_null_question(...)
    elseif len == 2 then
        return self:_2_null_question(...)
    elseif len == 1 then
        return self:_1_null_question(...)
    elseif len == 0 then
        return self:_0_null_question()
    else
        error("Too many arguments for true?")
    end
end

function object:_0_null_question()
    if self == object.__null then
        return object.__true
    else
        return object.__false
    end
end

function object:_1_null_question(obj)
    local condition
    if is_callable(obj) then
        condition = obj(self)
    else
        condition = obj
    end

    if condition == object.__null then
        return object.__true
    else
        return object.__false
    end
end

function object:_2_null_question(condition, true_branch)
    if is_callable(condition) then
        condition = condition(self)
    end

    if condition == object.__null then
        if is_callable(true_branch) then
            return true_branch(self, condition)
        else
            return true_branch
        end
    else
        return object.__false
    end
end

function object:_3_null_question(condition, true_branch, false_branch)
    if is_callable(condition) then
        condition = condition(self)
    end

    if condition == object.__null then
        if is_callable(true_branch) then
            return true_branch(self, condition)
        else
            return true_branch
        end
    else
        if is_callable(false_branch) then
            return false_branch(self, condition)
        else
            return false_branch
        end
    end
end

-- Object: object
-- Call: not value
-- Returns: boolean
--
-- Returns true if value is false, false if value is true.
function object:_not(arg)
    if is_true(arg) then
        return object.__false
    else
        return object.__true
    end
end

-- Object: object
-- Call: while block
-- Call: while condition, block
--
-- Loops while the given condition remains true. If only one argument is given,
-- the value of that argument is used as the condition.
--
-- Example:
--
--  x = 0
--  while {
--    x = x + 1
--    x < 10
--  }
--
--  #Same thing
--  x = 0
--  while { x < 10 } { x = x + 1 }
function object:_while(...)
    local args = { ... }
    local arglen = #args
    if arglen == 1 then
        while is_true(args[1](self)) do
            --nothing
        end
    elseif arglen == 2 then
        if is_callable(args[1]) then
            while is_true(args[1](self)) do
                args[2](self)
            end
        else
            while is_true(args[1]) do
                args[2](self)
            end
        end
    elseif arglen == 0 then
        error(exception:argument_error("while", "at least 1", "none"))
    else
        error(exception:argument_error("while", "at most 2", arglen))
    end

    return object.__null
end

-- Object: object
-- Call: until block
-- Call: until condition, block
--
-- Loops until the condition becomes true. If only one argument is given, that
-- argument will be used as the condition.
--
-- Example:
--
--  x = 0
--  until {
--    x = x + 1
--    x > 10
--  }
--
--  #Same thing
--  x = 0
--  until { x > 10 } { x = x + 1 }
function object:_until(...)
    local args = { ... }
    local arglen = #args
    if arglen == 1 then
        while not is_true(args[1](self)) do
            --nothing
        end
    elseif arglen == 2 then
        if is_callable(args[1]) then
            while not is_true(args[1](self)) do
                args[2](self)
            end
        else
            while not is_true(args[1]) do
                args[2](self)
            end
        end
    else
        error("Too many arguments to until")
    end

    return object.__null
end

-- Object: object
-- Call: loop block
--
-- Loops block forever.
function object:loop(block)
    while true do
        block(self)
    end

    return object.__null
end

-- Object: object instance
-- Call: lhs && rhs
--
-- Performs boolean "and".
--
-- The value on the right-hand side should generally be a function to provide
-- short-circuiting.
function object:_and_and(rhs)
    if is_true(self) then
        if is_callable(rhs) then
            return rhs(self)
        else
            return rhs
        end
    else
        return self
    end
end

-- Object: object instance
-- Call: lhs || rhs
--
-- Performs boolean "or".
--
-- The value on the right-hand side should generally be a function to provide
-- short-circuiting.
function object:_or_or(rhs)
    if is_true(self) then
        return self
    elseif is_callable(rhs) then
        return rhs(self)
    else
        return rhs
    end
end

-- Object: object instance
-- Call: object.add_method name, function
-- Returns: self
--
-- Add a new method to the object with the given name.
function object:add_undermethod(name, func)
    name = to_identifier(name)
    self[name] = func

    return self
end

-- Object: object instance
-- Call: object.del_method name
-- Returns: self
--
-- Removes the method with the given name from the object.
function object:del_undermethod(name)
    name = to_identifier(name)
    self[name] = nil
    return self
end

-- Object: object instance
-- Call: object.get_method name
-- Returns: function
--
-- Returns the method with the given name, or null if it does not exist.
function object:get_undermethod(name)
    name = to_identifier(name)
    if self[name] == nil then
        return object.__null
    else
        return self[name]
    end
end

-- Object: object instance
-- Call: object.has_method? name
-- Returns: boolean
--
-- Returns true if the object has a method with the given name.
function object:has_undermethod_question(name)
    name = to_identifier(name)
    if self[name] ~= nil then
        return object.__true
    else
        return object.__false
    end
end

-- Object: object instance
-- Call: object.call_method name, arguments
--
-- Calls the given method on the object, passing in the provided arguments.
--
-- Example:
--
-- object.call_method "p", "hello", " ", "world"
function object:call_undermethod(name, ...)
    name = to_identifier(name)
    if self[name] == nil then
        error("No such method to call: " .. name .. " on " .. tostring(self))
    else
        return self[name](self, ...)
    end
end

-- Object: object
-- Call: apply method, arg_array
--
-- Calls the provided method with the arguments in the array.
--
-- Example:
--
-- mult = { x, y | x * y }
-- apply ->mult [2, 4]      # returns 8
function object:apply(meth, args)
    if type(args) ~= "table" or args._lua_array == nil then
        return meth(self, args)
    else
        return meth(self, unpack(args._lua_array))
    end
end

-- Object: object
-- Call: invoke method, *args
--
-- Calls the provided method with the arguments in the array.
--
-- Example:
--
-- mult = { x, y | x * y }
-- invoke ->mult 2 4        # returns 8
function object:invoke(meth, ...)
    return meth(self, ...)
end

-- Object: object instance
-- Call: object.with_this block, arguments
--
-- Calls the given method, but sets the target object as the scope.
-- In other words, calling my inside the block will return the object.
--
-- Example:
--
-- x = object.new
-- x.greeting = "Hello!"
--
-- #Will print "Hello!"
-- x.with_this { p greeting }
function object:with_underthis(block, ...)
    local args = { ... }
    if #args == 0 then
        return block(self)
    else
        return block(self, unpack(args))
    end
end

-- Object: object
-- Call: protect block, options
--
-- Handles exceptions which may be thrown inside the block.
--
-- Options should be provided as a hash. If no options are given, all
-- exceptions will be silently ignored.
--
-- Possible options:
--
-- * rescue: provide a function to call when an exception occurs
-- * ensure: provide a function to always call, even if an exception occurs
-- * from: only rescue a specific type of exception
--
-- Example:
--
-- protect { throw "Problem!" } rescue: { err | p "There was a problem: #{err}" }
function object:protect(block, options)
    if options == nil then
        local status, result = pcall(block, self)
        if status then
            return result
        else
            return object.__null
        end
    elseif type(options) == "table" and options._lua_hash ~= nil then
        local ensure = options:get(base_string:new("ensure"))
        local rescue = options:get(base_string:new("rescue"))
        local filter = options:get(base_string:new("from"))

        if not is_true(rescue) then
            rescue = nil
        end

        if not is_true(ensure) then
            ensure = nil
        end

        if not is_true(filter) then
            filter = nil
        end

        if (rescue and not is_callable(rescue)) or (ensure and not is_callable(ensure)) then
            error(exception:argument_error("protect", "function", tostring(options)))
        end

        local f = function()
            return block(self)
        end

        local handler

        if rescue then
            handler = function(err)
                if type(err) ~= "table" or err._lua_string then
                    err = exception:new(tostring(err))
                end

                if filter and err.type and err.type()._lua_string ~= filter._lua_string then
                    return err
                else
                    return rescue(self, err)
                end
            end
        else
            handler = function(err)
                if type(err) ~= "table" then
                    err = exception:new(tostring(err))
                end

                if filter and err.type and err.type()._lua_string ~= filter._lua_string then
                    return err
                end
            end
        end

        local status, result = xpcall(f, handler)

        if ensure then
            ensure(self)
        end

        if status == false and type(result) == "table" and result.type
            and filter and filter._lua_string ~= result.type()._lua_string then
            error(result)
        end

        return result
    else
        error(exception:argument_error("protect", "hash", tostring(options)))
    end
end

-- Object: object instance
-- Call: object.tap block
-- Returns: self
--
-- Calls given block in context of the object, passing in the object as an
-- argument, and always returns the object.
function object:tap(block)
    block(self, self)

    return self
end

-- Object: object
-- Call: throw exception
--
-- Throws an exception. If a string is provided as the exception, creates a new
-- exception with the string as the error message.
function object:throw(err)
    error(err, 2)
end

function object:__type(obj)
    if obj == nil then
        return object:__type(self)
    else
        local t = type(obj)
        if t == "table" then
            if obj._lua_array then
                return "array"
            elseif obj._lua_string then
                return "string"
            elseif obj._lua_regex then
                return "regex"
            elseif obj._lua_hash then
                return "hash"
            elseif obj._lua_number then
                return "number"
            else
                return "object"
            end
        else
            return t
        end
    end
end

function object:my_undertype()
    return base_string:new(self:__type())
end

-- Object: object
-- Call: includes files
--
-- Calls include for each of the given arguments.
--
-- Example:
--
-- includes :file :json
function object:includes(...)
    for _, f in pairs({ ... }) do
        object:include(f, nil, 4)
    end

    return object.__true
end

-- Object: object
-- Call: include file
-- Call: include file, object
--
-- Executes given file and adds any exported objects to the current context.
-- If an object is specified, it will only import that object.
--
-- This is essentially equivalant to calling squish(import(file)).
--
-- Example:
--
-- include :file
function object:include(file, name, env_level)
    --env_level is used to call to include from another function
    --it sets what environment the file is imported to

    if type(file) == "table" and file._lua_string then
        file = file._lua_string
    end

    if type(file) ~= "string" then
        error(exception:argument_error("include", "string", tostring(file)))
    end

    if type(name) == "table" and name._lua_string then
        name = name._lua_string
    end

    if name and type(name) ~= "string" then
        error(exception:argument_error("include", "string", tostring(file)))
    end

    self:_compile(file)

    package.loaded[file] = false

    require(file)

    if env_level == nil then
        env_level = 3
    end

    local status, env = pcall(getfenv, env_level)

    if status then
        if name then
            for k, v in pairs(_exports) do
                if k == name then
                    env[to_identifier(k)] = v
                end
            end
        else
            for k, v in pairs(_exports) do
                env[to_identifier(k)] = v
            end
        end
    end

    return object.__true
end

-- Object: object
-- Call: import file
-- Call: import file, object
--
-- Loads the given file and returns a hash of the exports from that file.
-- If an object name is specified, only that object will be returned.
function object:import(file, name)
    if type(file) == "table" and file._lua_string then
        file = file._lua_string
    end

    if type(file) ~= "string" then
        error(exception:argument_error("include", "string", tostring(file)))
    end

    if type(name) == "table" and name._lua_string then
        name = name._lua_string
    end

    if name and type(name) ~= "string" then
        error(exception:argument_error("include", "string", tostring(file)))
    end

    self:_compile(file)

    package.loaded[file] = false

    require(file)

    if name then
        if _exports[name] then
            return _exports[name]
        else
            return object.__null
        end
    else
        local imported = hash:new()

        for k, v in pairs(_exports) do
            imported:set(base_string:new(k), v)
        end

        return imported
    end
end

-- Object: object
-- Call: exit
-- Call: exit code
--
-- Immediately terminates the program. If a numeric code is provided, that will
-- be the exit status of the program.
function object:exit(code)
    if type(code) == "number" then
        os.exit(code)
    else
        os.exit()
    end
end

-- Object: object
-- Call: export object, name
--
-- Exports the object to be imported into another file using the given name.
function object:export(obj, name)
    if type(name) == "table" and name._lua_string then
        name = name._lua_string
    end

    if type(name) ~= "string" then
        error(exception:argument_error("export", "string", tostring(name)))
    end

    _exports[name] = obj

    return object.__true
end

-- Object: object
-- Call: program_args
-- Returns: array
--
-- Returns the arguments given to the program when it is executed.
function object:program_underargs()
    if self._program_args == nil then
        local arg_array = {}
        local len = #arg
        local i = 1
        local j = 0

        -- Skip -f and filename
        if arg[1] == '-f' then
            i = 2
        end

        while i <= len do
            arg_array[j] = base_string:new(arg[i])
            i = i + 1
            j = j + 1
        end

        self._program_args = array:new(arg_array)
    end

    return self._program_args
end

-- Object: object
-- Call: when condition, result
--
-- Takes any number of condition-result pairs. Checks each condition and when
-- one returns true, returns the result associated with that condition, calling
-- it with the condition if it is callable.
--
-- Example:
--
-- x = 3
-- when { x < 3 } { p "x is less than 3!" }
--  { x > 3 } { p "x is greater than 3!" }
--  { x == 3 } { p "x is exactly 3!" }
function object:when(...)
    local args = { ... }
    local len = #args
    if len % 2 == 1 then
        error(exception:argument_error("when", "an even number of", len))
    end

    local index = 1
    local cond
    local result = object.__null
    while index <= len do
        cond = args[index]

        if is_callable(cond) then
            cond = cond(self)
        end

        if is_true(cond) then
            result = args[index + 1]
            if is_callable(result) then
                result = result(self, cond)
            end
            break
        end

        index = index + 2
    end

    return result
end

-- Object: object
-- Call: when_equal input, value, result
--
-- Takes an _input_ value as the first argument,
-- then any number of (_value_, _result_) pairs.
--
-- Each _value_ is matched against the _input_ in order.
-- The first _value_ to equal the _input_ will cause the
-- matching _result_ to be returned.
--
-- Example:
--
-- x = 3
--
-- when_equal x,
--   1 { p "x is one" },
--   2 { p "x is two" },
--   3 { p "x is three" }
function object:when_underequal(...)
    local args = { ... }
    local len = #args

    if len < 2 then
        error(exception:argument_error("when_equal", "at least 2", len))
    end

    local value = args[1]

    if is_callable(value) then
        value = value(self)
    end

    local index = 2
    local cond
    local result = object.__null
    local is_num

    if type(value) == "number" then
        is_num = true
    end

    while index <= len do
        cond = args[index]

        if is_callable(cond) then
            cond = cond(self)
        end

        if (is_num and value == cond) or (not is_num and is_true(value:_equal_equal(cond))) then
            result = args[index + 1]

            if is_callable(result) then
                result = result(self, cond)
                break
            end
        end

        index = index + 2
    end

    return result
end

local object_native_methods = {
    true_question = object.true_question,
    false_question = object.false_question,
    null_question = object.null_question,
    when = object.when,
}

object._unchanged = function(method)
    return object_native_methods[method] == object[method]
end


--The comparable squish-in
comparable = object:new()

-- Object: comparable instance
-- Call: object > other
-- Returns: boolean
--
-- Compare two objects using <=> and return true if the target is greater.
function comparable:_greater(rhs)
    local cmp = self:_less_equal_greater(rhs)
    if cmp == 1 then
        return object.__true
    else
        return object.__false
    end
end

-- Object: comparable instance
-- Call: object < other
-- Returns: boolean
--
-- Compare two objects using <=> and return true if the target is less.
function comparable:_less(rhs)
    local cmp = self:_less_equal_greater(rhs)
    if cmp == -1 then
        return object.__true
    else
        return object.__false
    end
end

-- Object: comparable instance
-- Call: object <= other
-- Returns: boolean
--
-- Compare two objects using <=> and return true if the target is less or equal.
function comparable:_less_equal(rhs)
    local cmp = self:_less_equal_greater(rhs)
    if cmp < 1 then
        return object.__true
    else
        return object.__false
    end
end

-- Object: comparable instance
-- Call: object >= other
-- Returns: boolean
--
-- Compare two objects using <=> and return true if the target is greater or equal.
function comparable:_greater_equal(rhs)
    local cmp = self:_less_equal_greater(rhs)
    if cmp > -1 then
        return object.__true
    else
        return object.__false
    end
end

--The number object, for all things numbers

local number_instance = object:new()

number_instance:squish(comparable)

number = object:new()
number._prototype = number_instance

function number_instance:_lua_value()
    return self._lua_number
end

-- Object: number
-- Call: number.new num
-- Returns: number
--
-- Create a new number object. No real reason to use this directly.
function number:new(num)
    local n = new_brat(self)

    n._lua_number = num
    return n
end

-- Object: number instance
-- Call: number.number?
-- Returns: true
function number_instance:number_question()
    return object.__true
end

function number_instance:my()
    return self._lua_number
end

function number_instance:_or_or(rhs)
    return self._lua_number
end

function number_instance:_and_and(rhs)
    return self._lua_number
end

function number_instance:_equal_equal(rhs)
    if self._lua_number == rhs then
        return object.__true
    elseif type(rhs) == "table" and self._lua_number == rhs._lua_number then
        return object.__true
    else
        return object.__false
    end
end

function number_instance:_less_equal_greater(rhs)
    if type(rhs) ~= "number" then
        error("Cannot compare number to " .. type(rhs))
    end

    local lhs = self._lua_number

    if lhs > rhs then
        return 1
    elseif lhs < rhs then
        return -1
    elseif lhs == rhs then
        return 0
    else
        error("Error comparing numbers")
    end
end

-- Object: number instance
-- Call: lhs + rhs
-- Returns: number
--
-- Performs addition.
function number_instance:_plus(rhs)
    if type(rhs) ~= "number" then
        error("Cannot add number to " .. type(rhs))
    end

    return self._lua_number + rhs
end

-- Object: number instance
-- Call: lhs - rhs
-- Returns: number
--
-- Performs subtraction.
function number_instance:_minus(rhs)
    if rhs == nil then
        return -self._lua_number
    else
        if type(rhs) ~= "number" then
            error("Cannot subtract " .. type(rhs) .. " from number")
        end

        return self._lua_number - rhs
    end
end

-- Object: number instance
-- Call: lhs / rhs
-- Returns: number
--
-- Performs division.
function number_instance:_forward(rhs)
    if type(rhs) ~= "number" then
        error("Cannot divide number by " .. type(rhs))
    end

    return self._lua_number / rhs
end

-- Object: number instance
-- Call: num.tan
-- Returns: number
--
-- Returns the tangent.
function number_instance:tan()
    return math.tan(self._lua_number)
end

-- Object: number instance
-- Call: num.cos
-- Returns: number
--
-- Returns the cosine.
function number_instance:cos()
    return math.cos(self._lua_number)
end

-- Object: number instance
-- Call: num.sin
-- Returns: number
--
-- Returns the sine.
function number_instance:sin()
    return math.sin(self._lua_number)
end

-- Object: number instance
-- Call: num.to_rad
-- Returns: number
--
-- Converts to radians.
function number_instance:to_underrad()
    return math.rad(self._lua_number)
end

function number_instance:abs()
    return math.abs(self._lua_number)
end

-- Object: number instance
-- Call: lhs * rhs
-- Returns: number
--
-- Performs multiplication.
function number_instance:_star(rhs)
    if type(rhs) ~= "number" then
        error("Cannot multiply number by " .. type(rhs))
    end

    return self._lua_number * rhs
end

-- Object: number instance
-- Call: lhs % rhs
-- Returns: number
--
-- Performs the modulo operation.
function number_instance:_percent(rhs)
    if type(rhs) ~= "number" then
        error("Modulo needs a number, not " .. type(rhs))
    end

    return self._lua_number % rhs
end

-- Object: number instance
-- Call: lhs ^ rhs
-- Returns: number
--
-- Performs exponentiation.
function number_instance:_up(rhs)
    if type(rhs) ~= "number" then
        error("Cannot use " .. type(rhs) .. " as an exponent")
    end

    return self._lua_number ^ rhs
end

-- Object: number instance
-- Call: number.times block
-- Returns: number
--
-- Performs the block the specified number of times. Passes in the current
-- number to the function.
function number_instance:times(block)
    local index = 0
    local limit = self._lua_number
    while index < limit do
        block(self, index)
        index = index + 1
    end

    return limit
end

-- Object: number instance
-- Call: number.of item
-- Returns: array
--
-- Generates an array of the given item. If item is a function, uses the result
-- of calling it the specified number of times.
--
-- Example:
--
-- 3.of "ha" # Returns ["ha", "ha", "ha"]
function number_instance:of(item)
    local num = self._lua_number
    local i = 1
    local r = {}

    if is_callable(item) then
        while i <= num do
            r[i] = item(self)
            i = i + 1
        end
    else
        while i <= num do
            r[i] = item
            i = i + 1
        end
    end

    return array:new(r)
end

-- Object: number instance
-- Call: number.to limit
-- Call: number.to limit, block
--
-- With no function argument, returns an array containing the numbers from
-- the target to the limit (inclusive). If given a function argument, loops
-- from target to limit, passing in the current number as an argument.
--
-- Example:
--
-- 10.to 1 { n | p n }  # Prints 10 to 1 in decreasing order.
function number_instance:to(stop, block)
    local index = self._lua_number
    if stop < index then
        if block == nil then
            local a = {}
            local i = 1
            local e = math.abs(stop - index) + 1
            index = index + 1
            while i <= e do
                a[i] = index - i
                i = i + 1
            end

            return array:new(a)
        else
            while index >= stop do
                block(self, index)
                index = index - 1
            end

            return stop
        end
    else
        if block == nil then
            local a = {}
            local i = 1
            local e = math.abs(stop - index) + 1
            index = index - 1
            while i <= e do
                a[i] = index + i
                i = i + 1
            end

            return array:new(a)
        else
            while index <= stop do
                block(self, index)
                index = index + 1
            end

            return stop
        end
    end
end

-- Object: number instance
-- Call: number.to_s
-- Returns: string
--
-- Returns a string version of the number.
function number_instance:to_unders()
    return base_string:new(tostring(self._lua_number))
end

-- Object: number instance
-- Call: number.to_i
-- Returns: number
--
-- Truncates number.
function number_instance:to_underi()
    return math.floor(self._lua_number)
end

-- Object: number instance
-- Call: number.to_hex
-- Returns: string
--
-- Converts number to hexadecimal.
function number_instance:to_underhex()
    return base_string:new(string.format("%x", self._lua_number))
end

function number_instance:to_underf()
    return self._lua_number
end

-- Object: number instance
-- Call: number.to_char
-- Returns: string
--
-- Converts number to ASCII representation.
function number_instance:to_underchar()
    return base_string:new(string.char(self._lua_number))
end

-- Object: number instance
-- Call: number.zero?
-- Returns: boolean
--
-- Test whether number is zero.
function number_instance:zero_question()
    return self._lua_number == 0
end

-- Object: number instance
-- Call: number.one?
-- Returns: boolean
--
-- Test whether number is one.
function number_instance:one_question()
    return self._lua_number == 1
end

-- Object: number instance
-- Call: number.negative?
-- Returns: boolean
--
-- Test whether number is negative.
function number_instance:negative_question()
    return self._lua_number < 0
end

-- This is to use native operations when possible.
local number_native_operations = {
    _plus = number_instance._plus,
    _minus = number_instance._minus,
    _star = number_instance._star,
    _up = number_instance._up,
    _percent = number_instance._percent,
    _forward = number_instance._forward,
    _less = number_instance._less,
    _less_greater = number_instance._less_greater,
    _greater = number_instance._greater,
    _greater_equal = number_instance._greater_equal,
    _equal_equal = number_instance._equal_equal
}

number._unchanged = function(method)
    return number_native_operations[method] == number_instance[method] and rawget(number, method) == nil
end

--Enumerable squish-in

enumerable = new_brat(object)

-- Object: enumerable instance
-- Call: enum.any?
-- Call: enum.any? block
-- Returns: boolean
--
-- Returns true if, for any object in the collection, the block returns true.
--
-- If no block is given, returns true if the collection has any elements at all.
function enumerable:any_question(block)
    if block == nil then
        if self._length == 0 then
            return object.__false
        else
            return object.__true
        end
    end

    local flag = false
    local f = function(_self, item)
        if not flag and is_true(block(_self, item)) then
            flag = true
        end
    end

    self:each(f)

    if flag then
        return object.__true
    else
        return object.__false
    end
end

-- Object: enumerable instance
-- Call: enum.all? block
-- Returns: boolean
--
-- Returns true if, for every object in the collection, the block returns true.
function enumerable:all_question(block)
    local flag = true
    local f = function(_self, item)
        if flag and not is_true(block(_self, item)) then
            flag = false
        end
    end

    self:each(f)

    if flag then
        return object.__true
    else
        return object.__false
    end
end

-- Object: enumerable instance
-- Call: enum.find block
-- Returns: boolean
--
-- Returns the first object for which the block returns true.
function enumerable:find(block)
    local found = false
    local f = function(self, item)
        if not found and is_true(block(self, item)) then
            found = item
        end
    end

    self:each(f)

    if found then
        return found
    else
        return object.__null
    end
end

-- Object: enumerable instance
-- Call: enum.max
-- Returns: boolean
--
-- Returns maximum value in enumerable. All items in the enumerable must be comparable.
function enumerable:max()
    local m
    local f = function(self, item)
        local t = type(item)

        if m == nil then
            m = item
        elseif t == "table" then
            if item:_less_equal_greater(m) == 1 then
                m = item
            end
        elseif t == "number" and type(m) == "number" then
            if item > m then
                m = item
            end
        else
            error(exception:new("Cannot compare " .. tostring(item) .. " to " .. tostring(m)))
        end
    end

    self:each(f)

    if m == nil then
        return object.__null
    else
        return m
    end
end

-- Object: enumerable instance
-- Call: enum.min
-- Returns: boolean
--
-- Returns minimum value in enumerable. All items in the enumerable must be comparable.
function enumerable:min()
    local m
    local f = function(self, item)
        local t = type(item)

        if m == nil then
            m = item
        elseif t == "table" then
            if item:_less_equal_greater(m) == -1 then
                m = item
            end
        elseif t == "number" and type(m) == "number" then
            if item < m then
                m = item
            end
        else
            error(exception:new("Cannot compare " .. tostring(item) .. " to " .. tostring(m)))
        end
    end

    self:each(f)

    if m == nil then
        return object.__null
    else
        return m
    end
end

-- Object: enumerable instance
-- Call: enum.select block
-- Call: enum.select method_name
-- Returns: array
--
-- If passed a method name, invokes _method_ on each element and returns an
-- array containing any objects for which the _method_ returns true.
--
-- If passed a function, returns an array containing all objects for which the
-- block returns true.
function enumerable:select(block)
    local new_array = {}
    local i = 1
    local f

    if type(block) == "table" and block._lua_string then
        f = function(_self, item)
            local meth

            if type(item) == "number" then
                meth = number:new(item):get_undermethod(block)
            else
                meth = item:get_undermethod(block)
            end

            if is_true(meth(item)) then
                new_array[i] = item
                i = i + 1
            end
        end
    else
        f = function(_self, item)
            if is_true(block(_self, item)) then
                new_array[i] = item
                i = i + 1
            end
        end
    end

    self:each(f)

    return array:new(new_array)
end

-- Object: enumerable instance
-- Call: enum.reject block
-- Call: enum.reject method_name
-- Returns: array
--
-- If passed a method name, invokes method on each element and returns an
-- array containing any objects for which the method returns false.
--
-- If passed a function, returns an array containing all objects for which the
-- block returns false.
function enumerable:reject(block)
    local new_array = {}
    local f
    local i = 1

    if type(block) == "table" and block._lua_string then
        f = function(_self, item)
            local meth

            if type(item) == "number" then
                meth = number:new(item):get_undermethod(block)
            else
                meth = item:get_undermethod(block)
            end

            if not is_true(meth(item)) then
                new_array[i] = item
                i = i + 1
            end
        end
    else
        f = function(_self, item)
            if not is_true(block(_self, item)) then
                new_array[i] = item
                i = i + 1
            end
        end
    end

    self:each(f)

    return array:new(new_array)
end

--The array object
--Going to keep these separate from hash tables, every if Lua thinks they
--are the same
--

local array_instance = object:new()

array_instance:squish(enumerable)

array = object:new()
array._prototype = array_instance

function array_instance:_lua_value()
    return self._lua_array
end

-- Object: array
-- Call: array.new items
-- Returns: array
--
-- Create a new array.
--
-- Example:
--
-- array.new 1 2 3 # Returns [1, 2, 3]
function array:new(first, ...)
    local na = new_brat(self)

    -- Create empty array
    if first == nil then
        na._lua_array = {}
        na._length = 0
        return na
    end

    if type(first) == "table" and not first._is_an_object and not first.__call_thing and select("#") == 0 then
        na._lua_array = first
    else
        na._lua_array = { first, ... }
    end

    na._length = #na._lua_array
    return na
end

-- Object: array instance
-- Call: array.array?
-- Returns: boolean
--
-- Returns true.
function array_instance:array_question()
    return object.__true
end

-- Object: array instance
-- Call: array.compact
-- Returns: array
--
-- Return a copy of the array with all null values removed.
function array_instance:compact()
    return self:copy():compact_bang()
end

-- Object: array instance
-- Call: array.compact!
-- Returns: array
--
-- Destructively removes all null values for the array.
function array_instance:compact_bang()
    local len = self._length
    local a = self._lua_array
    local index = 1
    local i = 1
    while index <= len do
        if a[index] and a[index] ~= object.__null then
            a[i] = a[index]
            i = i + 1
        end
        index = index + 1
    end

    self._length = i - 1

    return self
end

-- Object: array instance
-- Call: array.copy
-- Returns: array
--
-- Returns a new array containing all the elements of the original.
function array_instance:copy()
    local result = {}
    local len = self._length
    local a = self._lua_array
    local index = 1

    while index <= len do
        result[index] = a[index]
        index = index + 1
    end

    local na = array:new(result)
    na._length = len

    return na
end

-- Object: array instance
-- Call: array.each block
--
-- Invokes the block for each item in the array.
function array_instance:each(block)
    local k = 1
    local len = self._length
    local a = self._lua_array

    while k <= len do
        block(self, a[k])
        k = k + 1
    end

    return self
end

-- Object: array instance
-- Call: array.each_slice num, block
-- Returns: self
--
-- Invokes block for each _num_ items, passed in as single arguments.
-- At the moment, this requires the size of the array to be an exact multiple of _num_.
--
-- Example:
--
-- [1 2 3 4].each_slice 2, { a, b |
--   p "#{a} - #{b}" # prints "1 - 2" then "3 - 4"
-- }
function array_instance:each_underslice(num, block)
    local k = 1
    local len = self._length
    local a = self._lua_array

    while k <= len do
        block(self, unpack(a, k, k + num - 1))
        k = k + num
    end

    return self
end

-- Object: array instance
-- Call: array.each_while block
-- Returns: last value from block
--
-- Invokes block for each item. Stops when block returns false or null.
function array_instance:each_underwhile(block)
    local k = 1
    local len = self._length
    local a = self._lua_array
    local res = object.__true

    while k <= len do
        res = block(self, a[k])
        if is_true(res) then
            k = k + 1
        else
            break
        end
    end

    return res
end

-- Object: array instance
-- Call: array.each_until block
-- Returns: last value from block
--
-- Invokes block for each item. Stops when block returns true.
function array_instance:each_underuntil(block)
    local k = 1
    local len = self._length
    local a = self._lua_array
    local res = object.__false

    while k <= len do
        res = block(self, a[k])

        if not is_true(res) then
            k = k + 1
        else
            break
        end
    end

    return res
end

-- Object: array instance
-- Call: array.each_with_index block
--
-- Invokes the block for each item in the array, passing in the current index
-- as well.
function array_instance:each_underwith_underindex(block)
    local k = 1
    local len = self._length
    local a = self._lua_array

    while k <= len do
        block(self, a[k], k - 1)
        k = k + 1
    end

    return self
end

-- Object: array instance
-- Call: array.deq item
--
-- Remove item from front of array.
function array_instance:deq(item)
    if self._length == 0 then
        return object.__null
    else
        local res = table.remove(self._lua_array, 1)
        if res == nil then
            res = object.__null
        end

        self._length = self._length - 1
        return res
    end
end

-- Object: array instance
-- Call: array.flatten
--
-- Flatten all elements into a single array.
function array_instance:flatten()
    if self._length == 0 then
        return array:new({})
    else
        local first = self._lua_array[1]
        if type(first) == "table" and first._lua_array then
            return first:flatten():_plus(self:rest():flatten())
        else
            return array:new({ first }):_plus(self:rest():flatten())
        end
    end
end

-- Object: array instance
-- Call: array.flat_map _block_
--
-- Flattens all results of _block_ into a single array.
function array_instance:flat_undermap(block)
    if self._length == 0 then
        return array:new({})
    else
        local i = 1
        local len = self._length
        local a = self._lua_array
        local out = {}

        while i <= len do
            local res = block(self, a[i])
            if type(res) == "table" and res._lua_array then
                if res._length > 0 then
                    local k = 1
                    while k <= res._length do
                        table.insert(out, res._lua_array[k])
                        k = k + 1
                    end
                end
            else
                table.insert(out, res)
            end

            i = i + 1
        end

        return array:new(out)
    end
end

-- Object: array instance
-- Call: array.pretty
-- Returns: string
--
-- Returns a string with a nicely formatted representation of the array.
function array_instance:pretty(depth)
    if depth == nil then
        depth = 0
    end

    local out = {}

    local f = function(s, item)
        if type(item) == "number" then
            table.insert(out, item)
        elseif item._lua_array then
            table.insert(out, item:pretty(depth + 1))
        elseif item._lua_string then
            table.insert(out, string.format("%q", item._lua_string))
        else
            table.insert(out, tostring(item))
        end
    end

    self:each(f)

    local out_string = string.rep(" ", depth) .. "[" .. table.concat(out, ",") .. "]"

    if depth > 0 then
        out_string = "\n" .. out_string
    end

    return out_string
end

-- Object: array instance
-- Call: array.reject! block
-- Returns: array
--
-- The first form calls _method_ on each element of the array and removes any elements where that _method_ returns true.
--
-- The second form removes any element for which the function returns true.
function array_instance:reject_bang(block)
    local f

    if is_callable(block) then
        f = block
    elseif type(block) == "table" and block._lua_string then
        f = function(_self, item)
            local meth

            if type(item) == "number" then
                meth = number:new(item):get_undermethod(block)
            else
                meth = item:get_undermethod(block)
            end

            return meth(item)
        end
    end

    local k = 1
    local i = 1
    local len = self._length
    local a = self._lua_array
    while k <= len do
        if not is_true(f(self, a[k])) then
            a[i] = a[k]
            i = i + 1
        end

        k = k + 1
    end

    self._length = i - 1

    return self
end

-- Object: array instance
-- Call: array.reverse_each block
--
-- Invokes block for each item in the array, but starts at the end.
function array_instance:reverse_undereach(block)
    local len = self._length
    local k = len
    local a = self._lua_array

    while k >= 1 do
        block(self, a[k])
        k = k - 1
    end

    return self
end

-- Object: array instance
-- Call: array.reverse_each_while block
--
-- Invokes block for each item in the array, starting from the end.
-- Halts if the block does not return true.
function array_instance:reverse_undereach_underwhile(block)
    local len = self._length
    local k = len
    local a = self._lua_array

    while k >= 1 do
        if is_true(block(self, a[k])) then
            k = k - 1
        else
            break
        end
    end

    return self
end

-- Object: array instance
-- Call: array.select! block
-- Returns: array
--
-- The first form takes the name of a method that will be called on elements
-- of the array.
-- All elements whose methods return false will be removed from the array.
--
-- The second form removes all items for which block returned false.
function array_instance:select_bang(block)
    local f

    if is_callable(block) then
        f = block
    elseif type(block) == "table" and block._lua_string then
        f = function(_self, item)
            local meth

            if type(item) == "number" then
                meth = number:new(item):get_undermethod(block)
            else
                meth = item:get_undermethod(block)
            end

            return meth(item)
        end
    end

    local len = self._length
    local a = self._lua_array
    local k = 1
    local i = 1
    while k <= len do
        if is_true(f(self, a[k])) then
            a[i] = a[k]
            i = i + 1
        end

        k = k + 1
    end

    self._length = i - 1

    return self
end

-- Object: array instance
-- Call: array.index_of item
-- Call: array.index_of item, start
-- Returns: number
--
-- Returns the index of the first item to match the given value.
-- If a start value is given, then the search begins at that index.
function array_instance:index_underof(item, start)
    local k
    if start == nil then
        k = 1
    else
        k = start + 1
    end

    local len = self._length
    local a = self._lua_array

    if type(item) == "number" then
        while k <= len do
            if item == a[k] then
                return k - 1
            end
            k = k + 1
        end
    else
        while k <= len do
            if is_true(item:_equal_equal(a[k])) then
                return k - 1
            end
            k = k + 1
        end
    end

    return object.__null
end

-- Object: array instance
-- Call: array.rindex_of item
-- Call: array.rindex_of item, start_index
--
-- Returns the last index of the item found in the array, or null if there is no such item.
--
-- If start_index is specified, start searching from the given index.
function array_instance:rindex_underof(item, start)
    local len = self._length

    local k
    if start == nil then
        k = len
    else
        k = start + 1
    end

    local a = self._lua_array

    if type(item) == "number" then
        while k >= 1 do
            if item == a[k] then
                return k - 1
            end
            k = k - 1
        end
    else
        while k >= 1 do
            if is_true(item:_equal_equal(a[k])) then
                return k - 1
            end
            k = k - 1
        end
    end

    return object.__null
end

-- Object: array instance
-- Call: array.map block
--
-- Invokes the block for each element in the array and returns a new array
-- containing the results.
function array_instance:map(block)
    local k = 1
    local len = self._length
    local a = self._lua_array
    local new_array = {}

    if type(block) == "table" and block._lua_string then
        local method = to_identifier(block._lua_string)
        block = function(_self, item)
            if type(item) == "number" then
                local n = number:new(item)
                return n[method](n)
            else
                local m = item[method]
                if m == nil then
                    error("In array.map: " .. tostring(item) .. " has no method called '" .. method .. "'")
                elseif is_callable(m) then
                    return item[method](item)
                else
                    return m
                end
            end
        end
    end

    while k <= len do
        if a[k] == nil then
            new_array[k] = block(self, object.__null)
        else
            new_array[k] = block(self, a[k])
        end

        k = k + 1
    end

    return array:new(new_array)
end

-- Object: array instance
-- Call: array.map_while block
--
-- Like array.map, invokes the block for each element in the array and
-- pushes the results to a new array.
--
-- However, if the _block_ returns false or nil, the mapping stops. The
-- new array may therefore be shorter than the original array.
function array_instance:map_underwhile(block)
    if type(block) == "table" and block._lua_string then
        local method = to_identifier(block._lua_string)
        block = function(_self, item)
            if type(item) == "number" then
                local n = number:new(item)
                return n[method](n)
            else
                local m = item[method]
                if m == nil then
                    error("In array.map: " .. tostring(item) .. " has no method called '" .. method .. "'")
                elseif is_callable(m) then
                    return item[method](item)
                else
                    return m
                end
            end
        end
    end

    local k = 1
    local len = self._length
    local a = self._lua_array
    local new_array = {}

    while k <= len do
        local res
        if a[k] == nil then
            res = block(self, object.__null)
        else
            res = block(self, a[k])
        end

        if is_true(res) then
            new_array[k] = res
        else
            break
        end

        k = k + 1
    end

    return array:new(new_array)
end

-- Object: array instance
-- Call: array.reduce block
-- Call: array.reduce initial, block
-- Call: array.reduce method_name
-- Call: array.reduce initial, method_name
--
-- Combines elements in array.
--
-- There are several forms of reduce: one that provides an initial value for
-- memo, one that does not, and two that just provide a method name instead
-- of a function.
--
-- Example:
--
-- #These are all equivalent:
-- 1.to(10).reduce 0 { sum, item | sum + item }
-- 1.to(10).reduce { sum, item | sum + item }
-- 1.to(10).reduce 0 :+
-- 1.to(10).reduce :+
function array_instance:reduce(identity, block)
    if self._length == 0 and block ~= nil then
        return identity
    end

    local index = 1
    if block == nil then
        block = identity
        identity = self:get(0)
        index = 2
    end

    if type(block) == "table" and block._lua_string then
        local method = to_identifier(block._lua_string)
        block = function(self, result, item)
            if type(result) == "number" then
                local n = number:new(result)
                return n[method](n, item)
            else
                return result[method](result, item)
            end
        end
    end

    local len = self._length
    local a = self._lua_array
    local item = nil
    local result = identity
    while index <= len do
        item = a[index]
        if item == nil then
            item = object.__null
        end
        result = block(self, result, item)
        index = index + 1
    end

    return result
end

-- Object: array instance
-- Call: array.sum
--
-- Returns the sum of the items in the array. Only works if the array only
-- contains numbers.
function array_instance:sum()
    local index = 1
    local len = self._length
    local a = self._lua_array
    local sum = 0
    local item = nil

    while index <= len do
        item = a[index]

        if type(item) == "number" then
            sum = sum + item
        elseif item == nil then
            sum = number.new(sum)._plus(object.__null)
        else
            sum = number.new(sum)._plus(item)
        end

        index = index + 1
    end

    return sum
end

-- Object: array instance
-- Call: array.map! block
--
-- Invokes the block for each element in the array and replaces that element
-- with the result.
function array_instance:map_bang(block)
    if type(block) == "table" and block._lua_string then
        local method = to_identifier(block._lua_string)
        block = function(_self, item)
            if type(item) == "number" then
                local n = number:new(item)
                return n[method](n)
            else
                return item[method](item)
            end
        end
    end

    local k = 1
    local len = self._length
    local a = self._lua_array

    while k <= len do
        if a[k] == nil then
            a[k] = object.__null
        end

        a[k] = block(self, a[k])
        k = k + 1
    end

    return self
end

-- Object: array instance
-- Call: array.map_with_index block
--
-- Invokes the block for each element in the array, passing in the index as
-- well, and returns a new array containing the results.
function array_instance:map_underwith_underindex(block)
    local k = 1
    local len = self._length
    local a = self._lua_array
    local new_array = {}

    while k <= len do
        if a[k] == nil then
            new_array[k] = block(self, object.__null, k - 1)
        else
            new_array[k] = block(self, a[k], k - 1)
        end
        k = k + 1
    end

    return array:new(new_array)
end

-- Object: array instance
-- Call: array.map_with_index! block
-- Returns: array
--
-- Invokes the block for each element in the array, passing in the index as
-- well, and then replaces the element with the result of the block.
function array_instance:map_underwith_underindex_bang(block)
    local k = 1
    local len = self._length
    local a = self._lua_array

    while k <= len do
        if a[k] == nil then
            a[k] = object.__null
        end
        a[k] = block(self, a[k], k - 1)
        k = k + 1
    end

    return self
end

-- Object: array instance
-- Call: array.empty?
-- Returns: boolean
--
-- Returns true if the array is empty.
function array_instance:empty_question()
    if self._length == 0 then
        return object.__true
    else
        return object.__false
    end
end

-- Object: array instance
-- Call: array.first
--
-- Returns first element in array, or null if the array is empty.
function array_instance:first()
    if self._length == 0 or self._lua_array[1] == nil then
        return object.__null
    else
        return self._lua_array[1]
    end
end

-- Object: array instance
-- Call: array.last
--
-- Returns last element in array, or null if the array is empty
function array_instance:last()
    if self._length == 0 or self._lua_array[self._length] == nil then
        return object.__null
    else
        return self._lua_array[self._length]
    end
end

-- Object: array instance
-- Call: array.pop
-- Call: array.pop items
--
-- Removes and returns the last element in the array, or null if the array is empty.
--
-- If a number of items is specified, removes and returns at most that many
-- items from the end of the array.
function array_instance:pop(number)
    if number == nil then
        if self._length == 0 then
            return object.__null
        else
            local res = table.remove(self._lua_array, self._length)
            if res == nil then
                res = object.__null
            end
            self._length = self._length - 1
            return res
        end
    else
        if self._length == 0 then
            return array:new({})
        else
            local new_array = {}
            local index = 1
            local item

            while index <= number do
                item = table.remove(self._lua_array, self._length)
                self._length = self._length - 1
                new_array[index] = item

                if self._length == 0 then
                    break
                end

                index = index + 1
            end

            return array:new(new_array)
        end
    end
end

-- Object: array instance
-- Call: array.push item
-- Returns: self
--
-- Pushes item onto the end of the array.
function array_instance:push(item)
    self._length = self._length + 1
    self._lua_array[self._length] = item
    return self
end

-- Object: array instance
-- Call: array.rest
-- Returns: array
--
-- Returns the entire array except the first element.
function array_instance:rest()
    if self._length < 2 then
        return array:new()
    else
        return self:get(1, -1)
    end
end

-- Object: array instance
-- Call: array.reverse!
-- Returns: self
--
-- Reverses the array.
function array_instance:reverse_bang()
    local len = self._length

    if len < 2 then
        return self
    end

    local index = 1
    local temp = nil
    local stop = len / 2
    local a = self._lua_array
    while index <= stop do
        temp = a[index]
        a[index] = a[len - index + 1]
        a[len - index + 1] = temp
        index = index + 1
    end

    return self
end

-- Object: array instance
-- Call: array.reverse
-- Returns: array
--
-- Returns a copy of the array, reversed.
function array_instance:reverse()
    local len = self._length

    if len < 2 then
        return self
    end

    local index = 1
    local a = self._lua_array
    local b = {}
    while index <= len do
        b[index] = a[len - index + 1]
        index = index + 1
    end

    return self:new(b)
end

-- Object: array instance
-- Call: array.shuffle
-- Returns: array
--
-- Returns a copy of the array with the elements shuffled.
function array_instance:shuffle()
    local new_array = {}
    local a = self._lua_array
    local len = #a
    local index = 1

    while index <= len do
        new_array[index] = a[index]
        index = index + 1
    end

    return array:new(new_array):shuffle_bang()
end

-- Object: array instance
-- Call: array.shuffle!
-- Returns: array
--
-- Shuffles the elements of the array in place.
function array_instance:shuffle_bang()
    local a = self._lua_array
    local index = #a
    local random_index
    local temp
    local random = math.random

    if not object._random_seed then
        math.randomseed(os.time())
        object._random_seed = true
    end

    while index > 1 do
        random_index = random(index)
        temp = a[index]
        a[index] = a[random_index]
        a[random_index] = temp

        index = index - 1
    end

    return self
end

-- Object: array instance
-- Call: array.set index, value
-- Returns: value
--
-- Set an index to the given value. More commonly called like
-- _array_[_index_] = _value_.
--
-- If the index is positive and past the end of the array,
-- the array is expanded to the required length.
--
-- The index may be negative, in which case the indexes begin at the end of the
-- array. However, the resulting index must exist (the array does not expand to
-- accommodate negative indexes).
function array_instance:set(index, value)
    if type(index) ~= "number" then
        error(exception:argument_error("array[]", "valid index", index))
    end

    if index < 0 then
        index = self._length + index
    end

    if index < 0 then
        error(exception:argument_error("array[]", "valid index", index))
    end

    self._lua_array[index + 1] = value

    if index >= self._length then
        self._length = index + 1
    end

    return value
end

-- Object: array instance
-- Call: array.get index
-- Call: array.get start, end
--
-- This may also be called as _array_[_index_] or _array_[_start_, _end_].
--
-- For a single index, returns the value at the given index. If the index does
-- not have a value or is past the end of the array, `null` is returned.
--
-- The index may be negative, in which case -1 is the last element of the array.
--
-- If a start and an end index are given, this method returns values between the
-- two indexes.
function array_instance:get(start_index, end_index)
    local len = self._length
    if end_index == nil then
        if start_index < 0 then
            start_index = len + start_index
        end

        local val = self._lua_array[start_index + 1]
        if val == nil then
            return object.__null
        else
            return val
        end
    else
        if start_index < 0 then
            start_index = len + start_index
        end

        if end_index < 0 then
            end_index = len + end_index
        end

        if start_index < 0 then
            start_index = 0
        end

        if end_index < 0 then
            end_index = 0
        end

        if start_index > end_index then
            local temp = start_index
            start_index = end_index
            end_index = temp
        end

        if start_index >= len then
            return array:new()
        end

        if end_index >= len then
            end_index = len - 1
        end

        local index = start_index
        local new_index = 1
        local new_array = {}
        local val

        while index <= end_index do
            val = self._lua_array[index + 1]
            if val == nil then
                new_array[new_index] = object.__null
            else
                new_array[new_index] = val
            end
            index = index + 1
            new_index = new_index + 1
        end

        return array:new(new_array)
    end
end

-- Object: array instance
-- Call: array.insert index, value
-- Returns: self
--
-- Inserts the given value into the array at the given position. If the index
-- is beyond the end of the array, the array will be extended to that index.
--
-- The index may be negative, but the resulting index must already exist.
function array_instance:insert(index, value)
    if type(index) ~= "number" then
        error(exception:argument_error("array.insert", "valid index", index))
    end

    if index < 0 then
        index = self._length + index
    end

    if index < 0 then
        error(exception:argument_error("array.insert", "valid index", index))
    end

    if index < self._length then
        table.insert(self._lua_array, index + 1, value)
        self._length = self._length + 1
    else
        self:set(index, value)
    end

    return self
end

-- Object: array instance
-- Call: array.length
-- Returns: number
--
-- Returns the length of the array.
function array_instance:length()
    return self._length
end

function array_instance:_dup()
    return array:new(self:_copy())
end

function array_instance:_copy()
    local na = {}
    local a = self._lua_array
    local len = self._length
    local i = 1

    while i <= len do
        na[i] = a[i]
        i = i + 1
    end

    return na
end

-- Object: array instance
-- Call: array.sort
-- Returns: array
--
-- Returns a new array with the contents sorted.
--
-- All items in the array must be comparable and nonnull.
function array_instance:sort()
    local a = self._lua_array
    if self._length <= 1 then
        return self:_dup()
    end

    a = self:_copy()

    table.sort(a, compare)

    return array:new(a)
end

-- Object: array instance
-- Call: array.sort_by block
-- Returns: array
--
-- Returns a new array with the contents sorted using the given function.
--
-- The function should take two arguments and return true when `a < b`
-- and false otherwise.
function array_instance:sort_underby(block)
    local a = self._lua_array
    if #a <= 1 then
        return self:_dup()
    end

    local comp = function(rhs, lhs)
        return is_true(block(self, rhs, lhs))
    end

    a = self:_copy()

    table.sort(a, comp)

    return array:new(a)
end

-- Object: array instance
-- Call: array.sort!
-- Returns: self
--
-- Sorts the array in place.
function array_instance:sort_bang()
    local a = self._lua_array
    if self._length <= 1 then
        return self
    end

    table.sort(a, compare)

    return self
end

-- Object: array instance
-- Call: array.join
-- Call: array.join separator
-- Call: array.join separator, final
-- Returns: string
--
-- Coverts all elements of the array into strings and joins them together into
-- a single string.
--
-- If a separator is given, it will be placed in between each element.
-- If a final value is given, it will be inserted in between the last
-- and penultimate values.
--
-- Example:
--
-- [1,2,3,4].join(", ", ", and ") #=> "1, 2, 3, and 4"
--
function array_instance:join(separator, final)
    if self._length == 0 then
        return base_string:new("")
    elseif self._length == 1 then
        return base_string:new(tostring(self._lua_array[1]))
    end

    if separator == nil then
        separator = ""
    elseif type(separator) == "table" then
        separator = separator._lua_string
    end

    if type(final) == "table" then
        final = final._lua_string
    end

    local i = 1
    local len = self._length
    local a = self._lua_array
    local obj = nil
    local contents = {}

    while i <= len do
        obj = a[i]

        if obj == nil then
            obj = object.__null
        end

        contents[i] = tostring(obj)

        i = i + 1
    end

    if final then
        return base_string:new(table.concat(contents, separator, 1, len - 1) .. final .. contents[len])
    else
        return base_string:new(table.concat(contents, separator))
    end
end

-- Object: array instance
-- Call: array << value
-- Returns: self
--
-- Appends value to end of array.
function array_instance:_less_less(obj)
    self._length = self._length + 1
    self._lua_array[self._length] = obj

    return self
end

-- Object: array instance
-- Call: array.concat array
-- Returns: self
--
-- Appends array to the end of another array.
function array_instance:concat(arr)
    if arr._length == 0 then
        return self
    elseif self._length == 0 then
        local c = arr:copy()
        self._lua_array = c._lua_array
        self._length = c._length

        return self
    end

    local a = self._lua_array
    local len = self._length
    local stop = arr._length + len
    local i = 1

    while i < stop do
        a[i + len] = arr._lua_array[i]
        i = i + 1
    end

    self._length = stop
    return self
end

-- Object: array instance
-- Call: array1 + array2
-- Returns: array
--
-- Create a new array by joining two existing arrays.
function array_instance:_plus(obj)
    if type(obj) ~= "table" or obj._lua_array == nil then
        error(exception:argument_error("array.+", "array", tostring(obj)))
    end

    local index = 1
    local rhs = obj._lua_array
    local lhs = self._lua_array
    local na = {}

    local len = self._length
    while index <= len do
        na[index] = lhs[index]
        index = index + 1
    end

    len = obj._length
    index = 1
    while index <= len do
        na[index + self._length] = rhs[index]
        index = index + 1
    end

    return self:new(na)
end

-- Object: array instance
-- Call: array.to_s
-- Returns: string
--
-- Convert array and contents to strings.
function array_instance:to_unders()
    local s = "[" .. self:join(", ")._lua_string .. "]"
    return base_string:new(s)
end

function array_instance:__hash()
    return self:to_unders()._lua_string
end

-- Object: array instance
-- Call: array1 == array2
-- Returns: boolean
--
-- Compares the contents of two arrays.
function array_instance:_equal_equal(rhs)
    if type(rhs) ~= "table" or rhs._lua_array == nil then
        return object.__false
    elseif rhs._lua_array == self._lua_array then
        return object.__true
    elseif rhs._length ~= self._length then
        return object.__false
    else
        local k = 1
        local lhs = self._lua_array
        local rhs = rhs._lua_array
        local len = self._length
        local match = true
        while k <= len do
            local vr = rhs[k]
            local vl = lhs[k]

            if vr ~= vl then
                local tr = type(vr)
                local tl = type(vl)

                if tr == "table" and
                    tl == "table" and
                    vl._is_an_object and
                    vr._is_an_object and
                    is_true(vl:_equal_equal(vr)) then
                    --next
                elseif vl == nil and vr == object.__null then
                    --next
                elseif vr == nil and vl == object.__null then
                    --next
                else
                    match = false
                    break
                end
            end

            k = k + 1
        end

        if match then
            return object.__true
        else
            return object.__false
        end
    end
end

-- Object: array instance
-- Call: array.clear
-- Returns: self
--
-- Empties array.
function array_instance:clear()
    self._lua_array = {}
    self._length = 0
    return self
end

-- Object: array instance
-- Call: array.delete_first value
-- Returns: self
--
-- Removes first item in array matching the given value.
function array_instance:delete_underfirst(item)
    local a = self._lua_array
    local len = self._length
    if len == 0 then
        return self
    end

    local found = false
    local k = 0
    while k <= len do
        if a[k] == item then
            found = true
            break
        elseif type(a[k]) == "table" and a[k]._is_an_object and a[k]:_equal_equal(item) then
            found = true
            break
        end

        k = k + 1
    end

    if found then
        table.remove(a, k)
        self._length = self._length - 1
    end

    return self
end

-- Object: array instance
-- Call: array.include? value
-- Returns: boolean
--
-- Returns true if the array contains the given value.
function array_instance:include_question(item)
    local a = self._lua_array
    local len = self._length
    if len == 0 then
        return object.__false
    end

    local found = false
    local k = 0
    while k <= len do
        if type(a[k]) == "table" and a[k]._is_an_object then
            if is_true(a[k]:_equal_equal(item)) then
                found = true
                break
            end
        elseif a[k] == item then
            found = true
            break
        end

        k = k + 1
    end

    if found then
        return object.__true
    else
        return object.__false
    end
end

-- Object: array instance
-- Call: array.unique
-- Returns: array
--
-- Returns a new array containing no duplicate items.
function array_instance:unique()
    local h = hash:new()
    local a = self._lua_array
    local len = self._length
    local item
    local index = 1

    while index <= len do
        item = a[index]
        if item == nil then
            item = object.__null
        end
        h:set(item, true)
        index = index + 1
    end

    return h:keys()
end

-- Object: array instance
-- Call: array.unique!
-- Returns: self
--
-- Removes duplicate items from array.
function array_instance:unique_bang()
    local h = hash:new()
    local a = self._lua_array
    local len = self._length
    local item
    local index = 1

    while index <= len do
        item = a[index]
        if item == nil then
            item = object.__null
        end
        h:set(item, true)
        index = index + 1
    end

    self._lua_array = h:keys()._lua_array
    self._length = #self._lua_array

    return self
end

--Hash objects

local hash_instance = object:new()

hash = object:new()
hash._prototype = hash_instance

function hash_instance:_lua_value()
    return self._lua_hash
end

-- Object: hash
-- Call: hash.new
-- Returns: hash
--
-- Returns a new hash table.
function hash:new(arg)
    local nh = new_brat(self)
    nh._length = nil

    if type(arg) == "table" and arg._lua_hash then
        nh._lua_hash = arg._lua_hash
        nh._key_hash = arg._key_hash
    elseif type(arg) == "table" then
        local key_map = {}
        local key
        for k, v in pairs(arg) do
            if type(k) == "table" and type(k.__hash) == "function" then
                key = k:__hash()
                key_map[key] = k
            end
        end

        nh._lua_hash = arg
        nh._key_hash = key_map
    elseif arg == nil then
        nh._lua_hash = {}
        nh._key_hash = {}
    else
        error("argument error")
    end

    return nh
end

-- Object: hash instance
-- Call: hash.hash?
-- Returns: boolean
--
-- Returns true.
function hash_instance:hash_question()
    return object.__true
end

-- Object: hash instance
-- Call: hash == hash2
-- Returns: boolean
--
-- Returns true if the contents of the two hashes are the same.
function hash_instance:_equal_equal(rhs)
    if self == rhs then
        return object.__true
    elseif type(rhs) ~= "table" or rhs._lua_hash == nil then
        return object.__false
    elseif self:length() ~= rhs:length() then
        return object.__false
    else
        local vr

        for k, v in pairs(self._lua_hash) do
            vr = rhs:get(k)
            if type(v) == "table" and v._equal_equal then
                if not is_true(v:_equal_equal(vr)) then
                    return object.__false
                end
            elseif v ~= vr then
                return object.__false
            end
        end

        return object.__true
    end
end

-- Object: hash instance
-- Call: hash.get key
--
-- Returns the value stored at the given key. More commonly used with the `[]`
-- syntax.
--
-- Example:
--
-- h = [:]
-- h[:hello] = :world
-- h[:hello] # returns "world"
function hash_instance:get(index)
    local val = self._lua_hash[index]
    if val then
        return val
    elseif type(index) == "table" and type(index.__hash) == "function" then
        index = self._key_hash[index:__hash()]
        val = self._lua_hash[index]

        if val then
            return val
        end
    elseif type(index) == "string" then
        index = self._key_hash[index]
        val = self._lua_hash[index]

        if val then
            return val
        end
    end

    return object.__null
end

-- Object: hash instance
-- Call: hash.set key, value
--
-- Stores the given value at the given key. More commonly used with the `[]`
-- syntax.
--
-- Example:
--
-- h = [:]
-- h[:hello] = :world
-- h[:hello] # returns "world"
function hash_instance:set(index, value)
    if type(index) == "table" and type(index.__hash) == "function" then
        local key = index:__hash()
        if self._key_hash[key] then
            self._lua_hash[self._key_hash[key]] = nil
        end

        self._key_hash[key] = index
    elseif type(index) == "string" then
        local key = index
        if self._key_hash[key] then
            index = self._key_hash[key]
        else
            index = base_string:new(key)
            self._key_hash[key] = index
        end
    end

    self._lua_hash[index] = value
    self._length = nil

    return value
end

-- Object: hash instance
-- Call: hash.clear
-- Returns: self
--
-- Removes all contents from hash.
function hash_instance:clear()
    self._key_hash = {}
    self._lua_hash = {}
    self._length = 0

    return self
end

-- Object: hash instance
-- Call: hash.delete key
--
-- Deletes given key from the hash table. Returns the value stored at that key.
function hash_instance:delete(index)
    if type(index) == "table" and type(index.__hash) == "function" then
        local key = index:__hash()
        index = self._key_hash[key]
        self._key_hash[key] = nil
    end

    self._lua_hash[index] = nil
    self._length = nil

    return value
end

-- Object: hash instance
-- Call: hash.map block
-- Returns: array
--
-- Invokes the block for each key-value pair in the hash and returns a new
-- array containing the results.
function hash_instance:map(block)
    local a = {}
    local i = 1
    for k, v in pairs(self._lua_hash) do
        a[i] = block(self, k, v)
        i = i + 1
    end

    return array:new(a)
end

-- Object: hash instance
-- Call: hash.each block
-- Returns: self
--
-- Invokes the block for each key-value pair in the hash.
function hash_instance:each(block)
    for k, v in pairs(self._lua_hash) do
        block(self, k, v)
    end

    return self
end

-- Object: hash instance
-- Call: hash.each_value block
-- Returns: self
--
-- Invokes the block for each value in the hash.
function hash_instance:each_undervalue(block)
    for k, v in pairs(self._lua_hash) do
        block(self, v)
    end

    return self
end

-- Object: hash instance
-- Call: hash.each_value block
-- Returns: self
--
-- Invokes the block for each key in the hash.
function hash_instance:each_underkey(block)
    for k, v in pairs(self._lua_hash) do
        block(self, k)
    end

    return self
end

-- Object: hash instance
-- Call: hash + hash2
-- Returns: hash
--
-- Combines two hashes into a single hash. Values from the righthand side
-- value take precedence.
function hash_instance:_plus(rhs)
    if type(rhs) ~= "table" or rhs._lua_hash == nil then
        error(exception:argument_error("hash.+", "hash", tostring(rhs)))
    end

    local new_hash = hash:new()

    for k, v in pairs(self._lua_hash) do
        new_hash:set(k, v)
    end

    for k, v in pairs(rhs._lua_hash) do
        new_hash:set(k, v)
    end

    return new_hash
end

require 'md5'

local md5_hash = md5.digest
md5 = nil

function hash_instance:__hash()
    local h = {}
    local i = 1
    for k, v in pairs(self._lua_hash) do
        h[i] = tostring(k) .. tostring(v)
        i = i + 1
    end

    table.sort(h)
    local s = ""
    while i > 1 do
        i = i - 1
        s = s .. h[i]
    end

    return md5_hash(s)
end

-- Object: hash instance
-- Call: hash.keys
-- Returns: array
--
-- Returns an array containing all the keys from the hash table.
function hash_instance:keys()
    local keys = {}
    local index = 1
    for k, v in pairs(self._lua_hash) do
        keys[index] = k
        index = index + 1
    end

    return array:new(keys)
end

-- Object: hash instance
-- Call: hash.values
-- Returns: array
--
-- Returns an array containing all the values from the hash table.
function hash_instance:values()
    local values = {}
    local index = 1
    for k, v in pairs(self._lua_hash) do
        values[index] = v
        index = index + 1
    end

    return array:new(values)
end

-- Object: hash instance
-- Call: hash.length
-- Returns: number
--
-- Returns the number of elements in the hash table.
--
-- Example:
--
-- [a: 1].length # 1
function hash_instance:length()
    if self._length == nil then
        local len = 0

        for k, v in pairs(self._lua_hash) do
            len = len + 1
        end

        self._length = len
    end

    return self._length
end

-- Object: hash instance
-- Call: hash.select block
-- Returns: hash
--
-- Passes each key-value to the given block and returns a new hash only
-- containing the pairs for which the block returns true.
function hash_instance:select(block)
    local result = {}
    for k, v in pairs(self._lua_hash) do
        if is_true(block(self, k, v)) then
            result[k] = v
        end
    end

    return hash:new(result)
end

-- Object: hash instance
-- Call: hash.empty?
-- Returns: boolean
--
-- Returns true if the hash table is empty.
function hash_instance:empty_question()
    if next(self._lua_hash) == nil then
        self._length = 0
        return object.__true
    else
        return object.__false
    end
end

-- Object: hash instance
-- Call: hash.key? key
-- Returns: boolean
--
-- Returns true if the hash table contains the given key.
function hash_instance:key_question(item)
    local val = self._lua_hash[item]
    if val then
        return object.__true
    elseif type(item) == "table" and type(item.__hash) == "function" then
        item = self._key_hash[item:__hash()]
        val = self._lua_hash[item]

        if val then
            return object.__true
        end
    end

    return object.__false
end

-- Object: hash instance
-- Call: hash.to_s
-- Returns: string
--
-- Converts the hash table to a string.
function hash_instance:to_unders()
    local contents = {}
    local i = 1
    for k, v in pairs(self._lua_hash) do
        contents[i] = tostring(k) .. ": " .. tostring(v)
        i = i + 1
    end

    if i == 1 then
        return base_string:new("[:]")
    else
        return base_string:new("[" .. table.concat(contents, ", ") .. "]")
    end
end

local hash_native_methods = {
    get = hash_instance.get,
    set = hash_instance.set,
}

function hash_instance:_unchanged(method)
    return hash_native_methods[method] == hash_instance[method] and rawget(hash, method) == nil
end

--String objects

local string_instance = object:new()

string_instance:squish(comparable)
string_instance:squish(enumerable)

base_string = object:new()

base_string._prototype = string_instance

function string_instance:_lua_value()
    return self._lua_string
end

-- Object: string
-- Call: string.new
-- Returns: string
--
-- Create a new string. There should be no reason to use this directly.
function base_string:new(s)
    if s == nil then
        s = ""
    end

    local ns = new_brat(self)

    ns._lua_string = s

    if type(s) == "string" then
        ns._lua_string = s
    elseif type(s) == "table" then
        if s._lua_string == nil then
            error("error")
        else
            ns._lua_string = s._lua_string
        end
    end

    return ns
end

-- Object: string instance
-- Call: string.length
-- Returns: number
--
-- Returns the length of the string.
function string_instance:length()
    return #self._lua_string
end

-- Object: string instance
-- Call: string.to_s
-- Returns: self
--
-- Does nothing, just returns the string itself.
function string_instance:to_unders()
    return self
end

-- Object: string instance
-- Call: string.chomp
-- Returns: string
--
-- Create a new string with any line endings removed.
--
-- Example:
--
-- "a\n\n".chomp  # Returns "a"
function string_instance:chomp()
    return base_string:new(string.gsub(self._lua_string, "[\n\r]+$", ""))
end

-- Object: string instance
-- Call: string.chomp!
-- Returns: string
--
-- Remove any line endings from string.
--
-- Example:
--
-- a = "a\r\n"
-- a.chomp!
-- a              # Returns "a"
function string_instance:chomp_bang()
    self._lua_string = string.gsub(self._lua_string, "[\n\r]+$", "")
    return self
end

-- Object: string instance
-- Call: string.each block
-- Returns: string
--
-- Interate over each character in the string,
-- passing them into the given block.
--
-- Example:
--
-- a = []
-- "abc".each { letter |
--   a << letter
-- }
--
-- a             # Returns ["a", "b", "c"]
function string_instance:each(block)
    local s = self._lua_string
    local len = #s
    local index = 1

    while index <= len do
        block(self, base_string:new(s:sub(index, index)))

        index = index + 1
    end

    return self
end

-- Object: string instance
-- Call: string.each_line
-- Call: string.each_line block
-- Returns: string or array
--
-- Iterate over each line of the string.
--
-- Returns an array of lines if no block is provided.
function string_instance:each_underline(block)
    local pos = 1
    local s = self._lua_string
    local s_end
    local result
    if block == nil then
        result = {}
    end

    s_end = s:find("\n", pos, true)

    while s_end do
        if block then
            block(self, base_string:new(s:sub(pos, s_end - 1)))
        else
            table.insert(result, base_string:new(s:sub(pos, s_end - 1)))
        end

        pos = s_end + 1
        s_end = s:find("\n", pos, true)
    end

    if pos <= #s then
        if block then
            block(self, base_string:new(s:sub(pos, -1)))
        else
            table.insert(result, base_string:new(s:sub(pos, -1)))
        end
    end

    if block then
        return self
    else
        return array:new(result)
    end
end

-- Object: string instance
-- Call: string.empty?
-- Returns: boolean
--
-- Returns true if the string is empty (zero length), false otherwise.
--
-- Example:
--
-- "".empty?      # Returns true
-- "a".empty?     # Returns false
-- "\n".empty?    # Returns false
function string_instance:empty_question()
    if #self._lua_string == 0 then
        return object.__true
    else
        return object.__false
    end
end

-- Object: string instance
-- Call: string.reverse_each block
-- Returns: boolean
--
-- Iterates over each character in string, starting from the end.
--
-- Example:
--
-- a = []
-- "abc".each { letter |
--   a << letter
-- }
--
-- a             # Returns ["c", "b", "a"]
function string_instance:reverse_undereach(block)
    local s = self._lua_string
    local index = #s

    while index > 0 do
        block(self, base_string:new(s:sub(index, index)))

        index = index - 1
    end

    return self
end

string_instance.__stripper = orex.new("(^\\s+)|(\\s+$)")

-- Object: string instance
-- Call: string.strip
-- Returns: string
--
-- Returns a new string with all whitespace removed from the beginning and end
-- of the string.
--
-- Example:
--
-- "  a\n".strip       # Returns "a"
function string_instance:strip()
    return base_string:new(orex.gsub(self._lua_string, string_instance.__stripper, ""))
end

-- Object: string instance
-- Call: string.strip!
-- Returns: self
--
-- Removes all whitespace from the beginning and end of the string.
--
-- Example:
--
-- a = "   a\n"
-- a.strip!
-- a                  # Returns "a"
function string_instance:strip_bang()
    self._lua_string = orex.gsub(self._lua_string, string_instance.__stripper, "")
    return self
end

-- Object: string instance
-- Call: string.string?
-- Returns: boolean
--
-- Returns true...because it is a string.
function string_instance:string_question()
    return object.__true
end

-- Object: string instance
-- Call: string.alpha?
-- Returns: boolean
--
-- Returns true if the string contains only letters.
--
-- Example:
--
-- "abC".alpha?     # Returns true
-- "X1z".alpha?     # Returns false
function string_instance:alpha_question()
    if self._lua_string:match("^%a+$") then
        return object.__true
    else
        return object.__false
    end
end

-- Object: string instance
-- Call: string.alphanum?
-- Returns: boolean
--
-- Returns true if the string contains only letters and numbers.
--
-- Example:
--
-- "br47".alphanum?      # Returns true
-- "bl_nk".alphanum?     # Returns false
function string_instance:alphanum_question()
    if self._lua_string:match("^%w+$") then
        return object.__true
    else
        return object.__false
    end
end

-- Object: string instance
-- Call: string.numeric?
-- Returns: boolean
--
-- Returns true if the string only includes decimal digits and an optional
-- leading minus sign.
--
-- Example:
--
-- "five".numeric?      # Returns false
-- "-127".numeric?      # Returns true
function string_instance:numeric_question()
    if self._lua_string:match("^-?%d+$") then
        return object.__true
    else
        return object.__false
    end
end

-- Object: string instance
-- Call: string.blank?
-- Returns: boolean
--
-- Returns true if the string is empty or only contains whitespace characters.
--
-- Example:
--
-- "".blank?      # Returns true
-- "\n".blank?    # Returns true
function string_instance:blank_question()
    if self._lua_string:match("^%s*$") then
        return object.__true
    else
        return object.__false
    end
end

-- Object: string instance
-- Call: string.downcase
-- Returns: string
--
-- Create a new string with all letters downcased.
function string_instance:downcase()
    return base_string:new(string.lower(self._lua_string))
end

-- Object: string instance
-- Call: string.downcase!
-- Returns: self
--
-- Downcase all letters in the string,
function string_instance:downcase_bang()
    self._lua_string = string.lower(self._lua_string)
    return self
end

-- Object: string instance
-- Call: string.upcase
-- Returns: string
--
-- Return a new string with all letters changed to uppercase.
function string_instance:upcase()
    return base_string:new(string.upper(self._lua_string))
end

-- Object: string instance
-- Call: string.upcase!
-- Returns: self
--
-- Convert all letters in string to uppercase.
function string_instance:upcase_bang()
    self._lua_string = string.upper(self._lua_string)
    return self
end

-- Object: string instance
-- Call: string1 == string2
-- Returns: boolean
--
-- Compare the contents of two strings.
function string_instance:_equal_equal(rhs)
    if type(rhs) ~= "table" or rhs._lua_string == nil then
        return object.__false
    elseif self._lua_string == rhs._lua_string then
        return object.__true
    else
        return object.__false
    end
end

-- Object: string instance
-- Call: lhs <=> rhs
-- Returns: number
--
-- Compares two strings. Returns 1 if lhs is greater, -1 if rhs is greater,
-- and 0 if the two are equal.
function string_instance:_less_equal_greater(rhs)
    if type(rhs) ~= "table" or rhs._lua_string == nil then
        error(exception:new("Cannot compare string to " .. tostring(rhs)))
    end

    local lhs = self._lua_string
    rhs = rhs._lua_string

    if lhs > rhs then
        return 1
    elseif lhs < rhs then
        return -1
    elseif lhs == rhs then
        return 0
    else
        error("Error comparing strings")
    end
end

-- Object: string instance
-- Call: string1 + string2
-- Returns: string
--
-- Concatenates the two strings and creates a new string.
function string_instance:_plus(rhs)
    if type(rhs) ~= "table" or rhs._lua_string == nil then
        error("Cannot add string to non-string")
    end

    return self:new(self._lua_string .. rhs._lua_string)
end

-- Object: string instance
-- Call: string.include? substring
-- Call: string.include? regex
-- Returns: boolean
--
-- Returns true if the string includes the given substring or regular expression.
function string_instance:include_question(pattern)
    if type(pattern) == "table" then
        if pattern._lua_regex then
            if is_true(self:match(pattern)) then
                return object.__true
            else
                return object.__false
            end
        elseif pattern._lua_string then
            if string.find(self._lua_string, pattern._lua_string, 1, true) then
                return object.__true
            else
                return object.__false
            end
        end
    end

    error(exception:argument_error("string.include?", "string or regex", tostring(regx)))
end

-- Object: string instance
-- Call: string.match regex
-- Call: string.match regex, index
-- Returns: object or false
--
-- This method can be used to find substrings inside a string matching the
-- given regular expression. An optional start index can be provided.
--
-- If a match is found, an match object is
function string_instance:match(regx, start_index)
    if type(regx) ~= "table" or regx._lua_regex == nil then
        error(exception:argument_error("string.match", "regex", tostring(regx)))
    end

    if start_index then
        start_index = start_index + 1
    end

    return regx:match(self, start_index)
end

-- Object: string instance
-- Call: string.sub regex, replacement
-- Call: string.sub regex, replacement, limit
-- Returns: string
--
-- Returns a new string with instances of the given pattern replaced by the
-- provided replacement string.
--
-- Instead of a string, the replacement argument can be a function which will
-- be called with each match. The string returned by the function will be used
-- as the replacement.
--
-- A limit can be used to limit how many replacements are made.
function string_instance:sub(pattern, replacement, limit)
    local pattern_type
    if type(pattern) == "table" then
        if pattern._lua_string then
            pattern_type = "string"
            pattern = pattern._lua_string
        elseif pattern._lua_regex then
            pattern_type = "regex"
            pattern = pattern._lua_regex
        else
            error(exception:argument_error("string.sub", "regular expression", tostring(pattern)))
        end
    else
        error(exception:argument_error("string.sub", "regular expression", tostring(pattern)))
    end

    if is_callable(replacement) then
        local f = replacement
        replacement = function(s)
            local s = base_string:new(s)
            local r = f(s, s)
            if r._lua_string then
                return r._lua_string
            elseif r == object.__null then
                return nil
            else
                return r
            end
        end
    elseif type(replacement) == "table" then
        if replacement._lua_string then
            replacement = replacement._lua_string
        elseif replacement._lua_hash then
            local r = {}
            for k, v in pairs(replacement._lua_hash) do
                r[k._lua_string] = v._lua_string
            end
            replacement = r
        else
            error(exception:argument_error("string.sub", "string", tostring(replacement)))
        end
    else
        error(exception:argument_error("string.sub", "string", tostring(replacement)))
    end


    local ns
    if pattern_type == "regex" then
        ns = orex.gsub(self._lua_string, pattern, replacement, limit)
    else
        ns = self._lua_string:gsub(pattern, replacement, limit)
    end

    return base_string:new(ns)
end

-- Object: string instance
-- Call: string.sub! regex, replacement
-- Call: string.sub! regex, replacement, limit
-- Returns: self
--
-- Same as string.sub, but modifies the original string.
function string_instance:sub_bang(pattern, replacement, limit)
    if type(pattern) ~= "table" or not pattern._lua_regex then
        error(exception:argument_error("string.sub!", "regular expression", tostring(pattern)))
    elseif (type(replacement) == "table" and replacement._lua_string == nil) and not is_callable(replacement) then
        error(exception:argument_error("string.sub!", "string", tostring(replacement)))
    end

    self._lua_string = self:sub(pattern, replacement, limit)._lua_string

    return self
end

-- Object: string instance
-- Call: string.sub regex, replacement
-- Returns: string
--
-- Same as using string.sub with a limit of 1.
function string_instance:sub_underfirst(pattern, replacement)
    return self:sub(pattern, replacement, 1)
end

-- Object: string instance
-- Call: string.sub! regex, replacement
-- Returns: self
--
-- Same as using string.sub! with a limit of 1.
function string_instance:sub_underfirst_bang(pattern, replacement)
    return self:sub_bang(pattern, replacement, 1)
end

-- Object: string instance
-- Call: string * num
-- Returns: string
--
-- Create a new string with num copies of the original string.
function string_instance:_star(num)
    if type(num) ~= "number" then
        error(exception:argument_error("string.*", "number", tostring(num)))
    end

    return base_string:new(string.rep(self._lua_string, num))
end

-- Object: string instance
-- Call: string.find substring
-- Returns: number
--
-- Returns the index of the given substring inside the original string.
-- If no match is found, returns null.
function string_instance:find_underfirst(str)
    local s_index, e_index = self._lua_string:find(str._lua_string, 1, true)

    if s_index then
        return s_index - 1
    else
        return object.__null
    end
end

-- Object: string instance
-- Call: string.find_all substring
-- Returns: array
--
-- Returns an array of indexes for each match of the given substring
-- inside the original string.
--
-- If no matches are found, returns an empty array.
function string_instance:find_underall(str)
    local search_string = str._lua_string
    local current_index = 1
    local finds = {}

    while true do
        local s_index, e_index = self._lua_string:find(search_string, current_index, true)

        if s_index then
            table.insert(finds, s_index - 1)
            current_index = e_index + 1
        else
            break
        end
    end

    return array:new(finds)
end

-- Object: string instance
-- Call: string.get index
-- Call: string.get start, end
-- Returns: string
--
-- Retrieves a section of the string. If a single index is used, returns
-- at most one character. For indexes out of range, returns an empty string.
-- Negative indexes can be used to start from the end of the string.
--
-- While this method can be called literally, it is more common to use the
-- square bracket (`[]`) form.
--
-- Example:
--
-- "abc"[0]      # Returns "a"
-- "abc"[0, 1]   # Returns "ab"
-- "abc"[-1]     # Returns "c"
-- "abc"[-1, -2] # Returns "bc"
-- "abc"[-1 ,1]  # Returns "bc"
function string_instance:get(start_index, end_index)
    local len = #self._lua_string
    if end_index == nil then
        if start_index < 0 then
            start_index = len + start_index
        end

        local val = string.sub(self._lua_string, start_index + 1, start_index + 1)
        if val == nil then
            return object.__null
        else
            return base_string:new(val)
        end
    else
        if start_index < 0 then
            start_index = len + start_index
        end

        if end_index < 0 then
            end_index = len + end_index
        end

        if start_index < 0 then
            start_index = 0
        end

        if end_index < 0 then
            end_index = 0
        end

        if start_index > end_index then
            local temp = start_index
            start_index = end_index
            end_index = temp
        end

        if start_index >= len then
            return base_string:new()
        end

        if end_index > len then
            end_index = len
        end

        return base_string:new(string.sub(self._lua_string, start_index + 1, end_index + 1))
    end
end

-- Object: string instance
-- Call: string.reverse
-- Returns: string
--
-- Copy and reverse string.
function string_instance:reverse()
    return base_string:new(self._lua_string:reverse())
end

-- Object: string instance
-- Call: string.reverse!
-- Returns: self
--
-- Reverse string.
function string_instance:reverse_bang()
    self._lua_string = self._lua_string:reverse()
    return self
end

-- Object: string instance
-- Call: string.set index, character
-- Returns: self
--
-- Sets the given index in the string to the given character.
function string_instance:set(index, value)
    local len = #self._lua_string
    if index < 0 then
        index = len + index
    end

    if index > len then
        error(exception:new("Index " .. tostring(index) .. " is greater than length of string (" .. tostring(len) .. ")"))
    elseif index < 0 then
        error(exception:new("Index " .. tostring(index) .. " is outside the string"))
    elseif type(value) ~= "table" or value._lua_string == nil then
        error(exception:argument_error("string[]", "string", tostring(value)))
    elseif type(index) ~= "number" then
        error(exception:argument_error("string[]", "number", tostring(value)))
    end

    local original = self._lua_string
    local front = string.sub(original, 0, index)
    local back = string.sub(original, index + 2, -1)
    self._lua_string = front .. value._lua_string .. back

    return value
end

-- Object: string instance
-- Call: string.dice
-- Returns: array
--
-- Splits string into an array with each character as as single element.
--
-- Example:
--
-- "abc".dice == ["a" "b" "c"]
function string_instance:dice()
    return self:split("")
end

-- Object: string instance
-- Call: string.split separator
-- Returns: array
--
-- Splits the string into an array based on the given separator, which should be a string. If no separator is given, " " is assumed.
--
-- Example:
--
-- a = "hello, there"
-- a.split       #["hello,", "there"]
-- a.split ", "  #["hello", "there"]
-- a.split "z"   #["hello, there"]
function string_instance:split(sep)
    if sep == nil then
        sep = orex.new("\\s+")
    elseif type(sep) == "table" then
        if sep._lua_string then
            sep = orex.new(sep._lua_string, nil, nil, "ASIS")
        elseif sep._lua_regex then
            sep = sep._lua_regex
        end
    elseif type(sep) ~= "string" then
        error(exception:argument_error("string.split", "string or regex", tostring(sep)))
    end

    local result = {}
    local i = 1
    for value in orex.split(self._lua_string, sep) do
        if value and value ~= "" then
            result[i] = base_string:new(value)
            i = i + 1
        end
    end

    return array:new(result)
end

-- Object: string instance
-- Call: string << str
-- Returns: self
--
-- Concatenate a second string onto the current string. Modifies and returns
-- the current string.
--
-- Example:
--
-- a = "a"
-- a << "b"
-- a == "ab"
function string_instance:_less_less(value)
    if type(value) ~= "table" or value._lua_string == nil then
        error(exception:argument_error("string <<", "string", tostring(value)))
    end

    self._lua_string = self._lua_string .. value._lua_string

    return self
end

function string_instance:__hash()
    return self._lua_string
end

-- Object: string instance
-- Call: string.to_i
-- Call: string.to_i base
-- Returns: number
--
-- Interprets the given string as an integer. By default the string is
-- expected to be decimal representation.
function string_instance:to_underi(base)
    local n = tonumber(self._lua_string, base)
    if n then
        return math.floor(n)
    else
        return object.__null
    end
end

-- Object: string instance
-- Call: string.to_f
-- Returns: number
--
-- Interprets the given string as an number.
function string_instance:to_underf()
    local n = tonumber(self._lua_string)

    if n then
        return n
    else
        return object.__null
    end
end

-- Object: string instance
-- Call: string.to_byte
-- Returns: number or array
--
-- If string is a single character, returns the decimal value associated with that
-- character. If the string is longer than a single character, returns an array
-- of values.
--
-- Example:
--
-- "a".to_byte   # 97
-- "abc".to_byte # [97, 98, 99]
function string_instance:to_underbyte()
    if #self._lua_string == 1 then
        return self._lua_string:byte()
    else
        return array:new(self._lua_string:byte(1, #self._lua_string))
    end
end

--Symbols

symbol = object:new()

local si = object:new()
si:squish(string_instance)
si.chomp_bang = nil
si.strip_bang = nil
si.downcase_bang = nil
si.upcase_bang = nil
si.sub_bang = nil
si.sub_underfirst_bang = nil
si.set = nil
si._less_less = nil
si.reverse_bang = nil

symbol._prototype = si
si = nil

function symbol:new(s)
    if s == nil then
        s = ""
    end

    local ns = new_brat(self)
    ns._lua_string = s

    if type(s) == "string" then
        ns._lua_string = s
    elseif type(s) == "table" then
        if s._lua_string == nil then
            error("error")
        else
            ns._lua_string = s._lua_string
        end
    end

    return ns
end

--Regular expressions

local regex_instance = object:new()

local regex_match = object:new()
function regex_match._prototype:get(index)
    return self.matches:get(index)
end

function regex_match:init(start_pos, end_pos, full_match, matches)
    self.start_underpos = start_pos
    self.end_underpos = end_pos
    self.full_undermatch = full_match
    self.matches = matches

    return self
end

regex = object:new()

regex._prototype = regex_instance

function regex:new(string, options)
    if type(string) == "string" then
    elseif type(string) == "table" and string._lua_string ~= nil then
        string = string._lua_string
    else
        error(exception:argument_error("regex.new", "string", string))
    end

    if options == nil or type(options) == "string" then
    elseif type(options) == "table" and options._lua_string ~= nil then
        options = options._lua_string
    else
        error(exception:argument_error("regex.new", "string", string))
    end

    local nr = new_brat(self)

    nr._lua_regex = orex.new(string, options)
    nr._regex_string = string

    return nr
end

function regex_instance:regex_question()
    return object.__true
end

function regex_instance:to_unders()
    return base_string:new("/" .. self._regex_string .. "/")
end

function regex_instance:match(string, start_index)
    if type(string) == "string" then
    elseif type(string) == "table" and string._lua_string ~= nil then
        string = string._lua_string
    else
        error(exception:argument_error("regex.match", "string", string))
    end

    local result = { self._lua_regex:find(string, start_index) }

    if #result == 0 then
        return object.__false
    else
        return regex:_make_result(string, result)
    end
end

regex_instance._tilde = regex_instance.match

function regex:_make_result(str, result)
    full_match = base_string:new(str:sub(result[1], result[2]))

    local r = { full_match }

    if result[3] then
        local k = 3
        local v
        while k <= #result do
            v = result[k]
            if type(v) == "string" then
                r[k - 1] = base_string:new(v)
            elseif v == false then
                r[k - 1] = object.__false
            end

            k = k + 1
        end
    end

    return regex_match:new(result[1] - 1, result[2] - 1, full_match, array:new(r))
end

--Exception objects

local exception_instance = object:new()

exception = object:new()
exception._prototype = exception_instance

function exception:new(message, error_type)
    if message == nil then
        message = "Unspecified exception"
    end

    if error_type == nil then
        error_type = "standard error"
    end

    local msg = base_string:new(message)
    local stack_trace = base_string:new(debug.traceback(message, 2))
    error_type = base_string:new(error_type)

    local e = new_brat(self)

    e.error_undermessage = function() return msg end
    e.stack_undertrace = function()
        return stack_trace
    end
    e.type = function() return error_type end
    return e
end

function exception._handler(exp)
    if type(exp) == "table" and exp.stack_undertrace then
        print(exp:stack_undertrace())
    else
        print(debug.traceback(exp, 3))
    end
    return nil
end

function exception_instance:to_unders()
    if self.error_undermessage ~= nil then
        return self.error_undermessage()
    else
        return base_string:new("Generic error")
    end
end

function exception:argument_undererror(method, expected, given)
    return self:new(
    "Argument error: " ..
    tostring(method) .. " expected " .. tostring(expected) .. " argument(s) but was given " .. tostring(given) .. ".",
        "argument error")
end

exception.argument_error = exception.argument_undererror

function exception:method_undererror(object, method_name)
    return self:new("Method error: " .. tostring(object) .. " has no method called '" .. tostring(method_name) .. "'.",
        "method error")
end

exception.method_error = exception.method_undererror

function exception:null_undererror(name, cannot)
    return self:new("Null error: " .. tostring(name) .. " is null, cannot " .. tostring(cannot) .. ".", "null error")
end

exception.null_error = exception.null_undererror

function exception:name_undererror(name)
    return self:new("Name error: No such method or local variable '" .. tostring(name) .. "'.", "name error")
end

exception.name_error = exception.name_undererror

-- Functions

local brat_function_instance = object:new()
brat_function = object:new()
brat_function._prototype = brat_function_instance

function brat_function:new(func)
    local nb = new_brat(self)
    nb._func = func

    return nb
end

function brat_function_instance:object_question()
    return object.__false
end

function brat_function_instance:function_question()
    return object.__true
end

--Small object addendum which had to wait for other stuff to be defined

local load_path = array:new({ base_string:new('.'), base_string:new(program_path .. 'stdlib') })

-- Object: object
-- Call: load_path
-- Returns: array
--
-- The path used to search for files to load when using include() or import().
function object:load_underpath()
    return load_path
end

local _compiling = array:new()

--Searchs load paths for a file with name + .brat and compiles it to Lua
function object:_compile(name)
    local check_and_compile = function(self, path)
        if type(path) == "table" and path._lua_string then
            path = path._lua_string
        end

        if type(path) ~= "string" then
            error(exception:argument_error("load_path", "string", path))
        end

        local file_name = path .. "/" .. name
        local lua_modified = lfs.attributes(file_name .. ".lua", "modification")
        local brat_modified = lfs.attributes(file_name .. ".brat", "modification")

        if brat_modified then
            if lua_modified == nil or brat_modified > lua_modified then
                if not is_true(_compiling:include_question(file_name)) then
                    _compiling:_less_less(file_name)
                    compile_file(file_name)
                    _compiling:delete_underfirst(file_name)
                end
            end
        end
    end

    local load_paths = object:load_underpath()

    if type(load_paths.each) ~= "function" then
        error(exception:new("include expects enumerable for load_path"))
    end

    load_paths:each(check_and_compile)
end

function object:argv()
    if object._argv == nil then
        local f = function(self, a)
            return base_string:new(a)
        end
        object._argv = array:new(arg):map_bang(f)
    end

    return object._argv
end