Odin » Forums » Appending dynamic arrays in maps
bpunsky
4 posts

None

#12315 Appending dynamic arrays in maps
2 months, 3 weeks ago Edited by on June 30, 2017, 12:50 p.m. Reason: grammar

I was testing some ideas in Odin, namely appending dynamic arrays within maps, and came across a problem.

1
2
3
4
5
foo: map[string][dynamic]int;

foo["bar"] = make([dynamic]int, 0);

append(foo["bar"], 1, 2, 3);


Obviously this throws an error, stating that append can only be used on addressable values. However, the available workarounds seem rather unattractive - copying out the array, appending it, and then copying it back in, or letting the map value be a pointer to int. This is just a test - in a real use case, I wouldn't want to take the hit on either of those. Is there some function or other mechanism that can get the pointer to the map value? For example:

1
append(maps.findptr(foo, "bar"), 1, 2, 3);


If not, do the inner workings of map allow such a thing to be implemented reasonably? Even if the address of the value could change, making the pointer dangerous, it would be useful in cases like this where you can be reasonably sure that the inner state of the map won't change while you're using the pointer.

None
gingerBill
Ginger Bill
212 posts
2 projects

I am ginger thus have no soul.

#12329 Appending dynamic arrays in maps
2 months, 3 weeks ago

You could just copy that value, modify that, then update it. That should technically work.

However, I could see what I can do with that particular case.
bpunsky
4 posts

None

#12333 Appending dynamic arrays in maps
2 months, 3 weeks ago Edited by on July 3, 2017, 12:25 a.m. Reason: why is there an extra linebreak after code blocks?

Cool, thanks. I also realize now that the cost of copying the value in most cases is pretty minimal - my example of an int was dumb, what I was really using was dynamic arrays, but 40 bytes... not a big deal. Still, with large structs as a value, I could see this being troublesome.

The language is surprisingly solid for being so new and unfinished, but playing around I've noticed a few things that might be worth bringing to your attention. First, type-parameterized functions don't seem to import properly - I had a simple function:

1
2
3
typename :: proc(T: type) -> string {
    return fmt.aprint(type_info(T));
}

and it worked while in the main file, but when I moved it out to another file and imported that it gave me an error that the name wasn't defined. Other functions in that file were all good, the type parameterization seemed to be key.

Another thing is that there doesn't currently seem to be any built-in way to remove a value from a dynamic array. I've solved that problem for myself, but the solution is less than ideal - although once you have implicit type parameters, it would be much cleaner to implement:

1
2
3
4
5
6
7
8
9
remove :: proc(T: type, array: ^[dynamic]T, index: int) #inline {
    if len(array) > 0 && len(array) > index {
        copy(array^[index..<len(array)-1], array^[index+1..]);
        
        tmp := transmute(raw.DynamicArray, array^);
        tmp.len -= 1;
        array^ = transmute([dynamic]T, tmp);
    }
}

Last, a problem I find utterly mystifying, that maybe has to do with bypassing some Windows initialization stuff in the runtime - reading from the console is impossible. Since there's no standard library way to read console input, I tried rolling my own. Using os.read with os.stdin, the function doesn't return when enter is pressed. If I hit ctrl-C, it works just fine and reads the input, but of course exits the program. I tried importing ReadConsoleA from kernel32.lib, and the same thing happened. Bizarre. Actually, I just tried again to verify and os.read is returning 0 bytes read, but ReadConsoleA is still working alright (not sure what changed...). Here's the full file, sorry for the length:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import "fmt.odin";
import "raw.odin";
import "os.odin";
import win32 "sys/windows.odin";

foreign_system_library (
    "kernel32.lib" when ODIN_OS == "windows";
)

foreign kernel32 {
    _read_console    :: proc(h: win32.Handle, buf: rawptr, to_read: u32, bytes_read: ^i32, input_control: rawptr) -> win32.Bool #cc_std #link_name "ReadConsoleA" ---; 
    set_console_mode :: proc(h: win32.Handle, mode: u32) -> win32.Bool #cc_std #link_name "SetConsoleMode" ---; 
}

read_console :: proc(handle: os.Handle, data: []u8) -> (int, os.Errno) #inline {
    if len(data) == 0 {
        return 0, os.ERROR_NONE;
    }

    single_read_length: i32;
    total_read: i64;
    length := i64(len(data));

    for total_read < length {
        remaining := length - total_read;
        to_read: u32;
        MAX :: 1<<32-1;
        if remaining <= MAX {
            to_read = u32(remaining);
        } else {
            to_read = MAX;
        }

        e := _read_console(win32.Handle(handle), &data[total_read], to_read, &single_read_length, nil);
        if single_read_length <= 0 || e == win32.FALSE {
            err := win32.get_last_error();
            return int(total_read), os.Errno(err);
        }
        total_read += i64(single_read_length);
    }

    return int(total_read), os.ERROR_NONE;
}

readline :: proc(max_bytes: int = 16) -> string {
    line := raw.String {
        data = ^u8(alloc(max_bytes)),
        len  = 0,
    };

    if bytes, err := read_console(os.stdin, slice_ptr(line.data, max_bytes)); err != os.ERROR_NONE || bytes == 0 {
        for i in line.len..line.len+bytes{
            char := (line.data + i)^;
            if char == '\r' || char == '\n' {
                bytes = i;
                break;
            }
        }

        line.len += bytes;
    } else {
        if err != os.ERROR_NONE {
            fmt.println("Error:", err);
        }
    }

    return transmute(string, line);
}

main :: proc() {
    for {
        input := readline();
        defer free(input);

        match input {
        case "help":
            fmt.println("There is no help for you.");
        case "echo":
            fmt.println("Echo-cho-o-o");
        case "exit":
            break;
        case:
            fmt.printf("{%s}\n", input);
        }
    }
}

The read_console function is basically copy and pasted from os_windows.odin's read, but I fixed what seems to be a bug - line 37 above, in os.read you were returning Errno(e) instead of Errno(err).

Also, I find it kind of strange that after pressing ctrl-C, the program continues, gets the input, prints it, and then exits rather than continuing the for loop in main. I would think it would either exit immediately, or finish executing.

EDIT: Oops, the reason os.read doesn't work is because line 51 is expecting err != os.ERROR_NONE instead of err == os.ERROR_NONE. With ReadConsoleA, ctrl-C returns os.ERROR_OPERATION_ABORTED, with ReadFile that doesn't happen. Still though, the rest is valid.

None
gingerBill
Ginger Bill
212 posts
2 projects

I am ginger thus have no soul.

#12356 Appending dynamic arrays in maps
2 months, 2 weeks ago

Thank you.

That is a bug and thank you for telling me. I will try to fix this right away.

Removing an element from an array is not built in because I want the user to decide how they want the do it e.g. shift the results downwards or take the last element and replace. Implicit parametric polymorphic procedures are in development but not yet advertised. So this `remove` procedure would become easier to use.

The standard library is nowhere near complete yet which may explain why a lot of it incomplete.


- Bill
bpunsky
4 posts

None

#12358 Appending dynamic arrays in maps
2 months, 2 weeks ago Edited by on July 5, 2017, 3:40 a.m.

I think that's a good call on the array removal, and I did figure out that you can already use implicit type parameters in the latest builds. It already works great in simple cases, but seems prone to error when used as part of a larger system - I'm sure that'll be fixed before the next demo, though.

Also, what do you think about providing default array removal functions - like "remove" and "remove_unordered," for example? It's not a big deal, it's easy to implement, but it's also handy to just be able to fire 'n' forget. Speaking of which, the latest update making most of the _preload.odin stuff user-side is awesome. Needless to say I'm following Odin pretty closely - I think it's the answer to C and C++ being crap.

None