Download
336 lines 13.6 KiBMoonScriptModified 2022-10-11 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
-- Custom implementations of list methods
import FnType, OptionalType, ListType, StructType, NilType, String, Bool, Int32, Int, Range, Pointer from require 'types'
import log, viz, node_assert, node_error, each_tag, context_err from require 'util'

types = {
    get: => FnType({@,Int}, OptionalType(@item_type), {"list","index"})
    get_or_fail: => FnType({@,Int}, @item_type, {"list","index"})
    data_pointer: => FnType({@}, Pointer, {"list"})
    range: => FnType({@,Range}, @, {"list","range"})
    copy: => FnType({@}, @, {"list"})
    equal_items: => FnType({@,@}, Bool, {"list","other"})
    insert: => FnType({@,@item_type,OptionalType(Int)}, NilType, {"list","item","at"})
    insert_all: => FnType({@,@,OptionalType(Int)}, NilType, {"list","items","at"})
    remove: => FnType({@,OptionalType(Int)}, NilType, {"list","at"})
    remove_range: => FnType({@,Range}, NilType, {"list","range"})
    find: => FnType({@,@item_type,OptionalType(Int)}, OptionalType(Int), {"list","item","after"})
    count: => FnType({@,@item_type,OptionalType(Int)}, Int, {"list","item","after"})
    reverse: => FnType({@}, NilType, {"list"})
    reversed: => FnType({@}, @, {"list"})
    wrapped: => FnType({@,Int}, Int, {"list","index"})
    join: => FnType({@,OptionalType(String)}, String, {"list","glue"})
    sort: =>
        ptr = StructType("", {{name:1, type:@item_type}})
        FnType({@,FnType({ptr,ptr},Int32),OptionalType(Bool)}, NilType, {"list","by","reversed"})
    sorted: => FnType({@,OptionalType(FnType(@item_type,@item_type,Int32)),Bool}, @, {"list","by","reversed"})
    get_random: => FnType({@}, @item_type, {"list"})
}

get = (code, use_failure)=>
    assert @__type, "No type"
    local list,index
    if @__tag == "IndexedTerm"
        list,index = @value,@index
    elseif @__tag == "FnCall"
        list,index = @fn.value,@[1]
    assert list and list.__type\is_a(ListType), "WTF: #{@__type}"
    assert index, "WTF: #{viz @}"

    list_reg,index_reg = code\add_values list, index
    len = code\fresh_locals "len"
    code\add "#{len} =l loadl #{list_reg}\n"

    -- Bounds check:
    src,index_too_low,index_too_high,bad_index,item = code\fresh_locals "src","index_too_low","index_too_high","bad_index","item"
    index_error,index_ok,done = code\fresh_labels "index_error","index_ok","done"
    code\add "#{index_too_low} =w csltl #{index_reg}, 1\n"
    code\add "#{index_too_high} =w csgtl #{index_reg}, #{len}\n"
    code\add "#{bad_index} =w or #{index_too_low}, #{index_too_high}\n"
    code\add "jnz #{bad_index}, #{index_error}, #{index_ok}\n"
    code\add_label index_error
    if use_failure
        code\add "call $dprintf(l 2, l #{code\get_string_reg(context_err(index, "Invalid index: %ld (list size = %ld)", 2).."\n", "index_error")}, l #{index_reg}, l #{len})\n"
        code\add "call $_exit(l 1)\n"
        code\add "jmp #{index_error}\n"
    else
        code\set_nil list.__type.item_type, item
        code\add "jmp #{done}\n"
    code\add_label index_ok

    items = code\fresh_local "items"
    code\add "#{items} =l add #{list_reg}, 8\n"
    code\add "#{items} =l loadl #{items}\n"
    offset,item_loc = code\fresh_locals "offset","item_location"
    code\add "#{offset} =l sub #{index_reg}, 1\n"
    item_type = list.__type.item_type
    code\add "#{offset} =l mul #{offset}, #{item_type.bytes}\n"
    code\add "#{item_loc} =l add #{items}, #{offset}\n"
    if item_type.base_type == "d" or item_type.base_type == "s"
        tmp = code\fresh_local "list.item.as_int"
        int_type = item_type.base_type == "d" and "l" or "w"
        code\add "#{tmp} =#{int_type} load#{int_type} #{item_loc}\n"
        code\add "#{item} =d cast #{tmp}\n"
    else
        code\add "#{item} =#{item_type.base_type} #{item_type.load} #{item_loc}\n"

    if not use_failure
        code\add_label done
        
    return item

methods = {
    get: (code)=> get(@, code, false)
    get_or_fail: (code)=> get(@, code, true)

    data_pointer: (code)=>
        list_reg = code\add_value @[1]
        data = code\fresh_locals "data"
        code\add "#{data} =l add list_reg, 8\n"
        return data

    range: (code)=>
        list,range = if @__tag == "IndexedTerm"
            @value,@index
        else
            @[1],@[2]

        t = list.__type
        list_reg,range_reg = code\add_values list,range
        use_aliasing = "1" -- TODO: re-enable when it's safe to do so
        slice = code\fresh_local "slice"
        code\add "#{slice} =l call $list_slice(l #{list_reg}, l #{range_reg}, l #{t.item_type.bytes}, w #{use_aliasing})\n"
        return slice

    insert: (code)=>
        list = node_assert @fn.value, @, "No list provided"
        local index, item
        positional = {}
        for arg in *@
            if arg.__tag == "KeywordArg"
                if arg.name[0] == "at"
                    index = arg.value
                elseif arg.name[0] == "item"
                    item = arg.value
                else
                    node_error arg.name, "Not a valid keyword argument, expected `at=` or `item=`"
            else
                table.insert positional, arg
        if not item
            item = table.remove(positional,1)
        if not index
            index = table.remove(positional,1)
        node_assert item, @, "No item provided to insert"

        list_t = list.__type
        list_reg, item_reg = code\add_values list, item
        item_t = item.__type
        node_assert item_t\is_a(list_t.item_type), item, "Cannot put a #{item_t} in a list of type #{list_t}"

        item64 = if item_t.base_type == "s" or item_t.base_type == "d"
            reg = code\fresh_local "item64"
            code\add "#{reg} =l cast #{item_reg}\n"
            reg
        elseif item_t.bytes < 8
            reg = code\fresh_local "item64"
            "#{reg} =l extu#{item_t.abi_type} #{item_reg}\n"
            reg
        else
            item_reg

        index_reg = if index
            code\add_value index
        else
            "#{Int.nil_value}"

        err_fmt = if index
            code\get_string_reg(context_err(index, "Invalid list index: %ld", 2).."\n", "index_error")
        else
            code\get_string_reg("", "empty")
        code\add "call $list_insert(l #{list_reg}, l #{item_t.bytes}, l #{index_reg}, l #{item64}, l #{err_fmt})\n"
        return "0"

    insert_all: (code)=>
        list = node_assert @fn.value, @, "No list provided"
        local index, items
        positional = {}
        for arg in *@
            if arg.__tag == "KeywordArg"
                if arg.name[0] == "at"
                    index = arg.value
                elseif arg.name[0] == "items"
                    items = arg.value
                else
                    node_error arg.name, "Not a valid keyword argument, expected `at=` or `items=`"
            else
                table.insert positional, arg
        if not items
            items = table.remove(positional,1)
        if not index
            index = table.remove(positional,1)
        node_assert items, @, "No items provided to insert"

        list_t = list.__type
        list_reg, items_reg = code\add_values(list, items)

        index_reg = if index
            code\add_value index
        else
            "#{Int.nil_value}"

        items_t = items.__type
        node_assert items_t == list_t, @, "Cannot put item from #{items_t} in a list of type #{list_t}"
        err_fmt = code\get_string_reg(context_err(@, "Invalid list index: %ld", 2).."\n", "index_error")
        code\add "call $list_insert_all(l #{list_reg}, l #{list_t.item_type.bytes}, l #{index_reg}, l #{items_reg}, l #{err_fmt})\n"
        return "0"

    equal_items: (code)=>
        list = node_assert @fn.value, @, "No list provided"
        other = node_assert @[1], @, "No items provided for comparison"
        list_t = list.__type
        other_t = other.__type
        node_assert other_t\is_a(list_t), item, "Cannot compare a list of #{list_t.item_type} with a list of type #{other_t.item_type}"
        item_type = list_t.item_type
        if item_type.base_type == "s" or item_type.base_type == "d"
            node_error @, "equal_items() is not yet supported for floating point values. You should avoid doing equality checks on floating point values because of precision and NaN issues."
        list_reg, other_reg = code\add_values(list, other)
        eq_reg = code\fresh_locals "equal"
        code\add "#{eq_reg} =w call $list_equal(l #{list_reg}, l #{other_reg}, l #{item_type.bytes})\n"
        return eq_reg

    copy: (code)=>
        list = node_assert @fn.value, @, "No list provided"
        list_t = list.__type
        list_reg = code\add_value list
        copy_reg = code\fresh_locals "copy"
        code\add "#{copy_reg} =l call $list_copy(l #{list_reg}, l #{list_t.item_type.bytes})\n"
        return copy_reg

    remove: (code)=>
        list = node_assert @fn.value, @, "No list provided"
        local first,last
        positional = {}
        for arg in *@
            if arg.__tag == "KeywordArg"
                if arg.name[0] == "at"
                    first = arg.value
                elseif arg.name[0] == "through"
                    last = arg.value
                else
                    node_error arg.name, "Not a valid keyword argument, expected `at=`"
            else
                table.insert positional, arg
        if not first
            first = table.remove(positional,1)
        if not last
            last = table.remove(positional,1)

        list_reg = code\add_value(list)
        first_reg = if first
            code\add_value first
        else
            "#{Int.nil_value}"

        last_reg = if last
            code\add_value last
        else
            "#{Int.nil_value}"

        item_t = list.__type.item_type
        err_fmt = if first
            code\get_string_reg(context_err(@, "Invalid removal range: %ld..%ld", 2).."\n", "index_error")
        else
            code\get_string_reg("", "empty")
        code\add "call $list_remove(l #{list_reg}, l #{item_t.bytes}, l #{first_reg}, l #{last_reg}, l #{err_fmt})\n"
        return "0"

    get_random: (code)=>
        list = if @__tag == "IndexedTerm"
            @value
        elseif @__tag == "FnCall"
            @fn.value
        list_reg = code\add_value list
        len,items = code\fresh_locals "len","items"
        code\add "#{len} =l loadl #{list_reg}\n"
        empty_label,ok_label,done = code\fresh_labels "is_empty","has_items","done"
        code\add "jnz #{len}, #{ok_label}, #{empty_label}\n"

        code\add_label empty_label
        code\add "call $dprintf(l 2, l #{code\get_string_reg(context_err(list, "List is unexpectedly empty", 2).."\n", "index_error")})\n"
        code\add "call $_exit(l 1)\n"
        code\add "jmp #{done}\n"

        code\add_label ok_label

        offset,item_loc,item = code\fresh_locals "offset","item_location","item"
        code\add "#{offset} =l call $random_range(l 0, l #{len})\n"
        code\add "#{items} =l add #{list_reg}, 8\n"
        code\add "#{items} =l loadl #{items}\n"
        item_type = list.__type.item_type
        code\add "#{offset} =l mul #{offset}, #{item_type.bytes}\n"
        code\add "#{item_loc} =l add #{items}, #{offset}\n"
        if item_type.base_type == "d" or item_type.base_type == "s"
            tmp = code\fresh_local "list.item.as_int"
            int_type = item_type.base_type == "d" and "l" or "w"
            code\add "#{tmp} =#{int_type} load#{int_type} #{item_loc}\n"
            code\add "#{item} =d cast #{tmp}\n"
        else
            code\add "#{item} =#{item_type.base_type} #{item_type.load} #{item_loc}\n"
        code\add "jmp #{done}\n"

        code\add_label done

        return item

    sort: (code)=>
        list = node_assert @fn.value, @, "No list provided"
        list_reg = code\add_value list
        by = node_assert @[1], @, "No comparison function provided"
        by_reg = code\add_value by
        len = code\fresh_local "len"
        code\add "#{len} =l loadl #{list_reg}\n"
        list_t = list.__type
        items_reg = code\fresh_local "items"
        code\add "#{items_reg} =l add #{list_reg}, 8\n"
        code\add "#{items_reg} =l loadl #{items_reg}\n"
        code\add "call $qsort(l #{items_reg}, l #{len}, l #{list_t.item_type.bytes}, l #{by_reg})\n"
        return "0"

    sorted: (code)=> error "Not implemented"
    remove_range: => error "Not implemented"
    find: => error "Not implemented"
    count: => error "Not implemented"
    join: (code)=>
        list = @fn.value
        glue = @[1]
        t = list.__type
        list_reg = code\add_value list
        buf_reg = code\fresh_local "buf"
        code\add "#{buf_reg} =l copy 0\n"
        make_between = if glue
            glue_reg = code\add_value glue
            -- NOTE: CORD_cat() works with 0 as equivalent to "", so no need to check
            -- for a nil value
            -> "#{buf_reg} =l call $CORD_cat(l #{buf_reg}, l #{glue_reg})\n"
        else
            nil

        item_reg = code\fresh_local "item"
        code\add_for_loop({
            type: t, iter_reg:list_reg, val_reg:item_reg,
            make_body: ->
                item_str = code\fresh_local "item.string"
                fn = code\get_tostring_fn t.item_type, scope
                return code\new_code "#{item_str} =l call #{fn}(#{t.item_type.base_type} #{item_reg}, l 0)\n",
                    "#{buf_reg} =l call $CORD_cat(l #{buf_reg}, l #{item_str})\n"
            make_between: make_between
        })
        code\add "#{buf_reg} =l call $CORD_to_const_char_star(l #{buf_reg})\n"
        code\add "#{buf_reg} =l call $bl_string(l #{buf_reg})\n"
        return buf_reg

    reverse: => error "Not implemented"
    reversed: => error "Not implemented"
    wrapped: => error "Not implemented"
}

return {:methods, :types}