From a56450efc85f2db0b3e3f8dc054b86879b653d1c Mon Sep 17 00:00:00 2001 From: "Paul W." Date: Sat, 1 Aug 2026 13:54:55 -0400 Subject: [PATCH] Rm ltx mem leaks (sort of) --- ltx/ltx.odin | 147 +++++++++++++++++++++++++++++++------------------ ltx/sample.ltx | 30 +++++----- 2 files changed, 108 insertions(+), 69 deletions(-) diff --git a/ltx/ltx.odin b/ltx/ltx.odin index a623ef4..bb0cc60 100644 --- a/ltx/ltx.odin +++ b/ltx/ltx.odin @@ -1,4 +1,6 @@ package ltx + +import "base:runtime" import "core:fmt" import "core:os" import "core:path/filepath" @@ -8,14 +10,18 @@ import "core:unicode" main :: proc() { if len(os.args) < 2 { - fmt.eprintf("A naive implementation of TeX-based arbitrary markup. Prints syntax tree and space-preserving XML substitution\nUsage:\n\t%s \n", os.args[0]) + fmt.eprintf( + "A naive implementation of TeX-based arbitrary markup. Prints syntax tree and space-preserving XML substitution\nUsage:\n\t%s \n", + os.args[0], + ) return } ltx: Ltx - + parse_err := ltx_parse_file(<x, os.args[1]) + defer ltx_free_file(<x) if parse_err != .None { - fmt.eprintln(ltx_get_error(<x, parse_err)) + fmt.eprintln(ltx_get_error(<x, parse_err, context.temp_allocator)) return } for node in ltx.nodes { @@ -24,10 +30,18 @@ main :: proc() { sb := strings.builder_make() defer strings.builder_destroy(&sb) - strip_ltx(&sb, ltx.nodes) + + fmt.println("--- BEGIN XML ---") strings.builder_reset(&sb) ltx_to_xml(&sb, ltx.nodes) fmt.println(strings.to_string(sb)) + fmt.println("--- END XML ---") + + fmt.println("--- BEGIN PLAIN TEXT ---") + strings.builder_reset(&sb) + ltx_to_text_with_smart_indent(&sb, ltx.nodes) + fmt.println(strings.to_string(sb)) + fmt.println("--- END PLAIN TEXT ---") } Tokens :: enum { @@ -122,19 +136,11 @@ ltx_consume_whitespace :: proc(ltx: ^Ltx) -> Ltx_Error { return .None } -is_numeric :: proc(c: rune) -> b32 { - return c >= '0' && c <= '9' -} - -is_alpha :: proc(c: rune) -> b32 { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') -} - validate_key :: proc(key: string) -> Ltx_Error { assert(len(key) > 0, "expected non-empty key") - if !is_alpha(rune(key[0])) do return .InvalidKey + if !unicode.is_alpha(rune(key[0])) do return .InvalidKey for c in key { - if !is_alpha(c) && !is_numeric(c) && c != '_' do return .InvalidKey + if !unicode.is_alpha(c) && !unicode.is_number(c) && c != '_' do return .InvalidKey } return .None } @@ -262,31 +268,41 @@ ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) { } ltx_parse_file :: proc(ltx: ^Ltx, file_path: string) -> Ltx_Error { - source, ok := os.read_entire_file(file_path) - if !ok do return .CannotReadFile - abs_path, abs_ok := filepath.abs(file_path) - if !abs_ok do return .CannotReadFile + source, err := os.read_entire_file_from_path(file_path, context.allocator) + if err != nil do return .CannotReadFile + abs_path, abs_err := filepath.abs(file_path) + if abs_err != nil do return .CannotReadFile ltx.source_path = abs_path ltx.source = string(source) return ltx_parse(ltx) } +ltx_free_file :: proc(ltx: ^Ltx) -> runtime.Allocator_Error { + delete(ltx.source) or_return + delete(ltx.source_path) or_return + return .None +} + print_indent :: proc(level: int) { for i in 0 ..< level { fmt.print(" ") } } + print_node :: proc(node: Node, indent_level := 0) { print_indent(indent_level) switch node.kind { case .Text: - fmt.printf("TEXT: \"%s\"\n", escape_white_space(node.text)) + fmt.printf("TEXT: \"%s\"\n", escape_white_space(node.text, context.temp_allocator)) case .Tag: fmt.print("TAG:", node.name) if len(node.attributes) > 0 { for k, v in node.attributes { - if v.type == .Flag do fmt.printf(" (%s)", k) - else do fmt.printf(" {{%s: %s}}", k, v.value) + if v.type == .Flag { + fmt.printf(" (%s)", k) + } else { + fmt.printf(" {{%s: %s}}", k, v.value) + } } } fmt.println() @@ -298,18 +314,43 @@ print_node :: proc(node: Node, indent_level := 0) { } } -strip_ltx :: proc(sb: ^strings.Builder, nodes: [dynamic]Node) -> b32 { +ltx_to_text_with_smart_indent :: proc (sb: ^strings.Builder, nodes: [dynamic]Node, indent_level := 0) { + should_inc_indent_level := true + for node in nodes { + if node.kind == .Text { + should_inc_indent_level = true + break + } + } + for node in nodes { switch node.kind { case .Text: - fmt.sbprint(sb, (node.text)) + for i in 0..") @@ -332,6 +373,7 @@ ltx_to_xml :: proc(sb: ^strings.Builder, nodes: [dynamic]Node, depth := 0) { if depth == 0 do fmt.sbprintln(sb, "") } +// Ltx_Error to english string ltx_error_to_string :: proc(error: Ltx_Error) -> string { switch error { case .None: @@ -358,55 +400,54 @@ ltx_error_to_string :: proc(error: Ltx_Error) -> string { return "unknown error" } -ltx_get_error :: proc(ltx: ^Ltx, error: Ltx_Error) -> string { +// Pretty format LTX error +ltx_get_error :: proc(ltx: ^Ltx, error: Ltx_Error, allocator := context.allocator) -> string { if error == .None do return "" file_path := len(ltx.source_path) > 0 ? ltx.source_path : "[source]" line := ltx.pos.line + 1 col := ltx.pos.col + 1 error_msg := ltx_error_to_string(error) - return fmt.tprintf("%s(%d,%d): error: %s", file_path, line, col, error_msg) + return fmt.aprintf("%s(%d,%d): error: %s", file_path, line, col, error_msg, allocator = allocator) } -escape_white_space :: proc(s: string) -> string { - sb: strings.Builder = strings.builder_make() - // defer strings.builder_destroy(&sb) - for c in s { - switch c { - case '\t': - fmt.sbprint(&sb, "\\t") - case '\n': - fmt.sbprint(&sb, "\\n") - case: - fmt.sbprint(&sb, c) - } - } - return strings.to_string(sb) +// Escapes white space characters with in their c-string slash form +escape_white_space :: proc(s: string, allocator := context.allocator) -> string { + sb := strings.builder_make(allocator) + + for c in s { + switch c { + case '\t': strings.write_string(&sb, "\\t") + case '\n': strings.write_string(&sb, "\\n") + case '\r': strings.write_string(&sb, "\\r") + case '\\': strings.write_string(&sb, "\\\\") + case: strings.write_rune(&sb, c) + } + } + return strings.to_string(sb) } -process_white_space :: proc(s: string) -> string { - sb: strings.Builder = strings.builder_make() - defer strings.builder_destroy(&sb) +// Processes and normalizes whitespace in a multi-line string. +process_white_space :: proc(s: string, allocator := context.allocator) -> string { + sb: strings.Builder = strings.builder_make(allocator) lines := strings.split(s, "\n") for _line in lines { line := strings.trim_right(_line, "\t ") if len(line) <= 0 do continue last_idx := 0 - last_char: u8 = 0 - for i := 0; i < len(line); i += 1 { - if line[i] == '\r' do continue - if unicode.is_space(rune(line[i])) { - if last_char == line[i] { + last_char: rune = 0 + for r, i in line { + if r == '\r' do continue + if unicode.is_space(r) { + if last_char == r { last_idx = i } else { fmt.sbprint(&sb, line[last_idx:i]) last_idx = i } } - last_char = line[i] + last_char = r } - fmt.sbprintln(&sb, line[last_idx:len(line)]) + fmt.sbprintln(&sb, line[last_idx:]) } - // return "" return strings.to_string(sb) } - diff --git a/ltx/sample.ltx b/ltx/sample.ltx index b76296b..2429092 100644 --- a/ltx/sample.ltx +++ b/ltx/sample.ltx @@ -4,26 +4,26 @@ \section{ \title{Resources} - \list{ - \item{\a[link_flag]{sdf}} - \item{\a[link_flag2]{}} - } + \list{ + \item{\a[link_flag]{sdf}} + \item{\a[link_flag2]{}} + } } \lol \section{ \title{Arbitrary tags!} - \list{ - \item{ - \name{yo!} - \desc{yoyo!} - } - \item{ - \name{oy!} - \desc{oyoy!} - } - } + \list{ + \item{ + \name{yo!} + \desc{yoyo!} + } + \item{ + \name{oy!} + \desc{oyoy!} + } + } } \link[link_flag]{https://example.com} @@ -33,5 +33,3 @@ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -