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
+93 -52
View File
@@ -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 <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
}
ltx: Ltx
parse_err := ltx_parse_file(&ltx, os.args[1])
defer ltx_free_file(&ltx)
if parse_err != .None {
fmt.eprintln(ltx_get_error(&ltx, parse_err))
fmt.eprintln(ltx_get_error(&ltx, 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..<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:
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) {
if len(nodes) <= 0 do return
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>")
}
// 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)
}
+14 -16
View File
@@ -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.