Rm ltx mem leaks (sort of)

This commit is contained in:
2026-08-01 13:54:55 -04:00
parent 7e4ddb7ac8
commit a56450efc8
2 changed files with 108 additions and 69 deletions
+94 -53
View File
@@ -1,4 +1,6 @@
package ltx package ltx
import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:os" import "core:os"
import "core:path/filepath" import "core:path/filepath"
@@ -8,14 +10,18 @@ import "core:unicode"
main :: proc() { main :: proc() {
if len(os.args) < 2 { 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 <filepath>\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 <filepath>\n",
os.args[0],
)
return return
} }
ltx: Ltx ltx: Ltx
parse_err := ltx_parse_file(&ltx, os.args[1]) parse_err := ltx_parse_file(&ltx, os.args[1])
defer ltx_free_file(&ltx)
if parse_err != .None { if parse_err != .None {
fmt.eprintln(ltx_get_error(&ltx, parse_err)) fmt.eprintln(ltx_get_error(&ltx, parse_err, context.temp_allocator))
return return
} }
for node in ltx.nodes { for node in ltx.nodes {
@@ -24,10 +30,18 @@ main :: proc() {
sb := strings.builder_make() sb := strings.builder_make()
defer strings.builder_destroy(&sb) defer strings.builder_destroy(&sb)
strip_ltx(&sb, ltx.nodes)
fmt.println("--- BEGIN XML ---")
strings.builder_reset(&sb) strings.builder_reset(&sb)
ltx_to_xml(&sb, ltx.nodes) ltx_to_xml(&sb, ltx.nodes)
fmt.println(strings.to_string(sb)) 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 { Tokens :: enum {
@@ -122,19 +136,11 @@ ltx_consume_whitespace :: proc(ltx: ^Ltx) -> Ltx_Error {
return .None 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 { validate_key :: proc(key: string) -> Ltx_Error {
assert(len(key) > 0, "expected non-empty key") 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 { 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 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 { ltx_parse_file :: proc(ltx: ^Ltx, file_path: string) -> Ltx_Error {
source, ok := os.read_entire_file(file_path) source, err := os.read_entire_file_from_path(file_path, context.allocator)
if !ok do return .CannotReadFile if err != nil do return .CannotReadFile
abs_path, abs_ok := filepath.abs(file_path) abs_path, abs_err := filepath.abs(file_path)
if !abs_ok do return .CannotReadFile if abs_err != nil do return .CannotReadFile
ltx.source_path = abs_path ltx.source_path = abs_path
ltx.source = string(source) ltx.source = string(source)
return ltx_parse(ltx) 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) { print_indent :: proc(level: int) {
for i in 0 ..< level { for i in 0 ..< level {
fmt.print(" ") fmt.print(" ")
} }
} }
print_node :: proc(node: Node, indent_level := 0) { print_node :: proc(node: Node, indent_level := 0) {
print_indent(indent_level) print_indent(indent_level)
switch node.kind { switch node.kind {
case .Text: 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: case .Tag:
fmt.print("TAG:", node.name) fmt.print("TAG:", node.name)
if len(node.attributes) > 0 { if len(node.attributes) > 0 {
for k, v in node.attributes { for k, v in node.attributes {
if v.type == .Flag do fmt.printf(" (%s)", k) if v.type == .Flag {
else do fmt.printf(" {{%s: %s}}", k, v.value) fmt.printf(" (%s)", k)
} else {
fmt.printf(" {{%s: %s}}", k, v.value)
}
} }
} }
fmt.println() 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 { for node in nodes {
switch node.kind { switch node.kind {
case .Text: case .Text:
fmt.sbprint(sb, (node.text)) for i in 0..<indent_level-1 do strings.write_rune(sb, ' ')
text := strings.trim(process_white_space(node.text, context.temp_allocator), " \t\n\r\v")
// TODO(Paul): do something about newlines; have them be limited to 2 max if they appear consecutively perhaps
if text != "" {
fmt.sbprintln(sb, text)
}
case .Tag: case .Tag:
strip_ltx(sb, node.children) ltx_to_text_with_smart_indent(sb, node.children, should_inc_indent_level ? indent_level + 1 : indent_level)
} }
} }
return true
} }
// Ltx to plain text; strip ltx tags
ltx_to_plain_text :: proc(sb: ^strings.Builder, nodes: [dynamic]Node) {
for node in nodes {
switch node.kind {
case .Text:
fmt.sbprint(sb, node.text)
case .Tag:
ltx_to_plain_text(sb, node.children)
}
}
}
// Ltx to XML
ltx_to_xml :: proc(sb: ^strings.Builder, nodes: [dynamic]Node, depth := 0) { ltx_to_xml :: proc(sb: ^strings.Builder, nodes: [dynamic]Node, depth := 0) {
if len(nodes) <= 0 do return if len(nodes) <= 0 do return
if depth == 0 do fmt.sbprintln(sb, "<ltx>") if depth == 0 do fmt.sbprintln(sb, "<ltx>")
@@ -332,6 +373,7 @@ ltx_to_xml :: proc(sb: ^strings.Builder, nodes: [dynamic]Node, depth := 0) {
if depth == 0 do fmt.sbprintln(sb, "</ltx>") if depth == 0 do fmt.sbprintln(sb, "</ltx>")
} }
// Ltx_Error to english string
ltx_error_to_string :: proc(error: Ltx_Error) -> string { ltx_error_to_string :: proc(error: Ltx_Error) -> string {
switch error { switch error {
case .None: case .None:
@@ -358,55 +400,54 @@ ltx_error_to_string :: proc(error: Ltx_Error) -> string {
return "unknown error" 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 "" if error == .None do return ""
file_path := len(ltx.source_path) > 0 ? ltx.source_path : "[source]" file_path := len(ltx.source_path) > 0 ? ltx.source_path : "[source]"
line := ltx.pos.line + 1 line := ltx.pos.line + 1
col := ltx.pos.col + 1 col := ltx.pos.col + 1
error_msg := ltx_error_to_string(error) 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 { // Escapes white space characters with in their c-string slash form
sb: strings.Builder = strings.builder_make() escape_white_space :: proc(s: string, allocator := context.allocator) -> string {
// defer strings.builder_destroy(&sb) sb := strings.builder_make(allocator)
for c in s {
switch c { for c in s {
case '\t': switch c {
fmt.sbprint(&sb, "\\t") case '\t': strings.write_string(&sb, "\\t")
case '\n': case '\n': strings.write_string(&sb, "\\n")
fmt.sbprint(&sb, "\\n") case '\r': strings.write_string(&sb, "\\r")
case: case '\\': strings.write_string(&sb, "\\\\")
fmt.sbprint(&sb, c) case: strings.write_rune(&sb, c)
} }
} }
return strings.to_string(sb) return strings.to_string(sb)
} }
process_white_space :: proc(s: string) -> string { // Processes and normalizes whitespace in a multi-line string.
sb: strings.Builder = strings.builder_make() process_white_space :: proc(s: string, allocator := context.allocator) -> string {
defer strings.builder_destroy(&sb) sb: strings.Builder = strings.builder_make(allocator)
lines := strings.split(s, "\n") lines := strings.split(s, "\n")
for _line in lines { for _line in lines {
line := strings.trim_right(_line, "\t ") line := strings.trim_right(_line, "\t ")
if len(line) <= 0 do continue if len(line) <= 0 do continue
last_idx := 0 last_idx := 0
last_char: u8 = 0 last_char: rune = 0
for i := 0; i < len(line); i += 1 { for r, i in line {
if line[i] == '\r' do continue if r == '\r' do continue
if unicode.is_space(rune(line[i])) { if unicode.is_space(r) {
if last_char == line[i] { if last_char == r {
last_idx = i last_idx = i
} else { } else {
fmt.sbprint(&sb, line[last_idx:i]) fmt.sbprint(&sb, line[last_idx:i])
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) return strings.to_string(sb)
} }
+14 -16
View File
@@ -4,26 +4,26 @@
\section{ \section{
\title{Resources} \title{Resources}
\list{ \list{
\item{\a[link_flag]{sdf}} \item{\a[link_flag]{sdf}}
\item{\a[link_flag2]{}} \item{\a[link_flag2]{}}
} }
} }
\lol \lol
\section{ \section{
\title{Arbitrary tags!} \title{Arbitrary tags!}
\list{ \list{
\item{ \item{
\name{yo!} \name{yo!}
\desc{yoyo!} \desc{yoyo!}
} }
\item{ \item{
\name{oy!} \name{oy!}
\desc{oyoy!} \desc{oyoy!}
} }
} }
} }
\link[link_flag]{https://example.com} \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 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. 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. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.