finished translating string operations to odin (next is file system)

This commit is contained in:
2025-07-20 19:59:22 -04:00
parent 26a4511701
commit 70518120f8
4 changed files with 150 additions and 209 deletions

View File

@@ -1680,7 +1680,7 @@ Str8 str8__fmt(Str8 fmt_template, Slice_A2_Str8* entries) {
inline
void str8cache__init(Str8Cache* cache, Opts_str8cache_init* opts) {
assert(cache != nullptr);
assert(opts != nullptr);
assert(opts != nullptr);
assert(opts->str_reserve.proc != nullptr);
assert(opts->cell_reserve.proc != nullptr);
assert(opts->tbl_backing.proc != nullptr);
@@ -1783,7 +1783,9 @@ void str8gen_append_str8(Str8Gen* gen, Str8 str){
slice_assert(result);
Slice_Byte to_copy = { result.ptr + gen->len, result.len - gen->len };
slice_copy(to_copy, slice_byte(str));
gen->ptr = cast(UTF8*, result.ptr); gen->len = result.len;
gen->ptr = cast(UTF8*, result.ptr);
gen->len += str.len;
gen->cap = max(gen->len , gen->cap); // TODO(Ed): Arenas currently hide total capacity before growth. Problably better todo classic append to actually track this.
}
void str8gen__append_fmt(Str8Gen* gen, Str8 fmt_template, Slice_A2_Str8* entries){
local_persist Byte tbl_mem[kilo(32)]; FArena tbl_arena = farena_make(slice_fmem(tbl_mem));
@@ -1792,8 +1794,8 @@ void str8gen__append_fmt(Str8Gen* gen, Str8 fmt_template, Slice_A2_Str8* entries
if (buffer.len < kilo(16)) {
Slice_Byte result = mem_grow(gen->backing, str8gen_slice_byte(* gen), kilo(16) + gen->cap );
slice_assert(result);
gen->ptr = result.ptr;
gen->cap = result.len;
gen->ptr = result.ptr;
gen->cap += kilo(16);
buffer = (Slice_Byte){ cast(Byte*, gen->ptr + gen->len), gen->cap - gen->len };
}
Str8 result = str8__fmt_kt1l(gen->backing, & buffer, kt, fmt_template);

View File

@@ -13,6 +13,7 @@ main :: proc()
import "base:builtin"
import "base:intrinsics"
import "base:runtime"
//region Package Mappings
abs :: builtin.abs
@@ -20,6 +21,11 @@ min :: builtin.min
max :: builtin.max
clamp :: builtin.clamp
ainfo :: proc {
farena_ainfo,
varena_ainfo,
arena_ainfo,
}
alloc :: proc {
mem_alloc,
alloc_type,
@@ -109,11 +115,11 @@ memory_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -
return dst
}
SliceBytes :: struct {
SliceByte :: struct {
data: [^]byte,
len: int
}
SliceRaw :: struct ($Type: typeid) {
SliceRaw :: struct ($Type: typeid) {
data: [^]Type,
len: int,
}
@@ -125,8 +131,8 @@ slice_assert :: #force_inline proc (s: $SliceType / []$Type) {
}
slice_end :: #force_inline proc "contextless" (s : $SliceType / []$Type) -> ^Type { return & s[len(s) - 1] }
@(require_results) slice_to_bytes :: proc "contextless" (s: []$Type) -> []byte { return ([^]byte)(raw_data(s))[:len(s) * size_of(Type)] }
@(require_results) slice_raw :: proc "contextless" (s: []$Type) -> SliceRaw(Type) { return transmute(SliceRaw(Type)) s }
@(require_results) slice_to_bytes :: proc "contextless" (s: []$Type) -> []byte { return ([^]byte)(raw_data(s))[:len(s) * size_of(Type)] }
@(require_results) slice_raw :: proc "contextless" (s: []$Type) -> SliceRaw(Type) { return transmute(SliceRaw(Type)) s }
slice_zero :: proc "contextless" (data: $SliceType / []$Type) { zero(raw_data(data), size_of(Type) * len(data)) }
slice_copy :: proc "contextless" (dst, src: $SliceType / []$Type) -> int {
@@ -319,7 +325,7 @@ alloc_type :: proc(ainfo: AllocatorInfo, $Type: typeid, alignment: int = MEMORY
ainfo.procedure(input, & output)
return transmute(^Type) raw_data(output.allocation)
}
alloc_slice :: proc(ainfo: AllocatorInfo, $SliceType: []$Type, num : int) -> []Type {
alloc_slice :: proc(ainfo: AllocatorInfo, $SliceType: typeid / []$Type, num : int, alignment: int = MEMORY_ALIGNMENT_DEFAULT, no_zero: b32 = false) -> []Type {
assert(ainfo.procedure != nil)
input := AllocatorProc_In {
data = ainfo.data,
@@ -329,7 +335,7 @@ alloc_slice :: proc(ainfo: AllocatorInfo, $SliceType: []$Type, num : int) -> []T
}
output: AllocatorProc_Out
ainfo.procedure(input, & output)
return transmute([]Type) Raw_Slice { raw_data(output.allocation), num }
return transmute([]Type) slice(raw_data(output.allocation), num)
}
//endregion Allocator Interface
@@ -459,6 +465,7 @@ farena_allocator_proc :: proc(input: AllocatorProc_In, output: ^AllocatorProc_Ou
output.save_point = farena_save(arena^)
}
}
farena_ainfo :: #force_inline proc "contextless" (arena : ^FArena) -> AllocatorInfo { return AllocatorInfo{farena_allocator_proc, arena} }
//endregion FArena
//region OS
@@ -709,6 +716,7 @@ varena_allocator_proc :: proc(input: AllocatorProc_In, output: ^AllocatorProc_Ou
output.save_point = varena_save(vm)
}
}
varena_ainfo :: #force_inline proc "contextless" (arena : ^VArena) -> AllocatorInfo { return AllocatorInfo{varena_allocator_proc, arena} }
//endregion VArena
//region Arena (Chained Arena)
@@ -850,7 +858,7 @@ arena_allocator_proc :: proc(input: AllocatorProc_In, output: ^AllocatorProc_Out
if input.op == .Grow {
zero(cursor(new_alloc)[len(input.old_allocation):], input.requested_size - len(input.old_allocation))
}
output.allocation = slice_to_bytes(new_alloc)
output.allocation = new_alloc
output.continuity_break = true
case .Shrink:
@@ -888,6 +896,7 @@ arena_allocator_proc :: proc(input: AllocatorProc_In, output: ^AllocatorProc_Out
output.save_point = arena_save(arena)
}
}
arena_ainfo :: #force_inline proc "contextless" (arena : ^Arena) -> AllocatorInfo { return AllocatorInfo{arena_allocator_proc, arena} }
//endregion Arena (Casey-Ryan Composite Arena)
//region Hashing
@@ -907,7 +916,7 @@ KT1L_Meta :: struct {
slot_size: uintptr,
kt_value_offset: uintptr,
type_width: uintptr,
type_name: string,
type: typeid,
}
kt1l_populate_slice_a2_Slice_Byte :: proc(kt: ^[]byte, backing: AllocatorInfo, values: []byte, num_values: int, m: KT1L_Meta) {
assert(kt != nil)
@@ -915,14 +924,14 @@ kt1l_populate_slice_a2_Slice_Byte :: proc(kt: ^[]byte, backing: AllocatorInfo, v
table_size_bytes := num_values * int(m.slot_size)
kt^ = mem_alloc(backing, table_size_bytes)
slice_assert(kt ^)
kt_raw : SliceBytes = transmute(SliceBytes) kt^
for cursor in 0 ..< cast(uintptr) num_values {
slot_offset := cursor * m.slot_size // slot id
kt_raw : SliceByte = transmute(SliceByte) kt^
for id in 0 ..< cast(uintptr) num_values {
slot_offset := id * m.slot_size // slot id
slot_cursor := kt_raw.data[slot_offset:] // slots[id] type: KT1L_<Type>
slot_key := cast(^u64) slot_cursor // slots[id].key type: U64
slot_value := slice(slot_cursor[m.kt_value_offset:], m.type_width) // slots[id].value type: <Type>
a2_offset := cursor * m.type_width * 2 // a2 entry id
a2_cursor := slice_cursor(values)[a2_offset:] // a2_entries[id] type: A2_<Type>
a2_offset := id * m.type_width * 2 // a2 entry id
a2_cursor := cursor(values)[a2_offset:] // a2_entries[id] type: A2_<Type>
a2_key := (transmute(^[]byte) a2_cursor) ^ // a2_entries[id].key type: <Type>
a2_value := slice(a2_cursor[m.type_width:], m.type_width) // a2_entries[id].value type: <Type>
copy(slot_value, a2_value) // slots[id].value = a2_entries[id].value
@@ -932,12 +941,12 @@ kt1l_populate_slice_a2_Slice_Byte :: proc(kt: ^[]byte, backing: AllocatorInfo, v
}
kt1l_populate_slice_a2 :: proc($Type: typeid, kt: ^[]KT1L_Slot(Type), backing: AllocatorInfo, values: [][2]Type) {
assert(kt != nil)
values_bytes := transmute([]byte) SliceBytes{data = raw_data(values), len = len(values) * size_of([2]Type)}
values_bytes := slice(transmute([^]u8) raw_data(values), len(values) * size_of([2]Type))
kt1l_populate_slice_a2_Slice_Byte(transmute(^[]byte) kt, backing, values_bytes, len(values), {
slot_size = size_of(KT1L_Slot(Type)),
kt_value_offset = offset_of(KT1L_Slot(Type), KT1L_Slot(Type).value),
kt_value_offset = offset_of(KT1L_Slot(Type), value),
type_width = size_of(Type),
type_name = #type_string(Type),
type = Type,
})
}
//endregion Key Table 1-Layer Linear (KT1L)
@@ -974,7 +983,7 @@ KT1CX_ByteMeta :: struct {
cell_depth: int,
cell_size: int,
type_width: int,
type_name: string,
type: typeid,
}
KT1CX_InfoMeta :: struct {
cell_pool_size: int,
@@ -985,7 +994,7 @@ KT1CX_InfoMeta :: struct {
cell_depth: int,
cell_size: int,
type_width: int,
type_name: string,
type: typeid,
}
KT1CX_Info :: struct {
backing_table: AllocatorInfo,
@@ -999,7 +1008,7 @@ kt1cx_init :: proc(info: KT1CX_Info, m: KT1CX_InfoMeta, result: ^KT1CX_Byte) {
assert(m.cell_pool_size >= 4 * Kilo)
assert(m.table_size >= 4 * Kilo)
assert(m.type_width > 0)
table_raw := transmute(SliceBytes) mem_alloc(info.backing_table, m.table_size * m.cell_size)
table_raw := transmute(SliceByte) mem_alloc(info.backing_table, m.table_size * m.cell_size)
slice_assert(transmute([]byte) table_raw)
result.cell_pool = mem_alloc(info.backing_cells, m.cell_pool_size * m.cell_size)
slice_assert(result.cell_pool)
@@ -1011,7 +1020,7 @@ kt1cx_clear :: proc(kt: KT1CX_Byte, m: KT1CX_ByteMeta) {
table_len := len(kt.table) * m.cell_size
for ; cell_cursor != end(kt.table); cell_cursor = cell_cursor[m.cell_size:] // for cell, cell_id in kt.table.cells
{
slots := SliceBytes { cell_cursor, m.cell_depth * m.slot_size } // slots = cell.slots
slots := SliceByte { cell_cursor, m.cell_depth * m.slot_size } // slots = cell.slots
slot_cursor := slots.data
for;; {
slot := slice(slot_cursor, m.slot_size) // slot = slots[slot_id]
@@ -1065,9 +1074,9 @@ kt1cx_get :: proc(kt: KT1CX_Byte, key: u64, m: KT1CX_ByteMeta) -> ^byte {
kt1cx_set :: proc(kt: KT1CX_Byte, key: u64, value: []byte, backing_cells: AllocatorInfo, m: KT1CX_ByteMeta) -> ^byte {
hash_index := kt1cx_slot_id(kt, key, m)
cell_offset := uintptr(hash_index) * uintptr(m.cell_size)
cell := SliceBytes{& kt.table[cell_offset], m.cell_size}
cell_cursor := cursor(kt.table)[cell_offset:] // KT1CX_Cell(Type) cell = kt.table[hash_index]
{
slots := SliceBytes {cell.data, m.cell_depth * m.slot_size}
slots := SliceByte {cell_cursor, m.cell_depth * m.slot_size} // cell.slots
slot_cursor := slots.data
for ;;
{
@@ -1081,11 +1090,11 @@ kt1cx_set :: proc(kt: KT1CX_Byte, key: u64, value: []byte, backing_cells: Alloca
return cast(^byte) slot_cursor
}
if slot_cursor == end(transmute([]byte) slots) {
curr_cell := transmute(^KT1CX_Byte_Cell) (uintptr(cell.data) + m.cell_next_offset)
curr_cell := transmute(^KT1CX_Byte_Cell) (uintptr(cell_cursor) + m.cell_next_offset) // curr_cell = cell
if curr_cell != nil {
slots.data = curr_cell.next
slot_cursor = curr_cell.next
cell.data = curr_cell.next
cell_cursor = curr_cell.next
continue
}
else {
@@ -1271,10 +1280,18 @@ str8_fmt_kt1l :: proc(ainfo: AllocatorInfo, _buffer: ^[]byte, table: []KT1L_Slot
}
str8_fmt_backed :: proc(tbl_ainfo, buf_ainfo: AllocatorInfo, fmt_template: string, entries: [][2]string) -> string {
return {}
kt: []KT1L_Slot(string); kt1l_populate_slice_a2(string, & kt, tbl_ainfo, entries)
buf_size := Kilo * 64
buffer := mem_alloc(buf_ainfo, buf_size)
result := str8_fmt_kt1l(buf_ainfo, & buffer, kt, fmt_template)
return result
}
str8_fmt_tmp :: proc(fmt_template: string, entries: [][2]string) -> string {
return {}
@static tbl_mem: [Kilo * 32]byte; tbl_arena := farena_make(tbl_mem[:])
@static buf_mem: [Kilo * 64]byte; buffer := buf_mem[:]
kt: []KT1L_Slot(string); kt1l_populate_slice_a2(string, & kt, ainfo(& tbl_arena), entries)
result := str8_fmt_kt1l({}, & buffer, kt, fmt_template)
return result
}
Str8Cache_CELL_DEPTH :: 4
@@ -1288,39 +1305,129 @@ Str8Cache :: struct {
tbl_backing: AllocatorInfo,
kt: KT1CX_Str8,
}
str8cache_init :: proc(cache: ^Str8Cache, str_reserve, cell_reserve, tbl_backing: AllocatorInfo, cell_pool_size, table_size: int) {
str8cache_init :: proc(cache: ^Str8Cache, str_reserve, cell_reserve, tbl_backing: AllocatorInfo, cell_pool_size := Kilo, table_size := Kilo) {
assert(cache != nil)
assert(str_reserve.procedure != nil)
assert(cell_reserve.procedure != nil)
assert(tbl_backing.procedure != nil)
cache.str_reserve = str_reserve
cache.cell_reserve = cell_reserve
cache.tbl_backing = tbl_backing
info := KT1CX_Info {
backing_cells = cell_reserve,
backing_table = tbl_backing,
}
m := KT1CX_InfoMeta {
cell_pool_size = cell_pool_size,
table_size = table_size,
slot_size = size_of(KT1CX_Slot_Str8),
slot_key_offset = offset_of(KT1CX_Slot_Str8, key),
cell_next_offset = offset_of(KT1CX_Cell_Str8, next),
cell_depth = Str8Cache_CELL_DEPTH,
type_width = size_of(string),
type = string
}
kt1cx_init(info, m, transmute(^KT1CX_Byte) & cache.kt)
return
}
str8cache_make :: proc(str_reserve, cell_reserve, tbl_backing: AllocatorInfo, cell_pool_size, table_size: int) -> Str8Cache {
cache : Str8Cache; str8cache_init(& cache, str_reserve, cell_reserve, tbl_backing, cell_pool_size, table_size); return cache
}
str8cache_clear :: proc(kt: KT1CX_Str8) {
kt1cx_assert(kt)
kt1cx_clear(kt1cx_byte(kt), {
slot_size = size_of(KT1CX_Slot_Str8),
slot_key_offset = offset_of(KT1CX_Slot_Str8, key),
cell_next_offset = offset_of(KT1CX_Cell_Str8, next),
cell_depth = Str8Cache_CELL_DEPTH,
cell_size = size_of(KT1CX_Cell_Str8),
type_width = size_of(string),
type = string,
})
}
str8cache_get :: proc(kt: KT1CX_Str8, key: u64) -> ^string {
return nil
kt1cx_assert(kt)
result := kt1cx_get(kt1cx_byte(kt), key, {
slot_size = size_of(KT1CX_Slot_Str8),
slot_key_offset = offset_of(KT1CX_Slot_Str8, key),
cell_next_offset = offset_of(KT1CX_Cell_Str8, next),
cell_depth = Str8Cache_CELL_DEPTH,
cell_size = size_of(KT1CX_Cell_Str8),
type_width = size_of(string),
type = string,
})
return transmute(^string) result
}
str8cache_set :: proc(kt: KT1CX_Str8, key: u64, value: string, str_reserve, cell_reserve: AllocatorInfo) -> ^string {
return nil
kt1cx_assert(kt)
slice_assert(transmute([]byte) value)
assert(str_reserve.procedure != nil)
assert(cell_reserve.procedure != nil)
entry := kt1cx_set(kt1cx_byte(kt), key, transmute([]byte) value, cell_reserve, {
slot_size = size_of(KT1CX_Slot_Str8),
slot_key_offset = offset_of(KT1CX_Slot_Str8, key),
cell_next_offset = offset_of(KT1CX_Cell_Str8, next),
cell_depth = Str8Cache_CELL_DEPTH,
cell_size = size_of(KT1CX_Cell_Str8),
type_width = size_of(string),
type = string,
})
assert(entry != nil)
result := transmute(^string) entry
is_empty := len(result) == 0
if is_empty {
result ^ = transmute(string) alloc_slice(str_reserve, []u8, len(value))
copy(result ^, value)
}
return result
}
cache_str8 :: proc(cache: ^Str8Cache, str: string) -> ^string {
return nil
cache_str8 :: proc(cache: ^Str8Cache, str: string) -> string {
assert(cache != nil)
key: u64 = 0; hash64_djb8(& key, transmute([]byte) str)
result := str8cache_set(cache.kt, key, str, cache.str_reserve, cache.cell_reserve)
return result ^
}
Str8Gen :: struct {
backing: AllocatorInfo,
ptr: ^u8,
len: int,
cap: int,
ptr: [^]u8,
len: int,
cap: int,
}
str8gen_init :: proc(gen: ^Str8Gen, ainfo: AllocatorInfo) {
assert(gen != nil)
gen.backing = ainfo
gen.ptr = raw_data(mem_alloc(ainfo, Kilo * 4))
assert(gen.ptr != nil)
gen.len = 0
gen.cap = Kilo * 4
}
str8gen_make :: proc(ainfo: AllocatorInfo) -> Str8Gen { gen: Str8Gen; str8gen_init(& gen, ainfo); return gen }
str8gen_to_bytes :: proc(gen: Str8Gen) -> []byte { return transmute([]byte) SliceBytes {data = gen.ptr, len = gen.len} }
str8_from_str8gen :: proc(gen: Str8Gen) -> string { return transmute(string) SliceBytes {data = gen.ptr, len = gen.len} }
str8gen_to_bytes :: proc(gen: Str8Gen) -> []byte { return transmute([]byte) SliceByte {data = gen.ptr, len = gen.len} }
str8_from_str8gen :: proc(gen: Str8Gen) -> string { return transmute(string) SliceByte {data = gen.ptr, len = gen.len} }
str8gen_append_str8 :: proc(gen: ^Str8Gen, str: string) {
result := mem_grow(gen.backing, str8gen_to_bytes(gen ^), len(str) + gen.len)
slice_assert(result)
to_copy := slice(cursor(result)[gen.len:], len(result) - gen.len)
copy(to_copy, transmute([]byte) str)
gen.ptr = transmute(^u8) raw_data(result)
gen.len = len(result)
gen.cap = max(gen.len, gen.cap) // TODO(Ed): Arenas currently hide total capacity before growth. Problably better todo classic append to actually track this.
}
str8gen_append_fmt :: proc(gen: ^Str8Gen, fmt_template: string, tokens: [][2]string) {
@static tbl_mem: [Kilo * 32]byte; tbl_arena := farena_make(tbl_mem[:])
kt: []KT1L_Slot(string); kt1l_populate_slice_a2(string, & kt, ainfo(& tbl_arena), tokens)
buffer := slice(gen.ptr[gen.len:], gen.cap - gen.len)
if len(buffer) < Kilo * 16 {
result := mem_grow(gen.backing, str8gen_to_bytes(gen ^), Kilo * 16 + gen.cap)
slice_assert(result)
gen.ptr = raw_data(result)
gen.cap += Kilo * 16
buffer = slice(gen.ptr[gen.len:], gen.cap - gen.len)
}
result := str8_fmt_kt1l(gen.backing, & buffer, kt, fmt_template)
gen.len += len(result)
}
//#endregion String Operations

View File

@@ -1,14 +0,0 @@
// raddbg 0.9.18 project file
recent_file: path: "c/watl.v0.msvc.c"
target:
{
executable: "build/watl.v0.msvc.exe"
working_directory: C
enabled: 1
}
breakpoint:
{
source_location: "c/watl.v0.msvc.c:2227:1"
hit_count: 0
}

View File

@@ -1,154 +0,0 @@
// raddbg 0.9.18 user file
recent_project: path: "watl_exercise.proj"
window:
{
size: 2048.000000 1152.000000
pos: 234 234
monitor: "\\\\.\\DISPLAY1"
maximized
panels:
{
0.683614: selected getting_started text:
{
selected
expression: "file:\"C:/projects/WATL_Exercise/c/watl.v0.msvc.c\".data"
auto: 1
query: input: ""
cursor_line: 2227
cursor_column: 1
mark_line: 2227
mark_column: 1
}
0.316386:
{
watch: expression: "query:targets"
watch:
{
selected
expression: ""
watch: "listing.ptr.text()"
}
}
}
}
keybindings:
{
{ kill_all f5 shift }
{ step_into_inst f11 alt }
{ step_over_inst f10 alt }
{ step_out f11 shift }
{ halt x ctrl shift }
{ halt pause }
{ run f5 }
{ restart f5 ctrl shift }
{ step_into f11 }
{ step_over f10 }
{ run_to_line f10 ctrl }
{ set_next_statement f10 ctrl shift }
{ inc_window_font_size equal alt }
{ dec_window_font_size minus alt }
{ window n ctrl shift }
{ toggle_fullscreen return ctrl }
{ new_panel_right p ctrl }
{ new_panel_down minus ctrl }
{ rotate_panel_columns 2 ctrl }
{ next_panel comma ctrl }
{ prev_panel comma ctrl shift }
{ focus_panel_right right ctrl alt }
{ focus_panel_left left ctrl alt }
{ focus_panel_up up ctrl alt }
{ focus_panel_down down ctrl alt }
{ undo z ctrl }
{ redo y ctrl }
{ go_back left alt }
{ go_forward right alt }
{ close_panel p ctrl shift alt }
{ next_tab page_down ctrl }
{ prev_tab page_up ctrl }
{ next_tab tab ctrl }
{ prev_tab tab ctrl shift }
{ move_tab_right page_down ctrl shift }
{ move_tab_left page_up ctrl shift }
{ close_tab w ctrl }
{ tab_bar_top up ctrl shift alt }
{ tab_bar_bottom down ctrl shift alt }
{ open_tab t ctrl }
{ open o ctrl }
{ switch i ctrl }
{ switch_to_partner_file o alt }
{ open_user n ctrl shift alt }
{ open_project n ctrl alt }
{ open_user o ctrl shift alt }
{ open_project o ctrl alt }
{ save_user s ctrl shift alt }
{ save_project s ctrl shift }
{ edit f2 }
{ accept return }
{ accept space }
{ cancel esc }
{ move_left left }
{ move_right right }
{ move_up up }
{ move_down down }
{ move_left_select left shift }
{ move_right_select right shift }
{ move_up_select up shift }
{ move_down_select down shift }
{ move_left_chunk left ctrl }
{ move_right_chunk right ctrl }
{ move_up_chunk up ctrl }
{ move_down_chunk down ctrl }
{ move_up_page page_up }
{ move_down_page page_down }
{ move_up_whole home ctrl }
{ move_down_whole end ctrl }
{ move_left_chunk_select left ctrl shift }
{ move_right_chunk_select right ctrl shift }
{ move_up_chunk_select up ctrl shift }
{ move_down_chunk_select down ctrl shift }
{ move_up_page_select page_up shift }
{ move_down_page_select page_down shift }
{ move_up_whole_select home ctrl shift }
{ move_down_whole_select end ctrl shift }
{ move_up_reorder up alt }
{ move_down_reorder down alt }
{ move_home home }
{ move_end end }
{ move_home_select home shift }
{ move_end_select end shift }
{ select_all a ctrl }
{ delete_single delete }
{ delete_chunk delete ctrl }
{ backspace_single backspace }
{ backspace_chunk backspace ctrl }
{ copy c ctrl }
{ copy insert ctrl }
{ cut x ctrl }
{ paste v ctrl }
{ paste insert shift }
{ insert_text null }
{ move_next tab }
{ move_prev tab shift }
{ goto_line g ctrl }
{ goto_address g alt }
{ search f ctrl }
{ search_backwards r ctrl }
{ find_next f3 }
{ find_prev f3 ctrl }
{ find_selected_thread f4 }
{ goto_name j ctrl }
{ goto_name_at_cursor f12 }
{ toggle_watch_expr_at_cursor w alt }
{ toggle_watch_expr_at_mouse d ctrl }
{ toggle_watch_pin f9 ctrl }
{ toggle_breakpoint f9 }
{ add_address_breakpoint f9 shift }
{ add_function_breakpoint f9 ctrl shift }
{ attach f6 shift }
{ open_palette f1 }
{ open_palette p ctrl shift }
{ log_marker m ctrl shift alt }
{ toggle_dev_menu d ctrl shift alt }
}
current_path: "C:/projects/WATL_Exercise/build"