diff --git a/source/lexer/lexer.hon b/source/lexer/lexer.hon index b56f5f7..6bfd666 100644 --- a/source/lexer/lexer.hon +++ b/source/lexer/lexer.hon @@ -1,15 +1,18 @@ import "@std" import "@std/mem" import "@std/arraylist" -#import "@std/static_string_map" +import "@std/strmap" -#keywords std.StaticStringMap(TokenKind) :: static_string_map.init([ -# { "func", .func }, -# { "return", .return }, -# { "if", .if }, -# { "for", .for }, -# { "else", .else }, -#]) +import "@source/strpool" + +keywords std.StaticStringMap(TokenKind) :: strmap.init([ + { "proc", .proc }, + { "return", .return }, + { "if", .if }, + { "for", .for }, + { "else", .else }, + { "while", .while }, +]) TokenIndex :: alias usize @@ -23,19 +26,19 @@ State :: struct { diagnostics std.ArrayList(ScanDiagnostic) } -init func(allocator mem.Allocator) State { +init proc(allocator mem.Allocator) State { return State { tokens = arraylist.init(allocator), diagnostics = arraylist.init(allocator), } } -deinit func(state @mut State) void { +deinit proc(state @mut State) void { arraylist.deinit(&state.tokens) arraylist.deinit(&state.diagnostics) } -scan func(state @mut State, input []u8) void ! mem.AllocError { +scan proc(state @mut State, input []u8) void ! (mem.AllocError | strpool.InternError) { tokens :: &state.tokens diagnostics :: &state.diagnostics @@ -56,7 +59,6 @@ scan func(state @mut State, input []u8) void ! mem.AllocError { # comments if char == '#' { while cursor < input.len and input[cursor] != '\n' : cursor += 1 {} - cursor += 1 continue } @@ -64,11 +66,27 @@ scan func(state @mut State, input []u8) void ! mem.AllocError { if is_alpha(char) or char == '_' { start :: cursor cursor += 1 - while cursor < input.len and (is_alpha(input[cursor]) or is_digit(input[cursor]) or input[cursor] == '_') { - cursor += 1 - } - kind :: ident_keyword_map(input[start..cursor]) - try arraylist.append(tokens, Token{ kind = kind, start = start }) + + # scan whole identifier + while (cursor < input.len and ( + is_alpha(input[cursor]) or + is_digit(input[cursor]) or + input[cursor] == '_' + )) : cursor += 1 {} + + kind :: strmap.get(&keywords, input[start..cursor]) orelse .ident + + # don't intern keywords (already O(1) lookup via token kind) + str_id :: if (kind == .ident) + try strpool.intern(&strpool.strings, input[start..cursor]) + else + strpool.NoId + + try arraylist.append(tokens, Token{ + kind = kind, + start = start, + str_id = str_id + }) continue } @@ -89,7 +107,7 @@ scan func(state @mut State, input []u8) void ! mem.AllocError { } # assert that decimals follow the decimal point - if has_decimal and cursor < input.len and !is_digit(input[cursor]) { + 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" }) @@ -167,47 +185,26 @@ scan func(state @mut State, input []u8) void ! mem.AllocError { token :: tokens.items.len try arraylist.append(tokens, Token{ kind = .invalid, start = cursor }) try arraylist.append(diagnostics, ScanDiagnostic{ token = token, message = "invalid character" }) + cursor += 1 } try arraylist.append(tokens, Token{ kind = .eof, start = cursor }) } -hide is_whitespace func(char u8) bool { +hide is_whitespace proc(char u8) bool { return char == ' ' or char == '\t' or char == '\n' or char == '\r' } -hide is_alpha func(char u8) bool { +hide is_alpha proc(char u8) bool { return match char { 'a'..='z', 'A'..='Z': true else: false } } -hide is_digit func(char u8) bool { +hide is_digit proc(char u8) bool { return match char { '0'..='9': true else: false } } - -# fixme: replace with a static string map once implemented -hide ident_keyword_map func(ident []u8) TokenKind { - if mem.eql(u8, ident, "if") return .if - if mem.eql(u8, ident, "else") return .else - if mem.eql(u8, ident, "for") return .for - if mem.eql(u8, ident, "while") return .while - if mem.eql(u8, ident, "func") return .func - if mem.eql(u8, ident, "return") return .return - return .ident -} - -# fixme(brolang): this function fails to infer the enum type from the return values due to the optional -#hide ident_keyword_map func(ident []u8) ?TokenKind { -# if mem.eql(u8, ident, "if") return .if -# if mem.eql(u8, ident, "else") return .else -# if mem.eql(u8, ident, "for") return .for -# if mem.eql(u8, ident, "while") return .while -# if mem.eql(u8, ident, "func") return .func -# if mem.eql(u8, ident, "return") return .return -# return null -#} diff --git a/source/lexer/lexer.test.hon b/source/lexer/lexer.test.hon new file mode 100644 index 0000000..6024927 --- /dev/null +++ b/source/lexer/lexer.test.hon @@ -0,0 +1,22 @@ +import "@source/strpool" +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) + + 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)?) +} diff --git a/source/lexer/token.hon b/source/lexer/token.hon index 85fd881..53b215c 100644 --- a/source/lexer/token.hon +++ b/source/lexer/token.hon @@ -1,9 +1,11 @@ +import "@source/strpool" + TokenKind :: enum { if else for while - func + proc return ident @@ -28,4 +30,5 @@ TokenKind :: enum { Token :: struct { kind TokenKind start int + str_id strpool.StringId = strpool.NoId } diff --git a/source/main.hon b/source/main.hon index 02711af..7afd67e 100644 --- a/source/main.hon +++ b/source/main.hon @@ -4,12 +4,13 @@ import "@std/mem" test import "@std/enums" test import "@std/arraylist" test import "@std/hashmap" -test import "@std/static_string_map" +test import "@std/strmap" import "@source/strpool" import "@source/lexer" test import "@source/strpool" +test import "@source/lexer" program :: `# these are immutable @@ -24,17 +25,14 @@ program :: `else `for `while - `func + `proc `return ` - `main func() void {} + `main proc() void {} -# cross-cutting concern, hence global singleton -strings StringPool = undefined - -main func() void { - strings = strpool.init(mem.c_allocator) - defer strpool.deinit(&strings) +main proc() void { + strpool.strings = strpool.init(mem.c_allocator) + defer strpool.deinit(&strpool.strings) debug.print("PROGRAM::[[\n{}\n]]\n\n", {program}) diff --git a/source/strpool/strpool.hon b/source/strpool/strpool.hon index 678bb27..a21ba6c 100644 --- a/source/strpool/strpool.hon +++ b/source/strpool/strpool.hon @@ -3,7 +3,13 @@ import "@std/arraylist" import "@std/hashmap" import "@std/testing" -StringId :: alias usize +# cross-cutting concern, hence global singleton (owned by main.hon) +strings StringPool = undefined + +InternError :: enum { out_of_space } + +StringId :: alias u32 +NoId :: maxval!(StringId) StringPool :: struct { ids hashmap.StringHashMap(StringId) # string → id @@ -11,7 +17,7 @@ StringPool :: struct { allocator mem.Allocator } -init func(allocator mem.Allocator) StringPool { +init proc(allocator mem.Allocator) StringPool { return StringPool{ ids = hashmap.init(allocator), strings = arraylist.init(allocator), @@ -19,7 +25,7 @@ init func(allocator mem.Allocator) StringPool { } } -deinit func(pool @mut StringPool) void { +deinit proc(pool @mut StringPool) void { hashmap.deinit(&pool.ids) for pool.strings.items |str| { mem.free(pool.allocator, str) @@ -27,10 +33,12 @@ deinit func(pool @mut StringPool) void { arraylist.deinit(&pool.strings) } -intern func(pool @mut StringPool, str []u8) StringId ! mem.AllocError { +intern proc(pool @mut StringPool, str []u8) StringId ! (mem.AllocError | InternError) { if hashmap.get(&pool.ids, str) |id| return id - id :: pool.strings.items.len + if (pool.strings.items.len >= usize(NoId)) 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) @@ -48,12 +56,12 @@ intern func(pool @mut StringPool, str []u8) StringId ! mem.AllocError { return id } -get_str func(pool @StringPool, id StringId) ?[]u8 { - if (id >= pool.strings.items.len) return null - return pool.strings.items[id] +get_str proc(pool @StringPool, id StringId) ?[]u8 { + if (usize(id) >= pool.strings.items.len) return null + return pool.strings.items[usize(id)] } -get_id func(pool @StringPool, str []u8) ?StringId { +get_id proc(pool @StringPool, str []u8) ?StringId { return hashmap.get(&pool.ids, str) } diff --git a/std/arraylist/arraylist.hon b/std/arraylist/arraylist.hon index 4eb28d1..64e141e 100644 --- a/std/arraylist/arraylist.hon +++ b/std/arraylist/arraylist.hon @@ -1,6 +1,6 @@ import "@std/mem" -ArrayList func($T type) type { +ArrayList proc($T type) type { return struct { items []mut T capacity usize @@ -8,7 +8,7 @@ ArrayList func($T type) type { } } -init func($T type, allocator mem.Allocator) ArrayList(T) { +init proc($T type, allocator mem.Allocator) ArrayList(T) { return ArrayList(T) { items = mem.empty(T), capacity = 0, @@ -16,14 +16,14 @@ init func($T type, allocator mem.Allocator) ArrayList(T) { } } -deinit func($T type, list @mut ArrayList(T)) void { +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 } -reserve func($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.AllocError { +reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.AllocError { if min_capacity <= list.capacity { return } @@ -51,7 +51,7 @@ reserve func($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.All return } -append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError { +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 @@ -62,13 +62,13 @@ append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError { return } -pop func($T type, list @mut ArrayList(T)) ?T { +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 } -clear func($T type, list @mut ArrayList(T)) void { +clear proc($T type, list @mut ArrayList(T)) void { list.items = list.items.ptr[..0] } diff --git a/std/debug/debug.hon b/std/debug/debug.hon index fa1f863..df5247f 100644 --- a/std/debug/debug.hon +++ b/std/debug/debug.hon @@ -1,7 +1,7 @@ import "@ffi/c" import "@std/io" -print func($format []u8, $Args type, args Args) void { +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) }, @@ -10,7 +10,7 @@ print func($format []u8, $Args type, args Args) void { io.print(writer, format, Args, args) catch |_| {} } -hide write func(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError { +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 { diff --git a/std/enums/enums.hon b/std/enums/enums.hon index d6a2624..3d11167 100644 --- a/std/enums/enums.hon +++ b/std/enums/enums.hon @@ -1,6 +1,6 @@ import "@std/meta" -EnumMap func($E, $V type) type { +EnumMap proc($E, $V type) type { match typeinfo!(E) { .enum |info|: return struct { present [info.fields.len]mut bool # fixme: replace with bitset @@ -10,7 +10,7 @@ EnumMap func($E, $V type) type { } } -init func( +init proc( $E, $V type, values meta.EnumFieldStruct(E, ?V, some!(null)), ) EnumMap(E, V) { @@ -31,7 +31,7 @@ init func( return map } -get func($E, $V type, map @EnumMap(E, V), key E) ?V { +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| { diff --git a/std/hashmap/hashmap.hon b/std/hashmap/hashmap.hon index 0080d05..1035c76 100644 --- a/std/hashmap/hashmap.hon +++ b/std/hashmap/hashmap.hon @@ -2,7 +2,7 @@ import "@std/mem" PutError :: enum { key_exists } -Entry func($K, $V type) type { +Entry proc($K, $V type) type { return struct { # hash = 0 means empty hash usize = 0 @@ -11,10 +11,10 @@ Entry func($K, $V type) type { } } -HashMap func( +HashMap proc( $K, $V type, - $hash_key func(key K) usize, - $keys_eql func(a, b K) bool, + $hash_key proc(key K) usize, + $keys_eql proc(a, b K) bool, ) type { return struct { entries []mut Entry(K, V) @@ -23,14 +23,14 @@ HashMap func( } } -StringHashMap func($V type) type { +StringHashMap proc($V type) type { return HashMap([]u8, V, str_hash, str_eql) } -init func( +init proc( $K, $V type, - $hash_key func(key K) usize, - $keys_eql func(a, b K) bool, + $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){ @@ -42,17 +42,17 @@ init func( #! free the entries in the hash map. #! note: this operation invalidates the map. -deinit func( +deinit proc( $K, $V type, - $hash_key func(key K) usize, - $keys_eql func(a, b K) bool, + $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 func( +get proc( $K, $V type, - $hash_key func(key K) usize, - $keys_eql func(a, b K) bool, + $hash_key proc(key K) usize, + $keys_eql proc(a, b K) bool, map @HashMap(K, V, hash_key, keys_eql), key K, ) ?V { @@ -73,10 +73,10 @@ get func( } } -put func( +put proc( $K, $V type, - $hash_key func(key K) usize, - $keys_eql func(a, b K) bool, + $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, @@ -89,9 +89,7 @@ put func( 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 - } + for (0..new_entries.len) |i| new_entries[i].hash = 0 # move old entries for old_entries |entry| { @@ -126,7 +124,7 @@ put func( return } - if (entry.hash == hash and keys_eql(entry.key, key)) { + if entry.hash == hash and keys_eql(entry.key, key) { return .key_exists } @@ -134,7 +132,7 @@ put func( } } -hide normalize func(hash usize) usize { +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 @@ -143,7 +141,7 @@ hide normalize func(hash usize) usize { #! FNV-1a hash implementation. #! note: vulnerable to collision attacks. -hide str_hash func(key []u8) usize { +hide str_hash proc(key []u8) usize { hash u32 = 2166136261 # offset basis prime u32 = 16777619 @@ -155,6 +153,4 @@ hide str_hash func(key []u8) usize { return usize(hash) } -hide str_eql func(a, b []u8) bool { - return mem.eql(a, b) -} +hide str_eql proc(a, b []u8) bool { return mem.eql(a, b) } diff --git a/std/io/file.hon b/std/io/file.hon index e1a37aa..6b824a2 100644 --- a/std/io/file.hon +++ b/std/io/file.hon @@ -19,18 +19,16 @@ CloseError :: enum { close_failed } -open func(io Io, path [;0]u8, mode FileMode) File ! OpenError { - handle Handle :: io.vtable.open(io.context, path, mode) catch |err| { - return err - } - return File {io = io, handle = handle} +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 } } -close func(file File) void ! CloseError { +close proc(file File) void ! CloseError { try file.io.vtable.close(file.io.context, file.handle) } -reader func(file File) Reader { +reader proc(file File) Reader { return Reader { context = file.io.context, handle = file.handle, @@ -38,7 +36,7 @@ reader func(file File) Reader { } } -writer func(file File) Writer { +writer proc(file File) Writer { return Writer { context = file.io.context, handle = file.handle, @@ -46,51 +44,39 @@ writer func(file File) Writer { } } -hide system_read func(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError { +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 - } + 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) - } + 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 - } + if (errno == c.EINTR) continue + if (errno == c.EBADF) return .not_open_for_reading return .read_failed } } -hide system_write func(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError { +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 - } + 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 (count >= 0) return usize(count) + errno c_int :: c.__error()?^ - if errno == c.EINTR { - continue - } - if errno == c.EBADF { - return .not_open_for_writing - } + if (errno == c.EINTR) continue + if (errno == c.EBADF) return .not_open_for_writing return .write_failed } } -hide system_open func(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError { +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 @@ -99,31 +85,25 @@ hide system_open func(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! Op } 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 - } + if (fd >= 0) return Handle{ file_desc = fd } + if (c.__error()?^ != c.EINTR) return .open_failed } } -hide system_close func(_ ?@mut anyopaque, handle Handle) void ! CloseError { - if c.close(handle.file_desc) != 0 { - return .close_failed - } +hide system_close proc(_ ?@mut anyopaque, handle Handle) void ! CloseError { + if (c.close(handle.file_desc) != 0) return .close_failed } -hide system_stdin func(_ ?@mut anyopaque) Handle { - return Handle {file_desc = c_int(Stream.stdin)} +hide system_stdin proc(_ ?@mut anyopaque) Handle { + return Handle{ file_desc = c_int(Stream.stdin) } } -hide system_stdout func(_ ?@mut anyopaque) Handle { - return Handle {file_desc = c_int(Stream.stdout)} +hide system_stdout proc(_ ?@mut anyopaque) Handle { + return Handle{ file_desc = c_int(Stream.stdout) } } -hide system_stderr func(_ ?@mut anyopaque) Handle { - return Handle {file_desc = c_int(Stream.stderr)} +hide system_stderr proc(_ ?@mut anyopaque) Handle { + return Handle{ file_desc = c_int(Stream.stderr) } } hide system_vtable IoVTable :: IoVTable { @@ -136,7 +116,7 @@ hide system_vtable IoVTable :: IoVTable { stderr = system_stderr, } -hide system func() Io { +hide system proc() Io { return Io { context = null, vtable = &system_vtable, diff --git a/std/io/io.hon b/std/io/io.hon index 11e2c4b..5a70f50 100644 --- a/std/io/io.hon +++ b/std/io/io.hon @@ -29,64 +29,55 @@ Io :: struct { } IoVTable :: struct { - read @func(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError - write @func(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError - open @func(context ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError - close @func(context ?@mut anyopaque, handle Handle) void ! CloseError - stdin @func(context ?@mut anyopaque) Handle - stdout @func(context ?@mut anyopaque) Handle - stderr @func(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 @func(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError + read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError } Writer :: struct { context ?@mut anyopaque handle Handle - write @func(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError + write @proc(context ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError } -read func(input Reader, buffer []mut u8) usize ! ReadError { - if buffer.len == 0 { - return 0 - } +read proc(input Reader, buffer []mut u8) usize ! ReadError { + if (buffer.len == 0) return 0 + count usize :: try input.read(input.context, input.handle, buffer) - if count > buffer.len { - return .read_failed - } + if (count > buffer.len) return .read_failed + return count } -write func(output Writer, bytes []u8) usize ! WriteError { - if bytes.len == 0 { - return 0 - } +write proc(output Writer, bytes []u8) usize ! WriteError { + if (bytes.len == 0) return 0 + count usize :: try output.write(output.context, output.handle, bytes) - if count > bytes.len { - return .write_failed - } + if (count > bytes.len) return .write_failed + return count } -write_all func(output Writer, bytes []u8) void ! WriteError { +write_all proc(output Writer, bytes []u8) void ! WriteError { offset usize = 0 while offset < bytes.len { - count usize :: write(output, bytes[offset..]) catch |err| { - return err - } - if count == 0 { - return .no_progress - } + count usize :: try write(output, bytes[offset..]) + if (count == 0) return .no_progress offset += count } - return } -stdin func(io Io) Reader { +stdin proc(io Io) Reader { return Reader { context = io.context, handle = io.vtable.stdin(io.context), @@ -94,7 +85,7 @@ stdin func(io Io) Reader { } } -stdout func(io Io) Writer { +stdout proc(io Io) Writer { return Writer { context = io.context, handle = io.vtable.stdout(io.context), @@ -102,7 +93,7 @@ stdout func(io Io) Writer { } } -stderr func(io Io) Writer { +stderr proc(io Io) Writer { return Writer { context = io.context, handle = io.vtable.stderr(io.context), @@ -110,7 +101,7 @@ stderr func(io Io) Writer { } } -print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError { +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 @@ -128,60 +119,58 @@ print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError } } -hide write_integer_signed func(output Writer, value i64, base u64, uppercase bool) void ! WriteError { +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 + while true { digit_value i64 :: rem!(current, i64(base)) - digit u8 = 0 - if digit_value < 0 { - digit = u8(-digit_value) - } else { - digit = u8(digit_value) - } + digit u8 = if (digit_value < 0) + u8(-digit_value) + else + u8(digit_value) + end -= 1 - if digit < 10 { - buffer[end] = '0' + digit - } else if uppercase { - buffer[end] = 'A' + digit - 10 - } else { - buffer[end] = 'a' + digit - 10 - } + 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 (current == 0) break } + if value < 0 { end -= 1 buffer[end] = '-' - } + } try write_all(output, buffer[end..]) - return } -hide write_integer_unsigned func(output Writer, value u64, base u64, uppercase bool) void ! WriteError { +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 + while true { digit u8 :: u8(rem!(current, base)) + end -= 1 - if digit < 10 { - buffer[end] = '0' + digit - } else if uppercase { - buffer[end] = 'A' + digit - 10 - } else { - buffer[end] = 'a' + digit - 10 - } + 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 - } + if (current == 0) break } + try write_all(output, buffer[end..]) - return } hide FormatTokenKind :: enum { @@ -205,18 +194,17 @@ hide FormatToken :: struct { field []u8 } -hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken { +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[index] = FormatToken{ kind = .unused, start = 0, end = 0, field = "" } } + field_count usize = 0 match typeinfo!(Args) { - .record |record|: { - if !record.is_tuple { - compile_error!("io.print arguments must be a tuple") - } - field_count = record.fields.len + .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") } @@ -232,68 +220,84 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken { compile_error!("io.print format has an unmatched '{'") } if cursor > literal_start { - tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, field = ""} + 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 = ""} + 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") } + 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 (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") } - tokens[token_count] = FormatToken { + + 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 } + 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 = ""} + 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 = ""} + tokens[token_count] = FormatToken{ + kind = .literal, + start = cursor, + end = cursor + 1, + field = "", + } token_count += 1 cursor += 2 literal_start = cursor @@ -301,23 +305,30 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken { } cursor += 1 } + if literal_start < format.len { - tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = format.len, field = ""} + 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 } -hide format_field_name func($T type, index usize) []u8 { +hide format_field_name proc($T type, index usize) []u8 { match typeinfo!(T) { - .record |record|: return record.fields[index].name + .record |r|: return r.fields[index].name else: compile_error!("io.print arguments must be a tuple") } } -hide write_integer func(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError { +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) @@ -326,46 +337,38 @@ hide write_integer func(output Writer, $T type, value T, base u64, uppercase boo } else: compile_error!("io.print integer format requires an integer argument") } - return } # note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters. -hide write_float func(output Writer, $T type, value T, scientific bool) void ! WriteError { +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) - } + 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 - } + 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") } - return } -hide write_decimal func(output Writer, $T type, value T) void ! WriteError { +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") } - return } -hide write_character func(output Writer, $T type, value T) void ! WriteError { +hide write_character proc(output Writer, $T type, value T) void ! WriteError { match typeinfo!(T) { .integer: { if minval!(T) < 0 or maxval!(T) > 255 { @@ -376,10 +379,9 @@ hide write_character func(output Writer, $T type, value T) void ! WriteError { } else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8") } - return } -hide write_default func(output Writer, $T type, value T) void ! WriteError { +hide write_default proc(output Writer, $T type, value T) void ! WriteError { match typeinfo!(T) { .bool: if value { try write_all(output, "true") @@ -403,5 +405,4 @@ hide write_default func(output Writer, $T type, value T) void ! WriteError { } else: compile_error!("io.print '{}' does not support this argument type") } - return } diff --git a/std/mem/mem.hon b/std/mem/mem.hon index 555065c..8cc526b 100644 --- a/std/mem/mem.hon +++ b/std/mem/mem.hon @@ -10,24 +10,24 @@ Allocator :: struct { } AllocatorVTable :: struct { - alloc @func(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8 - realloc @func(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 - free @func(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 func(allocator Allocator, size usize, alignment usize) ?*mut u8 { +raw_alloc proc(allocator Allocator, size usize, alignment usize) ?*mut u8 { return allocator.vtable.alloc(allocator.context, size, alignment) } -raw_realloc func(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { +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) } -raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void { +raw_free proc(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void { allocator.vtable.free(allocator.context, memory, size, alignment) } -eql func($T type, left, right []T) bool { +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]) { return false @@ -36,7 +36,7 @@ eql func($T type, left, right []T) bool { } #! allocate memory for a slice of type `T` with `count` elements. -alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError { +alloc proc($T type, allocator Allocator, count usize) []mut T ! AllocError { if (count == 0) return empty_slice(T, 0) element_size usize :: sizeof!(T) @@ -51,13 +51,14 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError { pointer *mut T :: ptrcast!(T, bytes) return pointer[..count] } + 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 func($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError { +realloc proc($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError { if new_count == memory.len { return memory } @@ -68,12 +69,8 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu } 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 - } + 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 @@ -98,7 +95,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu #! 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 func($T type, allocator Allocator, memory []T) void { +free proc($T type, allocator Allocator, memory []T) void { if (memory.len == 0 or sizeof!(T) == 0) return raw_free(allocator, ptrcast!( u8, @@ -109,21 +106,21 @@ free func($T type, allocator Allocator, memory []T) void { } #! get an empty slice of type `T` with `count` elements. -empty_slice func($T type, count usize) []mut T { +empty_slice proc($T type, count usize) []mut T { pointer *mut T :: ptrcast!(T, (&empty_storage).ptr) return pointer[..count] } #! get an empty slice of type `T` with 0 elements. -empty func($T type) []mut T { +empty proc($T type) []mut T { return empty_slice(T, 0) } hide empty_storage [1]mut u64 = [0] -hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption. +hide malloc_alignment usize :: 16 # note: aarch64-macos libc malloc alignment assumption. -hide power_of_two func(value usize) bool { +hide power_of_two proc(value usize) bool { if (value == 0) return false current usize = value while current > 1 { @@ -134,12 +131,9 @@ hide power_of_two func(value usize) bool { return true } -hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 { +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 (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)) @@ -148,7 +142,13 @@ hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 { return ptrcast!(u8, memory[0]) } -hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 { +hide c_realloc proc( + _ ?@mut anyopaque, + memory ?*mut u8, + old_size usize, + new_size usize, + alignment usize, +) ?*mut u8 { if (power_of_two(alignment) == false) return null if new_size == 0 { @@ -174,7 +174,7 @@ hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size return c_alloc(null, new_size, alignment) } -hide c_free func(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void { +hide c_free proc(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void { c.free(memory) } diff --git a/std/meta/meta.hon b/std/meta/meta.hon index 5c6bcd0..efe3676 100644 --- a/std/meta/meta.hon +++ b/std/meta/meta.hon @@ -43,7 +43,7 @@ TypeInfo :: union(enum) { distinct void } -EnumFieldStruct func($E, $Field type, $default ?Field) type { +EnumFieldStruct proc($E, $Field type, $default ?Field) type { match typeinfo!(E) { .enum |info|: { names [info.fields.len]mut []u8 = undefined diff --git a/std/meta/meta.test.hon b/std/meta/meta.test.hon index 69a2e56..1e8f248 100644 --- a/std/meta/meta.test.hon +++ b/std/meta/meta.test.hon @@ -9,7 +9,7 @@ TestTokenKind :: enum(u8) { TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null)) TestArrayAlias :: alias [3]u16 -hide array_info_matches func($Array, $Child type, $len usize) bool { +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 diff --git a/std/std.hon b/std/std.hon index 16b47ba..0c05558 100644 --- a/std/std.hon +++ b/std/std.hon @@ -2,10 +2,10 @@ import "io" import "enums" import "hashmap" import "arraylist" -import "static_string_map" +import "strmap" Io :: alias io.Io EnumMap :: alias enums.EnumMap ArrayList :: alias arraylist.ArrayList StringHashMap :: alias hashmap.StringHashMap -StaticStringMap :: alias static_string_map.StaticStringMap +StaticStringMap :: alias strmap.StaticStringMap diff --git a/std/static_string_map/static_string_map.hon b/std/strmap/strmap.hon similarity index 85% rename from std/static_string_map/static_string_map.hon rename to std/strmap/strmap.hon index be282bd..e8022cd 100644 --- a/std/static_string_map/static_string_map.hon +++ b/std/strmap/strmap.hon @@ -1,6 +1,6 @@ import "@std/mem" -StaticStringMap func($V type) type { +StaticStringMap proc($V type) type { return struct { keys [][]u8 values []V @@ -10,11 +10,11 @@ StaticStringMap func($V type) type { } } -hide Pair func($V type) type { +hide Pair proc($V type) type { return struct { []u8, V } } -init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) { +init proc($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) { if N > usize(maxval!(u32)) { compile_error!("static string map has too many entries") } @@ -64,7 +64,7 @@ init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) { 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| { # fixme: for casts and function calls, we should be able to omit the surrounding parentheses in the range + 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) } @@ -78,7 +78,7 @@ init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) { } } -get func($V type, map @StaticStringMap(V), key []u8) ?V { +get proc($V type, map @StaticStringMap(V), key []u8) ?V { if (map.keys.len == 0 or key.len > maxval!(u32)) return null length u32 = u32(key.len) @@ -90,4 +90,5 @@ get func($V type, map @StaticStringMap(V), key []u8) ?V { if (candidate.len != key.len) return null if mem.eql(u8, candidate, key) return map.values[idx] } + return null } diff --git a/std/static_string_map/static_string_map.test.hon b/std/strmap/strmap.test.hon similarity index 100% rename from std/static_string_map/static_string_map.test.hon rename to std/strmap/strmap.test.hon diff --git a/std/testing/testing.hon b/std/testing/testing.hon index 76188ca..77ab4b7 100644 --- a/std/testing/testing.hon +++ b/std/testing/testing.hon @@ -11,7 +11,7 @@ SourceLocation :: struct { column usize } -expect func(condition bool, location SourceLocation) void ! Error { +expect proc(condition bool, location SourceLocation) void ! Error { if !condition { debug.print("{s}:{d}:{d}: expectation failed\n", { location.file, @@ -22,7 +22,7 @@ expect func(condition bool, location SourceLocation) void ! Error { } } -expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error { +expect_equal proc($T type, expected, actual T, location SourceLocation) void ! Error { match typeinfo!(T) { .optional: { if expected |expected_value| { @@ -67,11 +67,11 @@ expect_equal func($T type, expected, actual T, location SourceLocation) void ! E } } -expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error { +expect_type proc($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error { try expect($(Expected == Actual), location) } -run func(name []u8, callback *func() void ! Error) bool { +run proc(name []u8, callback *proc() void ! Error) bool { callback() catch |_| { debug.print("{s}...[failed]\n", {name,}) return false @@ -80,6 +80,6 @@ run func(name []u8, callback *func() void ! Error) bool { return true } -summary func(passed, failed i32) void { +summary proc(passed, failed i32) void { debug.print("{d} passed, {d} failed\n", {passed, failed}) }