diff --git a/source/ast/ast.hon b/source/ast/ast.hon new file mode 100644 index 0000000..e1392b0 --- /dev/null +++ b/source/ast/ast.hon @@ -0,0 +1 @@ +# todo: move ast stuff from parser module to here diff --git a/source/lexer/lexer.hon b/source/lexer/lexer.hon index 48ab535..1728f55 100644 --- a/source/lexer/lexer.hon +++ b/source/lexer/lexer.hon @@ -1,29 +1,52 @@ import "@std" import "@std/mem" -import "@std/arraylist" import "@std/strmap" +import "@std/enums/enummap" +import "@std/arraylist" +import "@std/debug" import "@source/strpool" +ErrorCode :: enum { + invalid_character, + float_must_end_with_digit, +} + +ErrorDetails :: struct { + name []u8 + message []u8 +} + +error_msg_map std.EnumMap(ErrorCode, ErrorDetails) :: enummap.init({ + invalid_character = ErrorDetails { + name = "L0", + message = "invalid character", + }, + float_must_end_with_digit = ErrorDetails { + name = "L1", + message = "float must end with a digit", + }, +}) + keywords std.StringMap(TokenKind) :: strmap.init([ - { "proc", .proc }, - { "return", .return }, - { "if", .if }, - { "for", .for }, - { "else", .else }, - { "while", .while }, + { "proc", .proc }, + { "return", .return }, + { "if", .if }, + { "for", .for }, + { "else", .else }, + { "while", .while }, ]) -TokenIndex :: alias usize +TokenId :: distinct u32 -ScanDiagnostic :: struct { - token TokenIndex - message []u8 +Diagnostic :: struct { + code ErrorCode + token TokenId } State :: struct { tokens std.ArrayList(Token) - diagnostics std.ArrayList(ScanDiagnostic) + diagnostics std.ArrayList(Diagnostic) } init proc(allocator mem.Allocator) State { @@ -39,8 +62,8 @@ deinit proc(state @mut State) void { } scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternError) { - tokens :: &state.tokens - diagnostics :: &state.diagnostics + tokens :: &state.tokens + diagnostics :: &state.diagnostics cursor usize = 0 while cursor < input.len { @@ -48,9 +71,9 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE # whitespace if char == '\n' { - try arraylist.append(tokens, Token{ kind = .newline, start = cursor }) - cursor += 1 - continue + try add_token(tokens, Token{ kind = .newline, start = cursor }) + cursor += 1 + continue } else if is_whitespace(char) { cursor += 1 continue @@ -58,10 +81,8 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE # comments if char == '#' { - # fixme(brolang): while currently requires curly braces but this should be legal - #while (cursor < input.len and input[cursor] != '\n') cursor += 1 - - while cursor < input.len and input[cursor] != '\n' : cursor += 1 {} + while (cursor < input.len and input[cursor] != '\n') cursor += 1 + cursor += 1 # also skip newline continue } @@ -75,7 +96,7 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE is_alpha(input[cursor]) or is_digit(input[cursor]) or input[cursor] == '_' - )) : cursor += 1 {} + )) cursor += 1 kind :: strmap.get(&keywords, input[start..cursor]) orelse .ident @@ -83,9 +104,9 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE str_id :: if (kind == .ident) try strpool.intern(&strpool.strings, input[start..cursor]) else - strpool.NoId + strpool.NO_ID - try arraylist.append(tokens, Token{ + try add_token(tokens, Token{ kind = kind, start = start, str_id = str_id @@ -99,7 +120,7 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE has_decimal bool = false # scan integer part - while cursor < input.len and is_digit(input[cursor]) : cursor += 1 {} + while (cursor < input.len and is_digit(input[cursor])) cursor += 1 # check for decimal point if cursor < input.len and input[cursor] == '.' { @@ -109,22 +130,17 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE # assert that decimals follow the decimal point if has_decimal and (cursor >= input.len or !is_digit(input[cursor])) { - token :: tokens.items.len - try arraylist.append(tokens, Token{ kind = .invalid, start = start }) - try arraylist.append(diagnostics, ScanDiagnostic{ - token = token, - message = "float must end with a digit", - }) - continue + token :: token_id(tokens.items.len) + try arraylist.append(diagnostics, Diagnostic{ token = token, code = .float_must_end_with_digit }) + try add_token(tokens, Token{ kind = .invalid, start = start }) + continue } # scan decimal part - while cursor < input.len and is_digit(input[cursor]) : cursor += 1 {} + while (cursor < input.len and is_digit(input[cursor])) cursor += 1 - if (has_decimal) - try arraylist.append(tokens, Token{ kind = .float, start = start }) - else - try arraylist.append(tokens, Token{ kind = .int, start = start }) + kind :: if (has_decimal) .float else .int + try add_token(tokens, Token{ kind = kind, start = start }) continue } @@ -135,15 +151,15 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE cursor += 1 while cursor < input.len and input[cursor] != '"' and input[cursor] != '\n' : cursor += 1 { - # ignore escaped characters - if (input[cursor] == '\\' and cursor + 1 < input.len) cursor += 1 + # ignore escaped characters + if (input[cursor] == '\\' and cursor + 1 < input.len) cursor += 1 } if cursor < input.len and input[cursor] == '"' { cursor += 1 - try arraylist.append(tokens, Token{ kind = .string, start = start }) + try add_token(tokens, Token{ kind = .string, start = start }) } else { - try arraylist.append(tokens, Token{ kind = .invalid, start = start }) + try add_token(tokens, Token{ kind = .invalid, start = start }) } continue @@ -151,51 +167,95 @@ scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternE # mutable assignment if char == '=' { - try arraylist.append(tokens, Token{ kind = .equal, start = cursor }) + try add_token(tokens, Token{ kind = .equal, start = cursor }) cursor += 1 continue } - # immutable assignment - if cursor + 1 < input.len and char == ':' and input[cursor + 1] == ':' { - try arraylist.append(tokens, Token{ kind = .double_colon, start = cursor }) - cursor += 2 + # immutable assignment or single colon + if char == ':' { + if cursor + 1 < input.len and input[cursor + 1] == ':' { + try add_token(tokens, Token{ kind = .double_colon, start = cursor }) + cursor += 2 + continue + } + + try add_token(tokens, Token{ kind = .colon, start = cursor }) + cursor += 1 continue } # parentheses if char == '(' { - try arraylist.append(tokens, Token{ kind = .open_paren, start = cursor }) + try add_token(tokens, Token{ kind = .open_paren, start = cursor }) cursor += 1 continue } else if char == ')' { - try arraylist.append(tokens, Token{ kind = .close_paren, start = cursor }) + try add_token(tokens, Token{ kind = .close_paren, start = cursor }) cursor += 1 continue } # curly braces if char == '{' { - try arraylist.append(tokens, Token{ kind = .open_curly, start = cursor }) + try add_token(tokens, Token{ kind = .open_curly, start = cursor }) cursor += 1 continue } else if char == '}' { - try arraylist.append(tokens, Token{ kind = .close_curly, start = cursor }) + try add_token(tokens, Token{ kind = .close_curly, start = cursor }) cursor += 1 continue } # invalid character - token :: tokens.items.len - try arraylist.append(tokens, Token{ kind = .invalid, start = cursor }) - try arraylist.append(diagnostics, ScanDiagnostic{ - token = token, - message = "invalid character", - }) + token :: token_id(tokens.items.len) + try arraylist.append(diagnostics, Diagnostic{ token = token, code = .invalid_character }) + try add_token(tokens, Token{ kind = .invalid, start = cursor }) cursor += 1 } - try arraylist.append(tokens, Token{ kind = .eof, start = cursor }) + try add_token(tokens, Token{ kind = .eof, start = cursor }) +} + +#! returns the cursor position after scanning a number. +scan_number proc(start usize, input []u8) usize { + debug.assert(is_digit(input[start])) + + has_decimal bool = false + cursor usize = start + + # scan integer part + while cursor < input.len and is_digit(input[cursor]) : cursor += 1 {} + + # check for decimal point + if cursor < input.len and input[cursor] == '.' { + has_decimal = true + cursor += 1 + } + + # assert that decimals follow the decimal point + debug.assert(!(has_decimal and (cursor >= input.len or !is_digit(input[cursor])))) + + # scan decimal part + while cursor < input.len and is_digit(input[cursor]) : cursor += 1 {} + + return cursor +} + +#! returns the cursor position after scanning a string. +scan_string proc(start usize, input []u8) usize { + debug.assert(input[start] == '"') + + cursor usize = start + 1 + while cursor < input.len and input[cursor] != '"' and input[cursor] != '\n' : cursor += 1 { + # ignore escaped characters + if (input[cursor] == '\\' and cursor + 1 < input.len) cursor += 1 + } + + debug.assert(cursor < input.len and input[cursor] == '"') + cursor += 1 + + return cursor } hide is_whitespace proc(char u8) bool { @@ -215,3 +275,13 @@ hide is_digit proc(char u8) bool { else: false } } + +hide add_token proc(tokens @mut std.ArrayList(Token), token Token) void ! mem.AllocError { + _ = token_id(tokens.items.len) + try arraylist.append(tokens, token) +} + +hide token_id proc(idx uint) TokenId { + debug.assert(u64(idx) < u64(maxval!(TokenId))) + return TokenId(idx) +} diff --git a/source/lexer/lexer.test.hon b/source/lexer/lexer.test.hon index 6024927..4a0181b 100644 --- a/source/lexer/lexer.test.hon +++ b/source/lexer/lexer.test.hon @@ -3,20 +3,19 @@ import "@std/mem" import "@std/testing" handles_keywords_identifiers_and_error_progress test { - strpool.strings = strpool.init(mem.c_allocator) - defer strpool.deinit(&strpool.strings) + strpool.strings = strpool.init(mem.c_allocator) + defer strpool.deinit(&strpool.strings) - state State = init(mem.c_allocator) - defer deinit(&state) - try scan(&state, "if name # comment\n@1.") + state State = init(mem.c_allocator) + defer deinit(&state) + try scan(&state, "if name # comment\n@1.") - try testing.expect_equal(6, state.tokens.items.len) - try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind) - try testing.expect_equal(TokenKind.ident, state.tokens.items[1].kind) - try testing.expect_equal(TokenKind.newline, state.tokens.items[2].kind) - try testing.expect_equal(TokenKind.invalid, state.tokens.items[3].kind) - try testing.expect_equal(TokenKind.invalid, state.tokens.items[4].kind) - try testing.expect_equal(TokenKind.eof, state.tokens.items[5].kind) - try testing.expect_equal(2, state.diagnostics.items.len) - try testing.expect_equal("name", strpool.get_str(&strpool.strings, state.tokens.items[1].str_id)?) + try testing.expect_equal(5, state.tokens.items.len) + try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind) + try testing.expect_equal(TokenKind.ident, state.tokens.items[1].kind) + try testing.expect_equal(TokenKind.invalid, state.tokens.items[2].kind) + try testing.expect_equal(TokenKind.invalid, state.tokens.items[3].kind) + try testing.expect_equal(TokenKind.eof, state.tokens.items[4].kind) + try testing.expect_equal(2, state.diagnostics.items.len) + try testing.expect_equal("name", strpool.get_str(&strpool.strings, state.tokens.items[1].str_id)?) } diff --git a/source/lexer/token.hon b/source/lexer/token.hon index 53b215c..cc0339e 100644 --- a/source/lexer/token.hon +++ b/source/lexer/token.hon @@ -1,34 +1,38 @@ import "@source/strpool" -TokenKind :: enum { - if - else - for - while - proc - return - - ident - int - float - string - - equal - double_colon - - open_paren - close_paren - open_curly - close_curly - - newline - - invalid - eof -} - Token :: struct { kind TokenKind start int - str_id strpool.StringId = strpool.NoId + str_id strpool.StringId = strpool.NO_ID +} + +TokenKind :: enum { + # keywords + if, else + for, while + proc, return + + # literals + ident, string + int, float + + # comparison + equal_equal, not_equal + less, less_equal + greater, greater_equal + + # assignment + equal, double_colon + + # delimiters + open_paren, close_paren + open_bracket, close_bracket + open_curly, close_curly + + colon + newline + + # special + eof + invalid } diff --git a/source/main.hon b/source/main.hon index 7afd67e..6306ec5 100644 --- a/source/main.hon +++ b/source/main.hon @@ -1,34 +1,24 @@ import "@std/debug" import "@std/mem" +import "@std/enums/enummap" -test import "@std/enums" +test import "@std/enums/enummap" test import "@std/arraylist" test import "@std/hashmap" test import "@std/strmap" import "@source/strpool" import "@source/lexer" +import "@source/parser" test import "@source/strpool" test import "@source/lexer" program :: - `# these are immutable - `x :: 32 - `y :: 3.2 - ` - `# these are mutable - `z u32 = 54 - ` - `# keywords - `if - `else - `for - `while - `proc - `return - ` - `main proc() void {} + `# literals + `x :: 123 + +# todo: consider making current aliased types (IDs) distinct instead main proc() void { strpool.strings = strpool.init(mem.c_allocator) @@ -48,5 +38,40 @@ main proc() void { for scan_state.tokens.items |token| { debug.print("{}\n", {token.kind}) } + debug.print("]]\n\n", {}) + + debug.print("TOKENS::DIAGNOSTICS::[[\n", {}) + for scan_state.diagnostics.items |diagnostic| { + details :: enummap.get(&lexer.error_msg_map, diagnostic.code) + debug.print("{}: {}\n", {details?.name, details?.message}) + } + debug.print("]]\n\n", {}) + + parse_state parser.State = parser.init(mem.c_allocator) + defer parser.deinit(&parse_state) + parser.parse(&parse_state, scan_state.tokens.items) catch |err| { + debug.print("failed to parse: {}\n", {err}) + return + } + + debug.print("AST::[[\n", {}) + for parse_state.nodes.items |node, id| { + token :: scan_state.tokens.items[node.main_token] + + end :: if (token.kind == .int or token.kind == .float) + lexer.scan_number(token.start, program) + else if (token.kind == .string) + lexer.scan_string(token.start, program) + else + token.start + + debug.print("{} (id = {}): {}\n", { + node.kind, + id, + program[token.start..end], + #node.data0, + #node.data1, + }) + } debug.print("]]\n", {}) } diff --git a/source/parser/parser.hon b/source/parser/parser.hon new file mode 100644 index 0000000..42c51b2 --- /dev/null +++ b/source/parser/parser.hon @@ -0,0 +1,156 @@ +import "@std" +import "@std/mem" +import "@std/arraylist" +import "@std/debug" +import "@std/enums/enummap" + +import "@source/lexer" + +NodeId :: distinct u32 +ExtraId :: distinct NodeId + +NO_ID :: maxval!(NodeId) + +Diagnostic :: struct { + code ErrorCode + node NodeId +} + +ParseError :: alias ErrorCode | mem.AllocError + +ErrorCode :: enum { + unexpected_token, +} + +ErrorDetails :: struct { + name []u8 + message []u8 +} + +error_msg_map std.EnumMap(ErrorCode, ErrorDetails) :: enummap.init({ + unexpected_token = ErrorDetails { + name = "P0", + message = "unexpected token", + }, +}) + +hide Token :: alias lexer.Token +hide TokenId :: alias lexer.TokenId +hide TokenKind :: alias lexer.TokenKind + +NodeData :: union { + node_id NodeId + extra_id ExtraId +} + +Node :: struct { + kind NodeKind + main_token TokenId + + data0 NodeData = NodeData{ node_id = NO_ID } + data1 NodeData = NodeData{ node_id = NO_ID } +} + +NodeKind :: enum { + literal_int + literal_float + literal_string + expr_unary + expr_binary + decl + invalid +} + +State :: struct { + nodes std.ArrayList(Node) + + #! array of indexes into the nodes array. + extra std.ArrayList(NodeId) + + next_token TokenId = TokenId(0) +} + +init proc(allocator mem.Allocator) State { + return State{ + nodes = arraylist.init(allocator), + extra = arraylist.init(allocator), + } +} + +deinit proc(state @mut State) void { + arraylist.deinit(&state.nodes) + arraylist.deinit(&state.extra) +} + +parse proc(state @mut State, tokens []Token) void ! ParseError { + _ = try parse_decl(state, tokens) +} + +parse_decl proc(state @mut State, tokens []Token) NodeId ! ParseError { + # expect identifier + try expect(state, tokens, .ident) + ident_token :: state.next_token + state.next_token += 1 + + # expect `::` (immutable assignment) + try expect(state, tokens, .double_colon) + state.next_token += 1 + + # expect expression + expr :: try parse_primary(state, tokens) + + # expect statement terminator (newline) + try expect_either(state, tokens, &[.newline, .eof]) + state.next_token += 1 + + decl :: try add_node(&state.nodes, Node{ + kind = .decl, + main_token = ident_token, + data0 = NodeData{ node_id = expr }, + }) + + return decl +} + +parse_primary proc(state @mut State, tokens []Token) NodeId ! mem.AllocError { + start_token :: state.next_token + state.next_token += 1 + return match tokens[start_token].kind { + .int: try add_node(&state.nodes, Node{ + kind = .literal_int, + main_token = start_token, + }) + .float: try add_node(&state.nodes, Node{ + kind = .literal_float, + main_token = start_token, + }) + .string: try add_node(&state.nodes, Node{ + kind = .literal_string, + main_token = start_token, + }) + else: try add_node(&state.nodes, Node{ + kind = .invalid, + main_token = start_token, + }) + } +} + +hide expect proc(state @mut State, tokens []Token, token_kind TokenKind) void ! ErrorCode { + if (tokens[state.next_token].kind != token_kind) return .unexpected_token +} + +hide expect_either proc(state @mut State, tokens []Token, token_kinds []TokenKind) void ! ErrorCode { + for (token_kinds) |kind| if (tokens[state.next_token].kind == kind) return + return .unexpected_token +} + +hide add_node proc(nodes @mut std.ArrayList(Node), node Node) NodeId ! mem.AllocError { + id :: node_id(nodes.items.len) + try arraylist.append(nodes, node) + return id +} + +hide node_id proc(idx uint) NodeId { + debug.assert(u64(idx) < u64(NO_ID)) + return NodeId(idx) +} diff --git a/source/strpool/strpool.hon b/source/strpool/strpool.hon index 5c55de1..ca775da 100644 --- a/source/strpool/strpool.hon +++ b/source/strpool/strpool.hon @@ -8,58 +8,59 @@ strings StringPool = undefined InternError :: enum { out_of_space } StringId :: alias u32 -NoId :: maxval!(StringId) + +NO_ID :: maxval!(StringId) StringPool :: struct { - ids hashmap.StringHashMap(StringId) # string → id - strings arraylist.ArrayList([]u8) # id → owned string - allocator mem.Allocator + ids hashmap.StringHashMap(StringId) # string → id + strings arraylist.ArrayList([]u8) # id → owned string + allocator mem.Allocator } init proc(allocator mem.Allocator) StringPool { - return StringPool{ - ids = hashmap.init(allocator), - strings = arraylist.init(allocator), - allocator = allocator, - } + return StringPool{ + ids = hashmap.init(allocator), + strings = arraylist.init(allocator), + allocator = allocator, + } } deinit proc(pool @mut StringPool) void { - hashmap.deinit(&pool.ids) - for pool.strings.items |str| { - mem.free(pool.allocator, str) - } - arraylist.deinit(&pool.strings) + hashmap.deinit(&pool.ids) + for pool.strings.items |str| { + mem.free(pool.allocator, str) + } + arraylist.deinit(&pool.strings) } intern proc(pool @mut StringPool, str []u8) StringId ! (mem.AllocError | InternError) { - if hashmap.get(&pool.ids, str) |id| return id + if hashmap.get(&pool.ids, str) |id| return id - if (pool.strings.items.len >= usize(NoId)) return .out_of_space - id StringId :: StringId(pool.strings.items.len) + if (pool.strings.items.len >= usize(NO_ID)) return .out_of_space + id StringId :: StringId(pool.strings.items.len) - owned_str []mut u8 :: try mem.alloc(u8, pool.allocator, str.len) - errdefer mem.free(pool.allocator, owned_str) - memcopy!(owned_str, str) + owned_str []mut u8 :: try mem.alloc(u8, pool.allocator, str.len) + errdefer mem.free(pool.allocator, owned_str) + memcopy!(owned_str, str) - try arraylist.append(&pool.strings, owned_str) - errdefer _ = arraylist.pop(&pool.strings) + try arraylist.append(&pool.strings, owned_str) + errdefer _ = arraylist.pop(&pool.strings) - hashmap.put(&pool.ids, owned_str, id) catch |err| { - match err { - .key_exists: unreachable - else: return err - } - } + hashmap.put(&pool.ids, owned_str, id) catch |err| { + match err { + .key_exists: unreachable + else: return err + } + } - return id + return id } get_str proc(pool @StringPool, id StringId) ?[]u8 { - if (usize(id) >= pool.strings.items.len) return null - return pool.strings.items[usize(id)] + if (usize(id) >= pool.strings.items.len) return null + return pool.strings.items[usize(id)] } get_id proc(pool @StringPool, str []u8) ?StringId { - return hashmap.get(&pool.ids, str) + return hashmap.get(&pool.ids, str) } diff --git a/source/strpool/strpool.test.hon b/source/strpool/strpool.test.hon index 1d5d48c..f2e9031 100644 --- a/source/strpool/strpool.test.hon +++ b/source/strpool/strpool.test.hon @@ -2,23 +2,23 @@ import "@std/mem" import "@std/testing" handles_intern test { - pool StringPool = init(mem.c_allocator) - defer deinit(&pool) + pool StringPool = init(mem.c_allocator) + defer deinit(&pool) - input [5]mut u8 = ['h', 'e', 'l', 'l', 'o'] - id :: try intern(&pool, input[..]) - duplicate :: try intern(&pool, "hello") - input[0] = 'j' + input [5]mut u8 = ['h', 'e', 'l', 'l', 'o'] + id :: try intern(&pool, input[..]) + duplicate :: try intern(&pool, "hello") + input[0] = 'j' - same_id :: get_id(&pool, "hello") - mutated_id :: get_id(&pool, input[..]) - hello_str :: get_str(&pool, id) - invalid_str :: get_str(&pool, id + 1) + same_id :: get_id(&pool, "hello") + mutated_id :: get_id(&pool, input[..]) + hello_str :: get_str(&pool, id) + invalid_str :: get_str(&pool, id + 1) - try testing.expect_equal(0, id) - try testing.expect_equal(id, duplicate) - try testing.expect_equal(id, same_id?) - try testing.expect_equal(null, mutated_id) - try testing.expect_equal(null, invalid_str) - try testing.expect_equal("hello", hello_str?) + try testing.expect_equal(0, id) + try testing.expect_equal(id, duplicate) + try testing.expect_equal(id, same_id?) + try testing.expect_equal(null, mutated_id) + try testing.expect_equal(null, invalid_str) + try testing.expect_equal("hello", hello_str?) } diff --git a/std/arraylist/arraylist.hon b/std/arraylist/arraylist.hon index 64e141e..33dc45f 100644 --- a/std/arraylist/arraylist.hon +++ b/std/arraylist/arraylist.hon @@ -1,74 +1,74 @@ import "@std/mem" ArrayList proc($T type) type { - return struct { - items []mut T - capacity usize - allocator mem.Allocator - } + return struct { + items []mut T + capacity usize + allocator mem.Allocator + } } init proc($T type, allocator mem.Allocator) ArrayList(T) { - return ArrayList(T) { - items = mem.empty(T), - capacity = 0, - allocator = allocator, - } + return ArrayList(T) { + items = mem.empty(T), + capacity = 0, + allocator = allocator, + } } deinit proc($T type, list @mut ArrayList(T)) void { - allocation []mut T :: list.items.ptr[..list.capacity] - mem.free(list.allocator, allocation) - list.items = mem.empty(T) - list.capacity = 0 + allocation []mut T :: list.items.ptr[..list.capacity] + mem.free(list.allocator, allocation) + list.items = mem.empty(T) + list.capacity = 0 } -reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.AllocError { - if min_capacity <= list.capacity { - return - } +hide reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.AllocError { + if min_capacity <= list.capacity { + return + } - new_capacity usize = 8 - if list.capacity >= 8 { - half usize :: divtrunc!(list.capacity, 2) - if list.capacity > maxval!(usize) - half { - new_capacity = min_capacity - } else { - new_capacity = list.capacity + half - } - } - if new_capacity < min_capacity { - new_capacity = min_capacity - } + new_capacity usize = 8 + if list.capacity >= 8 { + half usize :: divtrunc!(list.capacity, 2) + if list.capacity > maxval!(usize) - half { + new_capacity = min_capacity + } else { + new_capacity = list.capacity + half + } + } + if new_capacity < min_capacity { + new_capacity = min_capacity + } - length usize :: list.items.len - allocation []mut T :: list.items.ptr[..list.capacity] - grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| { - return .out_of_memory - } - list.items = grown.ptr[..length] - list.capacity = new_capacity - return + length usize :: list.items.len + allocation []mut T :: list.items.ptr[..list.capacity] + grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| { + return .out_of_memory + } + list.items = grown.ptr[..length] + list.capacity = new_capacity + return } append proc($T type, list @mut ArrayList(T), value T) void ! mem.AllocError { - length usize :: list.items.len - if length == maxval!(usize) { - return .out_of_memory - } - try reserve(list, length + 1) - list.items = list.items.ptr[..length + 1] - list.items[length] = value - return + length usize :: list.items.len + if length == maxval!(usize) { + return .out_of_memory + } + try reserve(list, length + 1) + list.items = list.items.ptr[..length + 1] + list.items[length] = value + return } pop proc($T type, list @mut ArrayList(T)) ?T { - if (list.items.len == 0) return null - value :: list.items[list.items.len - 1] - list.items = list.items.ptr[..list.items.len - 1] - return value + if (list.items.len == 0) return null + value :: list.items[list.items.len - 1] + list.items = list.items.ptr[..list.items.len - 1] + return value } clear proc($T type, list @mut ArrayList(T)) void { - list.items = list.items.ptr[..0] + list.items = list.items.ptr[..0] } diff --git a/std/arraylist/arraylist.test.hon b/std/arraylist/arraylist.test.hon index ae64e8d..5906869 100644 --- a/std/arraylist/arraylist.test.hon +++ b/std/arraylist/arraylist.test.hon @@ -2,31 +2,31 @@ import "@std/mem" import "@std/testing" handles_append test { - list ArrayList(i32) = init(mem.c_allocator) - defer deinit(&list) + list ArrayList(i32) = init(mem.c_allocator) + defer deinit(&list) - try append(&list, 42) + try append(&list, 42) - try testing.expect_equal(1, list.items.len) - try testing.expect_equal(42, list.items[0]) + try testing.expect_equal(1, list.items.len) + try testing.expect_equal(42, list.items[0]) } handles_clear test { - list ArrayList(i32) = init(mem.c_allocator) - defer deinit(&list) + list ArrayList(i32) = init(mem.c_allocator) + defer deinit(&list) - try append(&list, 42) - clear(&list) + try append(&list, 42) + clear(&list) - try testing.expect_equal(0, list.items.len) + try testing.expect_equal(0, list.items.len) } handles_reserve test { - list ArrayList(i32) = init(mem.c_allocator) - defer deinit(&list) + list ArrayList(i32) = init(mem.c_allocator) + defer deinit(&list) - try reserve(&list, 10) + try reserve(&list, 10) - try testing.expect_equal(10, list.capacity) - try testing.expect_equal(0, list.items.len) + try testing.expect_equal(10, list.capacity) + try testing.expect_equal(0, list.items.len) } diff --git a/std/build/build.hon b/std/build/build.hon index 8e48122..f5e4ba8 100644 --- a/std/build/build.hon +++ b/std/build/build.hon @@ -7,11 +7,11 @@ # Declarative and literal-only: one executable per build. List fields take an # address-of an array literal (`&["raylib"]`) and default to empty. BuildConfig :: struct { - name []u8 # output executable name under root/build - source []u8 # program package directory, relative to build.bro - libraries [][]u8 = &[] # library names to link (-l) - lib_paths [][]u8 = &[] # library search directories (-L) - includes [][]u8 = &[] # C include directories (-I) - defines [][]u8 = &[] # C preprocessor defines (name or name=value) - links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs) + name []u8 # output executable name under root/build + source []u8 # program package directory, relative to build.bro + libraries [][]u8 = &[] # library names to link (-l) + lib_paths [][]u8 = &[] # library search directories (-L) + includes [][]u8 = &[] # C include directories (-I) + defines [][]u8 = &[] # C preprocessor defines (name or name=value) + links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs) } diff --git a/std/debug/debug.hon b/std/debug/debug.hon index df5247f..76db5ff 100644 --- a/std/debug/debug.hon +++ b/std/debug/debug.hon @@ -1,28 +1,32 @@ import "@ffi/c" import "@std/io" +assert proc(ok bool) void { + if (!ok) unreachable +} + print proc($format []u8, $Args type, args Args) void { - writer io.Writer :: io.Writer{ - context = null, - handle = io.Handle{ file_desc = c_int(io.Stream.stderr) }, - write = write, - } - io.print(writer, format, Args, args) catch |_| {} + writer io.Writer :: io.Writer{ + context = null, + handle = io.Handle{ file_desc = c_int(io.Stream.stderr) }, + write = write, + } + io.print(writer, format, Args, args) catch |_| {} } hide write proc(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError { - request usize = bytes.len - maximum usize :: usize(maxval!(c_long)) - if request > maximum { - request = maximum - } - while true { - count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request)) - if count >= 0 { - return usize(count) - } - if c.__error()?^ != c.EINTR { - return .write_failed - } - } + request usize = bytes.len + maximum usize :: usize(maxval!(c_long)) + if request > maximum { + request = maximum + } + while true { + count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request)) + if count >= 0 { + return usize(count) + } + if c.__error()?^ != c.EINTR { + return .write_failed + } + } } diff --git a/std/enums/enummap/enummap.hon b/std/enums/enummap/enummap.hon new file mode 100644 index 0000000..891bfcf --- /dev/null +++ b/std/enums/enummap/enummap.hon @@ -0,0 +1,45 @@ +import "@std/meta" + +EnumMap proc($E, $V type) type { + match typeinfo!(E) { + .enum |info|: return struct { + present [info.fields.len]mut bool # fixme: replace with bitset + values [info.fields.len]mut V + } + else: compile_error!("EnumMap key must be an enum") + } +} + +init proc( + $E, $V type, + values meta.EnumFieldStruct(E, ?V, some!(null)), +) EnumMap(E, V) { + map EnumMap(E, V) = undefined + + match typeinfo!(E) { + .enum |info|: expand for info.fields |field, i| { + map.present[i] = false + + if field!(values, field.name) |value| { + map.present[i] = true + map.values[i] = value + } + } + else: compile_error!("EnumMap key must be an enum") + } + + return map +} + +get proc($E, $V type, map @EnumMap(E, V), key E) ?V { + # fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup + match typeinfo!(E) { + .enum |info|: expand for info.fields |field, i| { + if key == field!(E, field.name) { + if (map.present[i]) return map.values[i] + return null + } + } + else: compile_error!("EnumMap key must be an enum") + } +} diff --git a/std/enums/enummap/enummap.test.hon b/std/enums/enummap/enummap.test.hon new file mode 100644 index 0000000..841d8b3 --- /dev/null +++ b/std/enums/enummap/enummap.test.hon @@ -0,0 +1,25 @@ +import "@std/mem" +import "@std/testing" + +TestEnum :: enum(u8) { + ident = 3 + int = 8 + eof = 21 +} + +handles_sparse_enum_get test { + names EnumMap(TestEnum, []u8) = init({ + ident = "identifier", + int = "integer", + }) + + ident :: get(&names, TestEnum.ident) + + try testing.expect_type(?[]u8, ident) + try testing.expect(mem.eql("identifier", ident?)) + + eof :: get(&names, TestEnum.eof) + + try testing.expect_type(?[]u8, eof) + try testing.expect_equal(null, eof) +} diff --git a/std/enums/enums.hon b/std/enums/enums.hon index 3d11167..e69de29 100644 --- a/std/enums/enums.hon +++ b/std/enums/enums.hon @@ -1,45 +0,0 @@ -import "@std/meta" - -EnumMap proc($E, $V type) type { - match typeinfo!(E) { - .enum |info|: return struct { - present [info.fields.len]mut bool # fixme: replace with bitset - values [info.fields.len]mut V - } - else: compile_error!("EnumMap key must be an enum") - } -} - -init proc( - $E, $V type, - values meta.EnumFieldStruct(E, ?V, some!(null)), -) EnumMap(E, V) { - map EnumMap(E, V) = undefined - - match typeinfo!(E) { - .enum |info|: expand for info.fields |field, i| { - map.present[i] = false - - if field!(values, field.name) |value| { - map.present[i] = true - map.values[i] = value - } - } - else: compile_error!("EnumMap key must be an enum") - } - - return map -} - -get proc($E, $V type, map @EnumMap(E, V), key E) ?V { - # fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup - match typeinfo!(E) { - .enum |info|: expand for info.fields |field, i| { - if key == field!(E, field.name) { - if (map.present[i]) return map.values[i] - return null - } - } - else: compile_error!("EnumMap key must be an enum") - } -} diff --git a/std/enums/enums.test.hon b/std/enums/enums.test.hon index d50441a..e69de29 100644 --- a/std/enums/enums.test.hon +++ b/std/enums/enums.test.hon @@ -1,25 +0,0 @@ -import "@std/mem" -import "@std/testing" - -TestEnum :: enum(u8) { - ident = 3 - int = 8 - eof = 21 -} - -handles_sparse_enum_get test { - names EnumMap(TestEnum, []u8) = init({ - ident = "identifier", - int = "integer", - }) - - ident :: get(&names, TestEnum.ident) - - try testing.expect_type(?[]u8, ident) - try testing.expect(mem.eql("identifier", ident?)) - - eof :: get(&names, TestEnum.eof) - - try testing.expect_type(?[]u8, eof) - try testing.expect_equal(null, eof) -} diff --git a/std/hashmap/hashmap.hon b/std/hashmap/hashmap.hon index 1035c76..5af6836 100644 --- a/std/hashmap/hashmap.hon +++ b/std/hashmap/hashmap.hon @@ -3,154 +3,154 @@ import "@std/mem" PutError :: enum { key_exists } Entry proc($K, $V type) type { - return struct { - # hash = 0 means empty - hash usize = 0 - key K - value V - } + return struct { + # hash = 0 means empty + hash usize = 0 + key K + value V + } } HashMap proc( - $K, $V type, - $hash_key proc(key K) usize, - $keys_eql proc(a, b K) bool, + $K, $V type, + $hash_key proc(key K) usize, + $keys_eql proc(a, b K) bool, ) type { - return struct { - entries []mut Entry(K, V) - count usize - allocator mem.Allocator - } + return struct { + entries []mut Entry(K, V) + count usize + allocator mem.Allocator + } } StringHashMap proc($V type) type { - return HashMap([]u8, V, str_hash, str_eql) + return HashMap([]u8, V, str_hash, str_eql) } init proc( - $K, $V type, - $hash_key proc(key K) usize, - $keys_eql proc(a, b K) bool, - allocator mem.Allocator, + $K, $V type, + $hash_key proc(key K) usize, + $keys_eql proc(a, b K) bool, + allocator mem.Allocator, ) HashMap(K, V, hash_key, keys_eql) { - return HashMap(K, V, hash_key, keys_eql){ - entries = mem.empty(Entry(K, V)), - count = 0, - allocator = allocator, - } + return HashMap(K, V, hash_key, keys_eql){ + entries = mem.empty(Entry(K, V)), + count = 0, + allocator = allocator, + } } #! free the entries in the hash map. #! note: this operation invalidates the map. deinit proc( - $K, $V type, - $hash_key proc(key K) usize, - $keys_eql proc(a, b K) bool, - map @HashMap(K, V, hash_key, keys_eql), + $K, $V type, + $hash_key proc(key K) usize, + $keys_eql proc(a, b K) bool, + map @HashMap(K, V, hash_key, keys_eql), ) void { mem.free(map.allocator, map.entries) } get proc( - $K, $V type, - $hash_key proc(key K) usize, - $keys_eql proc(a, b K) bool, - map @HashMap(K, V, hash_key, keys_eql), - key K, + $K, $V type, + $hash_key proc(key K) usize, + $keys_eql proc(a, b K) bool, + map @HashMap(K, V, hash_key, keys_eql), + key K, ) ?V { - if (map.count == 0) return null + if (map.count == 0) return null - hash :: normalize(hash_key(key)) - idx usize = hash & (map.entries.len - 1) + hash :: normalize(hash_key(key)) + idx usize = hash & (map.entries.len - 1) - while true { - entry :: map.entries[idx] - if (entry.hash == 0) return null + while true { + entry :: map.entries[idx] + if (entry.hash == 0) return null - if (entry.hash == hash and keys_eql(entry.key, key)) { - return entry.value - } + if (entry.hash == hash and keys_eql(entry.key, key)) { + return entry.value + } - idx = (idx + 1) & (map.entries.len - 1) - } + idx = (idx + 1) & (map.entries.len - 1) + } } put proc( - $K, $V type, - $hash_key proc(key K) usize, - $keys_eql proc(a, b K) bool, - map @mut HashMap(K, V, hash_key, keys_eql), - key K, - value V, + $K, $V type, + $hash_key proc(key K) usize, + $keys_eql proc(a, b K) bool, + map @mut HashMap(K, V, hash_key, keys_eql), + key K, + value V, ) void ! (PutError | mem.AllocError) { - # check grow - threshold :: map.entries.len - divtrunc!(map.entries.len, 4) - if (map.entries.len == 0 or map.count + 1 > threshold) { - old_entries :: map.entries - new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8 - new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size) + # check grow + threshold :: map.entries.len - divtrunc!(map.entries.len, 4) + if (map.entries.len == 0 or map.count + 1 > threshold) { + old_entries :: map.entries + new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8 + new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size) - # zero new entries - for (0..new_entries.len) |i| new_entries[i].hash = 0 + # zero new entries + for (0..new_entries.len) |i| new_entries[i].hash = 0 - # move old entries - for old_entries |entry| { - if (entry.hash == 0) continue + # move old entries + for old_entries |entry| { + if (entry.hash == 0) continue - # find an empty slot - idx usize = entry.hash & (new_entries.len - 1) - while new_entries[idx].hash != 0 { - idx = (idx + 1) & (new_entries.len - 1) - } + # find an empty slot + idx usize = entry.hash & (new_entries.len - 1) + while new_entries[idx].hash != 0 { + idx = (idx + 1) & (new_entries.len - 1) + } - new_entries[idx] = entry - } + new_entries[idx] = entry + } - map.entries = new_entries - mem.free(map.allocator, old_entries) - } + map.entries = new_entries + mem.free(map.allocator, old_entries) + } - # put new entry - hash :: normalize(hash_key(key)) - idx usize = hash & (map.entries.len - 1) + # put new entry + hash :: normalize(hash_key(key)) + idx usize = hash & (map.entries.len - 1) - while true { - entry :: map.entries[idx] - if entry.hash == 0 { - map.entries[idx] = Entry(K, V){ - hash = hash, - key = key, - value = value, - } - map.count += 1 - return - } + while true { + entry :: map.entries[idx] + if entry.hash == 0 { + map.entries[idx] = Entry(K, V){ + hash = hash, + key = key, + value = value, + } + map.count += 1 + return + } - if entry.hash == hash and keys_eql(entry.key, key) { - return .key_exists - } + if entry.hash == hash and keys_eql(entry.key, key) { + return .key_exists + } - idx = (idx + 1) & (map.entries.len - 1) - } + idx = (idx + 1) & (map.entries.len - 1) + } } hide normalize proc(hash usize) usize { - # mapping both 0 and 1 to 1 is safe because equality resolves - # collisions (since hash and key must both be equal). - if (hash == 0) return 1 - return hash + # mapping both 0 and 1 to 1 is safe because equality resolves + # collisions (since hash and key must both be equal). + if (hash == 0) return 1 + return hash } #! FNV-1a hash implementation. #! note: vulnerable to collision attacks. hide str_hash proc(key []u8) usize { - hash u32 = 2166136261 # offset basis - prime u32 = 16777619 + hash u32 = 2166136261 # offset basis + prime u32 = 16777619 - for key |byte| { - product u64 :: u64(hash xor u32(byte)) * prime - hash = u32(product & u64(maxval!(u32))) - } + for key |byte| { + product u64 :: u64(hash xor u32(byte)) * prime + hash = u32(product & u64(maxval!(u32))) + } - return usize(hash) + return usize(hash) } hide str_eql proc(a, b []u8) bool { return mem.eql(a, b) } diff --git a/std/hashmap/hashmap.test.hon b/std/hashmap/hashmap.test.hon index 57ff8a4..a29934f 100644 --- a/std/hashmap/hashmap.test.hon +++ b/std/hashmap/hashmap.test.hon @@ -2,12 +2,12 @@ import "@std/mem" import "@std/testing" handles_put_and_get test { - map StringHashMap(u32) = init(mem.c_allocator) - defer deinit(&map) + map StringHashMap(u32) = init(mem.c_allocator) + defer deinit(&map) - try put(&map, "key", 42) - value :: get(&map, "key") + try put(&map, "key", 42) + value :: get(&map, "key") - try testing.expect_type(?u32, value) - try testing.expect_equal(42, value?) + try testing.expect_type(?u32, value) + try testing.expect_equal(42, value?) } diff --git a/std/io/file.hon b/std/io/file.hon index 6b824a2..e7fb404 100644 --- a/std/io/file.hon +++ b/std/io/file.hon @@ -1,124 +1,124 @@ import "@ffi/c" File :: struct { - io Io - handle Handle + io Io + handle Handle } FileMode :: enum { - read_only - write_only - read_write + read_only + write_only + read_write } OpenError :: enum { - open_failed + open_failed } CloseError :: enum { - close_failed + close_failed } open proc(io Io, path [;0]u8, mode FileMode) File ! OpenError { - handle Handle :: try io.vtable.open(io.context, path, mode) - return File{ io = io, handle = handle } + handle Handle :: try io.vtable.open(io.context, path, mode) + return File{ io = io, handle = handle } } close proc(file File) void ! CloseError { - try file.io.vtable.close(file.io.context, file.handle) + try file.io.vtable.close(file.io.context, file.handle) } reader proc(file File) Reader { - return Reader { - context = file.io.context, - handle = file.handle, - read = file.io.vtable.read, - } + return Reader { + context = file.io.context, + handle = file.handle, + read = file.io.vtable.read, + } } writer proc(file File) Writer { - return Writer { - context = file.io.context, - handle = file.handle, - write = file.io.vtable.write, - } + return Writer { + context = file.io.context, + handle = file.handle, + write = file.io.vtable.write, + } } hide system_read proc(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError { - request usize = buffer.len - maximum usize :: usize(maxval!(c_long)) - if (request > maximum) request = maximum + request usize = buffer.len + maximum usize :: usize(maxval!(c_long)) + if (request > maximum) request = maximum - while true { - count c_long :: c.read(handle.file_desc, buffer.ptr, c_ulong(request)) - if (count >= 0) return usize(count) + while true { + count c_long :: c.read(handle.file_desc, buffer.ptr, c_ulong(request)) + if (count >= 0) return usize(count) - errno c_int :: c.__error()?^ - if (errno == c.EINTR) continue - if (errno == c.EBADF) return .not_open_for_reading - return .read_failed - } + errno c_int :: c.__error()?^ + if (errno == c.EINTR) continue + if (errno == c.EBADF) return .not_open_for_reading + return .read_failed + } } hide system_write proc(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError { - request usize = bytes.len - maximum usize :: usize(maxval!(c_long)) - if (request > maximum) request = maximum + request usize = bytes.len + maximum usize :: usize(maxval!(c_long)) + if (request > maximum) request = maximum - while true { - count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request)) - if (count >= 0) return usize(count) + while true { + count c_long :: c.write(handle.file_desc, bytes.ptr, c_ulong(request)) + if (count >= 0) return usize(count) - errno c_int :: c.__error()?^ - if (errno == c.EINTR) continue - if (errno == c.EBADF) return .not_open_for_writing - return .write_failed - } + errno c_int :: c.__error()?^ + if (errno == c.EINTR) continue + if (errno == c.EBADF) return .not_open_for_writing + return .write_failed + } } hide system_open proc(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError { - flags c_int = c.O_RDONLY - match mode { - .read_only: flags = c.O_RDONLY - .write_only: flags = c.O_WRONLY - .read_write: flags = c.O_RDWR - } - while true { - fd c_int :: c.open(ptrcast!(c_char, path.ptr), flags) - if (fd >= 0) return Handle{ file_desc = fd } - if (c.__error()?^ != c.EINTR) return .open_failed - } + flags c_int = c.O_RDONLY + match mode { + .read_only: flags = c.O_RDONLY + .write_only: flags = c.O_WRONLY + .read_write: flags = c.O_RDWR + } + while true { + fd c_int :: c.open(ptrcast!(c_char, path.ptr), flags) + if (fd >= 0) return Handle{ file_desc = fd } + if (c.__error()?^ != c.EINTR) return .open_failed + } } hide system_close proc(_ ?@mut anyopaque, handle Handle) void ! CloseError { - if (c.close(handle.file_desc) != 0) return .close_failed + if (c.close(handle.file_desc) != 0) return .close_failed } hide system_stdin proc(_ ?@mut anyopaque) Handle { - return Handle{ file_desc = c_int(Stream.stdin) } + return Handle{ file_desc = c_int(Stream.stdin) } } hide system_stdout proc(_ ?@mut anyopaque) Handle { - return Handle{ file_desc = c_int(Stream.stdout) } + return Handle{ file_desc = c_int(Stream.stdout) } } hide system_stderr proc(_ ?@mut anyopaque) Handle { - return Handle{ file_desc = c_int(Stream.stderr) } + return Handle{ file_desc = c_int(Stream.stderr) } } hide system_vtable IoVTable :: IoVTable { - read = system_read, - write = system_write, - open = system_open, - close = system_close, - stdin = system_stdin, - stdout = system_stdout, - stderr = system_stderr, + read = system_read, + write = system_write, + open = system_open, + close = system_close, + stdin = system_stdin, + stdout = system_stdout, + stderr = system_stderr, } hide system proc() Io { - return Io { - context = null, - vtable = &system_vtable, - } + return Io { + context = null, + vtable = &system_vtable, + } } diff --git a/std/io/io.hon b/std/io/io.hon index 5a70f50..4a1cb52 100644 --- a/std/io/io.hon +++ b/std/io/io.hon @@ -2,407 +2,447 @@ import "@ffi/c" import "@std/meta" ReadError :: enum { - not_open_for_reading - read_failed + not_open_for_reading + read_failed } WriteError :: enum { - not_open_for_writing - write_failed - no_progress + not_open_for_writing + write_failed + no_progress } Handle :: union { - file_desc c_int - ptr @mut anyopaque + file_desc c_int + ptr @mut anyopaque } Stream :: enum(c_int) { - stdin = c.STDIN_FILENO - stdout = c.STDOUT_FILENO - stderr = c.STDERR_FILENO + stdin = c.STDIN_FILENO + stdout = c.STDOUT_FILENO + stderr = c.STDERR_FILENO } Io :: struct { - context ?@mut anyopaque - vtable @IoVTable + context ?@mut anyopaque + vtable @IoVTable } IoVTable :: struct { - read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError - write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError - open @proc(context ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError - close @proc(context ?@mut anyopaque, handle Handle) void ! CloseError - stdin @proc(context ?@mut anyopaque) Handle - stdout @proc(context ?@mut anyopaque) Handle - stderr @proc(context ?@mut anyopaque) Handle + read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError + write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError + open @proc(context ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError + close @proc(context ?@mut anyopaque, handle Handle) void ! CloseError + stdin @proc(context ?@mut anyopaque) Handle + stdout @proc(context ?@mut anyopaque) Handle + stderr @proc(context ?@mut anyopaque) Handle } Reader :: struct { - context ?@mut anyopaque - handle Handle - read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError + context ?@mut anyopaque + handle Handle + read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError } Writer :: struct { - context ?@mut anyopaque - handle Handle - write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError + context ?@mut anyopaque + handle Handle + write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError } read proc(input Reader, buffer []mut u8) usize ! ReadError { - if (buffer.len == 0) return 0 + if (buffer.len == 0) return 0 - count usize :: try input.read(input.context, input.handle, buffer) - if (count > buffer.len) return .read_failed + count usize :: try input.read(input.context, input.handle, buffer) + if (count > buffer.len) return .read_failed - return count + return count } write proc(output Writer, bytes []u8) usize ! WriteError { - if (bytes.len == 0) return 0 + if (bytes.len == 0) return 0 - count usize :: try output.write(output.context, output.handle, bytes) - if (count > bytes.len) return .write_failed + count usize :: try output.write(output.context, output.handle, bytes) + if (count > bytes.len) return .write_failed - return count + return count } write_all proc(output Writer, bytes []u8) void ! WriteError { - offset usize = 0 - while offset < bytes.len { - count usize :: try write(output, bytes[offset..]) - if (count == 0) return .no_progress - offset += count - } + offset usize = 0 + while offset < bytes.len { + count usize :: try write(output, bytes[offset..]) + if (count == 0) return .no_progress + offset += count + } } stdin proc(io Io) Reader { - return Reader { - context = io.context, - handle = io.vtable.stdin(io.context), - read = io.vtable.read, - } + return Reader { + context = io.context, + handle = io.vtable.stdin(io.context), + read = io.vtable.read, + } } stdout proc(io Io) Writer { - return Writer { - context = io.context, - handle = io.vtable.stdout(io.context), - write = io.vtable.write, - } + return Writer { + context = io.context, + handle = io.vtable.stdout(io.context), + write = io.vtable.write, + } } stderr proc(io Io) Writer { - return Writer { - context = io.context, - handle = io.vtable.stderr(io.context), - write = io.vtable.write, - } + return Writer { + context = io.context, + handle = io.vtable.stderr(io.context), + write = io.vtable.write, + } } print proc(output Writer, $format []u8, $Args type, args Args) void ! WriteError { - expand for parse_format(format.len, format, Args) |token| { - match token.kind { - .unused: break - .literal: try write_all(output, format[token.start..token.end]) - .string: try write_all(output, field!(args, token.field)) - .default: try write_default(output, field!(args, token.field)) - .decimal: try write_decimal(output, field!(args, token.field)) - .binary: try write_integer(output, field!(args, token.field), 2, false) - .octal: try write_integer(output, field!(args, token.field), 8, false) - .hex_lower: try write_integer(output, field!(args, token.field), 16, false) - .hex_upper: try write_integer(output, field!(args, token.field), 16, true) - .character: try write_character(output, field!(args, token.field)) - else: try write_float(output, field!(args, token.field), true) - } - } + expand for parse_format(format.len, format, Args) |token| { + match token.kind { + .unused: break + .literal: try write_all(output, format[token.start..token.end]) + .string: try write_all(output, field!(args, token.field)) + .default: try write_default(output, field!(args, token.field)) + .decimal: try write_decimal(output, field!(args, token.field)) + .binary: try write_integer(output, field!(args, token.field), 2, false) + .octal: try write_integer(output, field!(args, token.field), 8, false) + .hex_lower: try write_integer(output, field!(args, token.field), 16, false) + .hex_upper: try write_integer(output, field!(args, token.field), 16, true) + .character: try write_character(output, field!(args, token.field)) + else: try write_float(output, field!(args, token.field), true) + } + } } hide write_integer_signed proc(output Writer, value i64, base u64, uppercase bool) void ! WriteError { - buffer [65]mut u8 = undefined - end usize = buffer.len - current i64 = value + buffer [65]mut u8 = undefined + end usize = buffer.len + current i64 = value - while true { - digit_value i64 :: rem!(current, i64(base)) - digit u8 = if (digit_value < 0) - u8(-digit_value) - else - u8(digit_value) + while true { + digit_value i64 :: rem!(current, i64(base)) + digit u8 = if (digit_value < 0) + u8(-digit_value) + else + u8(digit_value) - end -= 1 - buffer[end] = if (digit < 10) - '0' + digit - else if (uppercase) - 'A' + digit - 10 - else - 'a' + digit - 10 + end -= 1 + buffer[end] = if (digit < 10) + '0' + digit + else if (uppercase) + 'A' + digit - 10 + else + 'a' + digit - 10 - current = divtrunc!(current, i64(base)) - if (current == 0) break - } - - if value < 0 { - end -= 1 - buffer[end] = '-' + current = divtrunc!(current, i64(base)) + if (current == 0) break } - try write_all(output, buffer[end..]) + + if value < 0 { + end -= 1 + buffer[end] = '-' + } + try write_all(output, buffer[end..]) } hide write_integer_unsigned proc(output Writer, value u64, base u64, uppercase bool) void ! WriteError { - buffer [65]mut u8 = undefined - end usize = buffer.len - current u64 = value + buffer [65]mut u8 = undefined + end usize = buffer.len + current u64 = value - while true { - digit u8 :: u8(rem!(current, base)) + while true { + digit u8 :: u8(rem!(current, base)) - end -= 1 - buffer[end] = if (digit < 10) - '0' + digit - else if (uppercase) - 'A' + digit - 10 - else - 'a' + digit - 10 + end -= 1 + buffer[end] = if (digit < 10) + '0' + digit + else if (uppercase) + 'A' + digit - 10 + else + 'a' + digit - 10 - current = divtrunc!(current, base) - if (current == 0) break - } + current = divtrunc!(current, base) + if (current == 0) break + } - try write_all(output, buffer[end..]) + try write_all(output, buffer[end..]) } hide FormatTokenKind :: enum { - unused - literal - default - string - decimal - binary - octal - hex_lower - hex_upper - character - scientific + unused + literal + default + string + decimal + binary + octal + hex_lower + hex_upper + character + scientific } hide FormatToken :: struct { - kind FormatTokenKind - start usize - end usize - field []u8 + kind FormatTokenKind + start usize + end usize + field []u8 } hide parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken { - tokens [N]mut FormatToken = undefined - for (usize(0))..format.len |index| { - tokens[index] = FormatToken{ kind = .unused, start = 0, end = 0, field = "" } - } + tokens [N]mut FormatToken = undefined + for (usize(0))..format.len |index| { + tokens[index] = FormatToken{ kind = .unused, start = 0, end = 0, field = "" } + } - field_count usize = 0 - match typeinfo!(Args) { - .record |r|: { - if (!r.is_tuple) compile_error!("io.print arguments must be a tuple") - field_count = r.fields.len - } - else: compile_error!("io.print arguments must be a tuple") - } + field_count usize = 0 + match typeinfo!(Args) { + .record |r|: { + if (!r.is_tuple) compile_error!("io.print arguments must be a tuple") + field_count = r.fields.len + } + else: compile_error!("io.print arguments must be a tuple") + } - token_count usize = 0 - argument_count usize = 0 - literal_start usize = 0 - cursor usize = 0 - while cursor < format.len { - byte :: format[cursor] - if byte == '{' { - if cursor + 1 >= format.len { - compile_error!("io.print format has an unmatched '{'") - } - if cursor > literal_start { - tokens[token_count] = FormatToken{ - kind = .literal, - start = literal_start, - end = cursor, - field = "", - } - token_count += 1 - } - next :: format[cursor + 1] - if next == '{' { - tokens[token_count] = FormatToken{ - kind = .literal, - start = cursor, - end = cursor + 1, - field = "", - } - token_count += 1 - cursor += 2 - literal_start = cursor - continue - } + token_count usize = 0 + argument_count usize = 0 + literal_start usize = 0 + cursor usize = 0 + while cursor < format.len { + byte :: format[cursor] + if byte == '{' { + if cursor + 1 >= format.len { + compile_error!("io.print format has an unmatched '{'") + } + if cursor > literal_start { + tokens[token_count] = FormatToken{ + kind = .literal, + start = literal_start, + end = cursor, + field = "", + } + token_count += 1 + } + next :: format[cursor + 1] + if next == '{' { + tokens[token_count] = FormatToken{ + kind = .literal, + start = cursor, + end = cursor + 1, + field = "", + } + token_count += 1 + cursor += 2 + literal_start = cursor + continue + } - kind FormatTokenKind = .default - width usize = 2 - if next != '}' { - if cursor + 2 >= format.len or format[cursor + 2] != '}' { - compile_error!("io.print format expects a one-character specifier") - } + kind FormatTokenKind = .default + width usize = 2 + if next != '}' { + if cursor + 2 >= format.len or format[cursor + 2] != '}' { + compile_error!("io.print format expects a one-character specifier") + } - width = 3 - if (next == 's') kind = .string - else if (next == 'd') kind = .decimal - else if (next == 'b') kind = .binary - else if (next == 'o') kind = .octal - else if (next == 'x') kind = .hex_lower - else if (next == 'X') kind = .hex_upper - else if (next == 'c') kind = .character - else if (next == 'e') kind = .scientific - else compile_error!("io.print format has an unknown specifier") - } + width = 3 + if (next == 's') kind = .string + else if (next == 'd') kind = .decimal + else if (next == 'b') kind = .binary + else if (next == 'o') kind = .octal + else if (next == 'x') kind = .hex_lower + else if (next == 'X') kind = .hex_upper + else if (next == 'c') kind = .character + else if (next == 'e') kind = .scientific + else compile_error!("io.print format has an unknown specifier") + } - if argument_count >= field_count { - compile_error!("io.print format argument count does not match the tuple") - } + if argument_count >= field_count { + compile_error!("io.print format argument count does not match the tuple") + } - tokens[token_count] = FormatToken{ - kind = kind, - start = 0, - end = 0, - field = format_field_name(Args, argument_count), - } + tokens[token_count] = FormatToken{ + kind = kind, + start = 0, + end = 0, + field = format_field_name(Args, argument_count), + } - token_count += 1 - argument_count += 1 - cursor += width - literal_start = cursor - continue - } + token_count += 1 + argument_count += 1 + cursor += width + literal_start = cursor + continue + } - if byte == '}' { - if cursor + 1 >= format.len or format[cursor + 1] != '}' { - compile_error!("io.print format has an unmatched '}'") - } - if cursor > literal_start { - tokens[token_count] = FormatToken{ - kind = .literal, - start = literal_start, - end = cursor, - field = "", - } - token_count += 1 - } - tokens[token_count] = FormatToken{ - kind = .literal, - start = cursor, - end = cursor + 1, - field = "", - } - token_count += 1 - cursor += 2 - literal_start = cursor - continue - } - cursor += 1 - } + if byte == '}' { + if cursor + 1 >= format.len or format[cursor + 1] != '}' { + compile_error!("io.print format has an unmatched '}'") + } + if cursor > literal_start { + tokens[token_count] = FormatToken{ + kind = .literal, + start = literal_start, + end = cursor, + field = "", + } + token_count += 1 + } + tokens[token_count] = FormatToken{ + kind = .literal, + start = cursor, + end = cursor + 1, + field = "", + } + token_count += 1 + cursor += 2 + literal_start = cursor + continue + } + cursor += 1 + } - if literal_start < format.len { - tokens[token_count] = FormatToken{ - kind = .literal, - start = literal_start, - end = format.len, - field = "", - } - } + if literal_start < format.len { + tokens[token_count] = FormatToken{ + kind = .literal, + start = literal_start, + end = format.len, + field = "", + } + } - if argument_count != field_count { - compile_error!("io.print format argument count does not match the tuple") - } - return tokens + if argument_count != field_count { + compile_error!("io.print format argument count does not match the tuple") + } + return tokens } hide format_field_name proc($T type, index usize) []u8 { - match typeinfo!(T) { - .record |r|: return r.fields[index].name - else: compile_error!("io.print arguments must be a tuple") - } + match typeinfo!(T) { + .record |r|: return r.fields[index].name + else: compile_error!("io.print arguments must be a tuple") + } +} +hide distinct_value proc($Backing, $Distinct type, value Distinct) Backing { + return ptrcast!(Backing, &value)^ } +hide scalar_or_distinct_type proc($T type) bool { + match typeinfo!(T) { + .bool: return true + .integer: return true + .float: return true + .distinct: return true + else: return false + } +} + + + hide write_integer proc(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError { - match typeinfo!(T) { - .integer: if minval!(T) < 0 { - try write_integer_signed(output, i64(value), base, uppercase) - } else { - try write_integer_unsigned(output, u64(value), base, uppercase) - } - else: compile_error!("io.print integer format requires an integer argument") - } + match typeinfo!(T) { + .integer: if minval!(T) < 0 { + try write_integer_signed(output, i64(value), base, uppercase) + } else { + try write_integer_unsigned(output, u64(value), base, uppercase) + } + .distinct |backing|: if scalar_or_distinct_type(backing) { + try write_integer(output, distinct_value(backing, T, value), base, uppercase) + } else { + compile_error!("io.print integer format requires an integer argument") + } + else: compile_error!("io.print integer format requires an integer argument") + } } # note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters. hide write_float proc(output Writer, $T type, value T, scientific bool) void ! WriteError { - match typeinfo!(T) { - .float: { - buffer [64]mut u8 = undefined - count c_int = 0 - if sizeof!(T) == 4 { - if (scientific) count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.8e", value) - else count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.9g", value) - } else if scientific { - count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.16e", value) - } else { - count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.17g", value) - } - if (count < 0 or usize(count) >= buffer.len) return .write_failed - try write_all(output, buffer[0..usize(count)]) - } - else: compile_error!("io.print float format requires a float argument") - } + match typeinfo!(T) { + .float: { + buffer [64]mut u8 = undefined + count c_int = 0 + if sizeof!(T) == 4 { + if (scientific) count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.8e", value) + else count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.9g", value) + } else if scientific { + count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.16e", value) + } else { + count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.17g", value) + } + if (count < 0 or usize(count) >= buffer.len) return .write_failed + try write_all(output, buffer[0..usize(count)]) + } + .distinct |backing|: if scalar_or_distinct_type(backing) { + try write_float(output, distinct_value(backing, T, value), scientific) + } else { + compile_error!("io.print float format requires a float argument") + } + else: compile_error!("io.print float format requires a float argument") + } } hide write_decimal proc(output Writer, $T type, value T) void ! WriteError { - match typeinfo!(T) { - .integer: try write_integer(output, value, 10, false) - .float: try write_float(output, value, false) - else: compile_error!("io.print '{d}' requires an integer or float argument") - } + match typeinfo!(T) { + .integer: try write_integer(output, value, 10, false) + .float: try write_float(output, value, false) + .distinct |backing|: if scalar_or_distinct_type(backing) { + try write_decimal(output, distinct_value(backing, T, value)) + } else { + compile_error!("io.print '{d}' requires an integer or float argument") + } + else: compile_error!("io.print '{d}' requires an integer or float argument") + } } hide write_character proc(output Writer, $T type, value T) void ! WriteError { - match typeinfo!(T) { - .integer: { - if minval!(T) < 0 or maxval!(T) > 255 { - compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") - } - buffer [1]u8 = [u8(value)] - try write_all(output, buffer[..]) - } - else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") - } + match typeinfo!(T) { + .integer: { + if minval!(T) < 0 or maxval!(T) > 255 { + compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") + } + buffer [1]u8 = [u8(value)] + try write_all(output, buffer[..]) + } + .distinct |backing|: if scalar_or_distinct_type(backing) { + try write_character(output, distinct_value(backing, T, value)) + } else { + compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") + } + else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") + } } hide write_default proc(output Writer, $T type, value T) void ! WriteError { - match typeinfo!(T) { - .bool: if value { - try write_all(output, "true") - } else { - try write_all(output, "false") - } - .integer: try write_integer(output, value, 10, false) - .float: try write_float(output, value, false) - .array: try write_all(output, value) - .pointer: try write_all(output, value) - .slice: try write_all(output, value) - .enum |enum_info|: { - expand for enum_info.fields |field| { - if value == field!(T, field.name) { - try write_all(output, ".") - try write_all(output, field.name) - return - } - } - return .write_failed - } - else: compile_error!("io.print '{}' does not support this argument type") - } + match typeinfo!(T) { + .bool: if value { + try write_all(output, "true") + } else { + try write_all(output, "false") + } + .integer: try write_integer(output, value, 10, false) + .float: try write_float(output, value, false) + .array: try write_all(output, value) + .pointer: try write_all(output, value) + .slice: try write_all(output, value) + .enum |enum_info|: { + expand for enum_info.fields |field| { + if value == field!(T, field.name) { + try write_all(output, ".") + try write_all(output, field.name) + return + } + } + return .write_failed + } + .distinct |backing|: if scalar_or_distinct_type(backing) { + try write_default(output, distinct_value(backing, T, value)) + } else { + compile_error!("io.print '{}' does not support this argument type") + } + else: compile_error!("io.print '{}' does not support this argument type") + } } diff --git a/std/mem/mem.hon b/std/mem/mem.hon index 8cc526b..26bf824 100644 --- a/std/mem/mem.hon +++ b/std/mem/mem.hon @@ -1,119 +1,119 @@ import "@ffi/c" AllocError :: enum { - out_of_memory + out_of_memory } Allocator :: struct { - context ?@mut anyopaque - vtable @AllocatorVTable + context ?@mut anyopaque + vtable @AllocatorVTable } AllocatorVTable :: struct { - alloc @proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8 - realloc @proc(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 - free @proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void + alloc @proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8 + realloc @proc(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 + free @proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void } raw_alloc proc(allocator Allocator, size usize, alignment usize) ?*mut u8 { - return allocator.vtable.alloc(allocator.context, size, alignment) + return allocator.vtable.alloc(allocator.context, size, alignment) } raw_realloc proc(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { - return allocator.vtable.realloc(allocator.context, memory, old_size, new_size, alignment) + return allocator.vtable.realloc(allocator.context, memory, old_size, new_size, alignment) } raw_free proc(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void { - allocator.vtable.free(allocator.context, memory, size, alignment) + allocator.vtable.free(allocator.context, memory, size, alignment) } eql proc($T type, left, right []T) bool { - if (left.len != right.len) return false - for (0..left.len) |i| if (left[i] != right[i]) { + if (left.len != right.len) return false + for (0..left.len) |i| if (left[i] != right[i]) { return false - } - return true + } + return true } #! allocate memory for a slice of type `T` with `count` elements. alloc proc($T type, allocator Allocator, count usize) []mut T ! AllocError { - if (count == 0) return empty_slice(T, 0) + if (count == 0) return empty_slice(T, 0) - element_size usize :: sizeof!(T) - if (element_size == 0) return empty_slice(T, count) + element_size usize :: sizeof!(T) + if (element_size == 0) return empty_slice(T, count) - if count > divtrunc!(maxval!(usize), element_size) { - return .out_of_memory - } + if count > divtrunc!(maxval!(usize), element_size) { + return .out_of_memory + } - memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T)) - if memory |bytes| { - pointer *mut T :: ptrcast!(T, bytes) - return pointer[..count] - } + memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T)) + if memory |bytes| { + pointer *mut T :: ptrcast!(T, bytes) + return pointer[..count] + } - return .out_of_memory + return .out_of_memory } #! reallocate memory for a slice of type `T` with `new_count` elements. #! reallocating with `new_count == 0` will free the memory and return an empty slice. #! note: memory must be reallocated with the same allocator that was used to allocate it. realloc proc($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError { - if new_count == memory.len { - return memory - } + if new_count == memory.len { + return memory + } - if new_count == 0 { - free(allocator, memory) - return empty_slice(T, 0) - } + if new_count == 0 { + free(allocator, memory) + return empty_slice(T, 0) + } - element_size usize :: sizeof!(T) - if (element_size == 0) return empty_slice(T, new_count) - if (new_count > divtrunc!(maxval!(usize), element_size)) return .out_of_memory + element_size usize :: sizeof!(T) + if (element_size == 0) return empty_slice(T, new_count) + if (new_count > divtrunc!(maxval!(usize), element_size)) return .out_of_memory - old_memory ?*mut u8 = null - old_size usize = 0 - if memory.len != 0 { - old_memory = ptrcast!(u8, memory.ptr) - old_size = memory.len * element_size - } - resized ?*mut u8 = raw_realloc( - allocator, - old_memory, - old_size, - new_count * element_size, - alignof!(T), - ) - if resized |bytes| { - pointer *mut T :: ptrcast!(T, bytes) - return pointer[..new_count] - } + old_memory ?*mut u8 = null + old_size usize = 0 + if memory.len != 0 { + old_memory = ptrcast!(u8, memory.ptr) + old_size = memory.len * element_size + } + resized ?*mut u8 = raw_realloc( + allocator, + old_memory, + old_size, + new_count * element_size, + alignof!(T), + ) + if resized |bytes| { + pointer *mut T :: ptrcast!(T, bytes) + return pointer[..new_count] + } - return .out_of_memory + return .out_of_memory } #! free memory allocated for a slice of type `T`. #! note: memory must be freed with the same allocator that was used to allocate it. free proc($T type, allocator Allocator, memory []T) void { - if (memory.len == 0 or sizeof!(T) == 0) return - raw_free(allocator, ptrcast!( - u8, - constcast!(memory).ptr), - memory.len * sizeof!(T), - alignof!(T), - ) + if (memory.len == 0 or sizeof!(T) == 0) return + raw_free(allocator, ptrcast!( + u8, + constcast!(memory).ptr), + memory.len * sizeof!(T), + alignof!(T), + ) } #! get an empty slice of type `T` with `count` elements. empty_slice proc($T type, count usize) []mut T { - pointer *mut T :: ptrcast!(T, (&empty_storage).ptr) - return pointer[..count] + pointer *mut T :: ptrcast!(T, (&empty_storage).ptr) + return pointer[..count] } #! get an empty slice of type `T` with 0 elements. empty proc($T type) []mut T { - return empty_slice(T, 0) + return empty_slice(T, 0) } hide empty_storage [1]mut u64 = [0] @@ -121,70 +121,70 @@ hide empty_storage [1]mut u64 = [0] hide malloc_alignment usize :: 16 # note: aarch64-macos libc malloc alignment assumption. hide power_of_two proc(value usize) bool { - if (value == 0) return false - current usize = value - while current > 1 { - half usize = divtrunc!(current, 2) - if (half * 2 != current) return false - current = half - } - return true + if (value == 0) return false + current usize = value + while current > 1 { + half usize = divtrunc!(current, 2) + if (half * 2 != current) return false + current = half + } + return true } hide c_alloc proc(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 { - if (power_of_two(alignment) == false) return null - if (alignment <= malloc_alignment) return ptrcast!(u8, c.malloc(c_ulong(size))) + if (power_of_two(alignment) == false) return null + if (alignment <= malloc_alignment) return ptrcast!(u8, c.malloc(c_ulong(size))) - memory [1]mut ?*mut anyopaque = [null] - status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size)) - if (status != 0) return null + memory [1]mut ?*mut anyopaque = [null] + status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size)) + if (status != 0) return null - return ptrcast!(u8, memory[0]) + return ptrcast!(u8, memory[0]) } hide c_realloc proc( - _ ?@mut anyopaque, - memory ?*mut u8, - old_size usize, - new_size usize, - alignment usize, + _ ?@mut anyopaque, + memory ?*mut u8, + old_size usize, + new_size usize, + alignment usize, ) ?*mut u8 { - if (power_of_two(alignment) == false) return null + if (power_of_two(alignment) == false) return null - if new_size == 0 { - c.free(memory) - return null - } + if new_size == 0 { + c.free(memory) + return null + } - if memory |old_memory| { - if alignment <= malloc_alignment { - return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size))) - } + if memory |old_memory| { + if alignment <= malloc_alignment { + return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size))) + } - new_memory ?*mut u8 = c_alloc(null, new_size, alignment) - if new_memory |new_bytes| { - copy_size usize = old_size - if (new_size < copy_size) copy_size = new_size - memcopy!(new_bytes[..copy_size], old_memory[..copy_size]) - c.free(old_memory) - } - return new_memory - } + new_memory ?*mut u8 = c_alloc(null, new_size, alignment) + if new_memory |new_bytes| { + copy_size usize = old_size + if (new_size < copy_size) copy_size = new_size + memcopy!(new_bytes[..copy_size], old_memory[..copy_size]) + c.free(old_memory) + } + return new_memory + } - return c_alloc(null, new_size, alignment) + return c_alloc(null, new_size, alignment) } hide c_free proc(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void { - c.free(memory) + c.free(memory) } hide c_vtable AllocatorVTable :: AllocatorVTable { - alloc = c_alloc, - realloc = c_realloc, - free = c_free, + alloc = c_alloc, + realloc = c_realloc, + free = c_free, } c_allocator Allocator :: Allocator { - context = null, - vtable = &c_vtable, + context = null, + vtable = &c_vtable, } diff --git a/std/meta/meta.hon b/std/meta/meta.hon index efe3676..c92603f 100644 --- a/std/meta/meta.hon +++ b/std/meta/meta.hon @@ -1,61 +1,61 @@ Layout :: enum { auto, c } ArrayInfo :: struct { - child type - len usize + child type + len usize } FieldInfo :: struct { - name []u8 - type type - index usize + name []u8 + type type + index usize } RecordInfo :: struct { - name ?[]u8 - fields []FieldInfo - is_tuple bool - layout Layout + name ?[]u8 + fields []FieldInfo + is_tuple bool + layout Layout } EnumInfo :: struct { - fields []FieldInfo + fields []FieldInfo } TypeInfo :: union(enum) { - invalid void - void void - noreturn void - anyopaque void - bool void - integer void - float void - array ArrayInfo - pointer void - slice void - range void - optional void - function void - enum EnumInfo - record RecordInfo - union void - fallible void - distinct void + invalid void + void void + noreturn void + anyopaque void + bool void + integer void + float void + array ArrayInfo + pointer void + slice void + range void + optional void + function void + enum EnumInfo + record RecordInfo + union void + fallible void + distinct type } EnumFieldStruct proc($E, $Field type, $default ?Field) type { - match typeinfo!(E) { - .enum |info|: { - names [info.fields.len]mut []u8 = undefined - field_types [info.fields.len]mut type = undefined - defaults [info.fields.len]mut ?Field = undefined - expand for info.fields |field, index| { - names[index] = field.name - field_types[index] = Field - defaults[index] = default - } - return struct_type!(.auto, names, field_types, defaults) - } - else: compile_error!("EnumFieldStruct key must be an enum") - } + match typeinfo!(E) { + .enum |info|: { + names [info.fields.len]mut []u8 = undefined + field_types [info.fields.len]mut type = undefined + defaults [info.fields.len]mut ?Field = undefined + expand for info.fields |field, index| { + names[index] = field.name + field_types[index] = Field + defaults[index] = default + } + return struct_type!(.auto, names, field_types, defaults) + } + else: compile_error!("EnumFieldStruct key must be an enum") + } } diff --git a/std/meta/meta.test.hon b/std/meta/meta.test.hon index 1e8f248..e6f6f2e 100644 --- a/std/meta/meta.test.hon +++ b/std/meta/meta.test.hon @@ -1,50 +1,67 @@ testing :: import "@std/testing" TestTokenKind :: enum(u8) { - ident = 3 - int = 8 - eof = 21 + ident = 3 + int = 8 + eof = 21 } TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null)) TestArrayAlias :: alias [3]u16 +TestInner :: distinct u16 +TestOuter :: distinct TestInner +TestOuterAlias :: alias TestOuter + hide array_info_matches proc($Array, $Child type, $len usize) bool { - match typeinfo!(Array) { - .array |info|: return info.child == Child and info.len == len - else: return false - } + match typeinfo!(Array) { + .array |info|: return info.child == Child and info.len == len + else: return false + } } +hide distinct_info_matches proc($Distinct, $Backing type) bool { + match typeinfo!(Distinct) { + .distinct |backing|: return backing == Backing + else: return false + } +} + array_reflection_exposes_child_and_logical_length test { - try testing.expect($(array_info_matches([4]i32, i32, 4))) - try testing.expect($(array_info_matches([0]bool, bool, 0))) - try testing.expect($(array_info_matches(TestArrayAlias, u16, 3))) - try testing.expect($(array_info_matches([2]mut i64, i64, 2))) - try testing.expect($(array_info_matches([2;0]u8, u8, 2))) + try testing.expect($(array_info_matches([4]i32, i32, 4))) + try testing.expect($(array_info_matches([0]bool, bool, 0))) + try testing.expect($(array_info_matches(TestArrayAlias, u16, 3))) + try testing.expect($(array_info_matches([2]mut i64, i64, 2))) + try testing.expect($(array_info_matches([2;0]u8, u8, 2))) } +distinct_reflection_exposes_immediate_backing test { + try testing.expect($(distinct_info_matches(TestInner, u16))) + try testing.expect($(distinct_info_matches(TestOuter, TestInner))) + try testing.expect($(distinct_info_matches(TestOuterAlias, TestInner))) +} + enum_field_struct_defaults test { - names TestNames = { - ident = "identifier", - int = "integer", - } - if field!(names, "ident") |value| { - try testing.expect(value.len == 10) - } else { - try testing.expect(false) - } - if field!(names, "int") |value| { - try testing.expect(value.len == 7) - } else { - try testing.expect(false) - } - if field!(names, "eof") |_| { - try testing.expect(false) - } + names TestNames = { + ident = "identifier", + int = "integer", + } + if field!(names, "ident") |value| { + try testing.expect(value.len == 10) + } else { + try testing.expect(false) + } + if field!(names, "int") |value| { + try testing.expect(value.len == 7) + } else { + try testing.expect(false) + } + if field!(names, "eof") |_| { + try testing.expect(false) + } - empty TestNames = {} - if field!(empty, "ident") |_| { - try testing.expect(false) - } + empty TestNames = {} + if field!(empty, "ident") |_| { + try testing.expect(false) + } } diff --git a/std/process/process.hon b/std/process/process.hon index 7bbeab0..583e5fb 100644 --- a/std/process/process.hon +++ b/std/process/process.hon @@ -1,5 +1,5 @@ import "@std/io" Init :: struct { - io io.Io + io io.Io } diff --git a/std/std.hon b/std/std.hon index 813b250..b500fa8 100644 --- a/std/std.hon +++ b/std/std.hon @@ -1,11 +1,11 @@ import "io" -import "enums" +import "enums/enummap" import "hashmap" import "arraylist" import "strmap" Io :: alias io.Io -EnumMap :: alias enums.EnumMap +EnumMap :: alias enummap.EnumMap ArrayList :: alias arraylist.ArrayList StringHashMap :: alias hashmap.StringHashMap StringMap :: alias strmap.StringMap diff --git a/std/strmap/strmap.hon b/std/strmap/strmap.hon index de45d93..71abf2f 100644 --- a/std/strmap/strmap.hon +++ b/std/strmap/strmap.hon @@ -1,95 +1,95 @@ import "@std/mem" StringMap proc($V type) type { - return struct { - keys [][]u8 - values []V - len_indexes []u32 - min_len u32 - max_len u32 - } + return struct { + keys [][]u8 + values []V + len_indexes []u32 + min_len u32 + max_len u32 + } } hide Pair proc($V type) type { - return struct { []u8, V } + return struct { []u8, V } } #! initializes a static string map from a list of key-value pairs (constructed at compile-time). init proc($V type, $N usize, $entries [N]Pair(V)) StringMap(V) { - if N > usize(maxval!(u32)) { - compile_error!("static string map has too many entries") - } + if N > usize(maxval!(u32)) { + compile_error!("static string map has too many entries") + } - keys [N]mut []u8 = undefined - values [N]mut V = undefined + keys [N]mut []u8 = undefined + values [N]mut V = undefined - # assert no duplicate keys - for entries |entry, i| { - if entry.0.len > usize(maxval!(u32)) { - compile_error!("static string map key is too long") - } + # assert no duplicate keys + for entries |entry, i| { + if entry.0.len > usize(maxval!(u32)) { + compile_error!("static string map key is too long") + } - for (0..i) |prior| if mem.eql(u8, entry.0, entries[prior].0) { - compile_error!("duplicate static string map key") - } + for (0..i) |prior| if mem.eql(u8, entry.0, entries[prior].0) { + compile_error!("duplicate static string map key") + } - keys[i] = entry.0 - values[i] = entry.1 - } + keys[i] = entry.0 + values[i] = entry.1 + } - if N == 0 { - len_indexes [0]u32 = undefined - return StringMap(V){ - keys = keys[..], - values = values[..], - len_indexes = len_indexes[..], - min_len = 0, - max_len = 0, - } - } + if N == 0 { + len_indexes [0]u32 = undefined + return StringMap(V){ + keys = keys[..], + values = values[..], + len_indexes = len_indexes[..], + min_len = 0, + max_len = 0, + } + } - # fixme: insertion sort is compile-time O(N²); replace if large maps affect builds - for 1..N |i| { - key :: keys[i] - value :: values[i] - j usize = i - while j > 0 and keys[j - 1].len > key.len : j -= 1 { - keys[j] = keys[j - 1] - values[j] = values[j - 1] - } - keys[j] = key - values[j] = value - } + # fixme: insertion sort is compile-time O(N²); replace if large maps affect builds + for 1..N |i| { + key :: keys[i] + value :: values[i] + j usize = i + while j > 0 and keys[j - 1].len > key.len : j -= 1 { + keys[j] = keys[j - 1] + values[j] = values[j - 1] + } + keys[j] = key + values[j] = value + } - min_len u32 :: u32(keys[0].len) - max_len u32 :: u32(keys[N - 1].len) - len_indexes [usize(max_len) + 1]mut u32 = undefined - entry_index usize = 0 - for 0..=usize(max_len) |length| { - while entry_index < N and keys[entry_index].len < length : entry_index += 1 {} - len_indexes[length] = u32(entry_index) - } + min_len u32 :: u32(keys[0].len) + max_len u32 :: u32(keys[N - 1].len) + len_indexes [usize(max_len) + 1]mut u32 = undefined + entry_index usize = 0 + for 0..=usize(max_len) |length| { + while entry_index < N and keys[entry_index].len < length : entry_index += 1 {} + len_indexes[length] = u32(entry_index) + } - return StringMap(V) { - keys = keys[..], - values = values[..], - len_indexes = len_indexes[..], - min_len = min_len, - max_len = max_len, - } + return StringMap(V) { + keys = keys[..], + values = values[..], + len_indexes = len_indexes[..], + min_len = min_len, + max_len = max_len, + } } get proc($V type, map @StringMap(V), key []u8) ?V { - if (map.keys.len == 0 or key.len > maxval!(u32)) return null + if (map.keys.len == 0 or key.len > maxval!(u32)) return null - length u32 = u32(key.len) - if (length < map.min_len or length > map.max_len) return null + length u32 = u32(key.len) + if (length < map.min_len or length > map.max_len) return null - idx usize = usize(map.len_indexes[usize(length)]) - while idx < map.keys.len : idx += 1 { - candidate :: map.keys[idx] - if (candidate.len != key.len) return null # key not found - if mem.eql(u8, candidate, key) return map.values[idx] - } - return null + idx usize = usize(map.len_indexes[usize(length)]) + while idx < map.keys.len : idx += 1 { + candidate :: map.keys[idx] + if (candidate.len != key.len) return null # key not found + if mem.eql(u8, candidate, key) return map.values[idx] + } + return null } diff --git a/std/strmap/strmap.test.hon b/std/strmap/strmap.test.hon index e69de29..044a482 100644 --- a/std/strmap/strmap.test.hon +++ b/std/strmap/strmap.test.hon @@ -0,0 +1 @@ +# todo diff --git a/std/testing/testing.hon b/std/testing/testing.hon index 77ab4b7..d820fef 100644 --- a/std/testing/testing.hon +++ b/std/testing/testing.hon @@ -1,85 +1,85 @@ -import "@std/debug" -import "@std/mem" +import "@std/debug" +import "@std/mem" -Error :: enum { - expectation_failed +Error :: enum { + expectation_failed } -SourceLocation :: struct { - file []u8 - line usize - column usize +SourceLocation :: struct { + file []u8 + line usize + column usize } -expect proc(condition bool, location SourceLocation) void ! Error { - if !condition { - debug.print("{s}:{d}:{d}: expectation failed\n", { - location.file, - location.line, - location.column, - }) - return .expectation_failed - } +expect proc(condition bool, location SourceLocation) void ! Error { + if !condition { + debug.print("{s}:{d}:{d}: expectation failed\n", { + location.file, + location.line, + location.column, + }) + return .expectation_failed + } } -expect_equal proc($T type, expected, actual T, location SourceLocation) void ! Error { - match typeinfo!(T) { - .optional: { - if expected |expected_value| { - if actual |actual_value| { - try expect_equal(expected_value, actual_value, location) - return - } - debug.print("{s}:{d}:{d}: expected an optional value, found null\n", { - location.file, - location.line, - location.column, - }) - return .expectation_failed - } - if actual |_| { - debug.print("{s}:{d}:{d}: expected null, found an optional value\n", { - location.file, - location.line, - location.column, - }) - return .expectation_failed - } - } - .slice: if !mem.eql(expected, actual) { - debug.print("{s}:{d}:{d}: expected and actual slices differ\n", { - location.file, - location.line, - location.column, - }) - return .expectation_failed - } - else: if expected != actual { - debug.print("{s}:{d}:{d}: expected {}, found {}\n", { - location.file, - location.line, - location.column, - expected, - actual, - }) - return .expectation_failed - } - } +expect_equal proc($T type, expected, actual T, location SourceLocation) void ! Error { + match typeinfo!(T) { + .optional: { + if expected |expected_value| { + if actual |actual_value| { + try expect_equal(expected_value, actual_value, location) + return + } + debug.print("{s}:{d}:{d}: expected an optional value, found null\n", { + location.file, + location.line, + location.column, + }) + return .expectation_failed + } + if actual |_| { + debug.print("{s}:{d}:{d}: expected null, found an optional value\n", { + location.file, + location.line, + location.column, + }) + return .expectation_failed + } + } + .slice: if !mem.eql(expected, actual) { + debug.print("{s}:{d}:{d}: expected and actual slices differ\n", { + location.file, + location.line, + location.column, + }) + return .expectation_failed + } + else: if expected != actual { + debug.print("{s}:{d}:{d}: expected {}, found {}\n", { + location.file, + location.line, + location.column, + expected, + actual, + }) + return .expectation_failed + } + } } -expect_type proc($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error { - try expect($(Expected == Actual), location) +expect_type proc($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error { + try expect($(Expected == Actual), location) } -run proc(name []u8, callback *proc() void ! Error) bool { - callback() catch |_| { - debug.print("{s}...[failed]\n", {name,}) - return false - } - debug.print("{s}...[ok]\n", {name,}) - return true +run proc(name []u8, callback *proc() void ! Error) bool { + callback() catch |_| { + debug.print("{s}...[failed]\n", {name,}) + return false + } + debug.print("{s}...[ok]\n", {name,}) + return true } -summary proc(passed, failed i32) void { - debug.print("{d} passed, {d} failed\n", {passed, failed}) +summary proc(passed, failed i32) void { + debug.print("{d} passed, {d} failed\n", {passed, failed}) }