Make ltx cli nicer
This commit is contained in:
+164
-143
@@ -9,102 +9,114 @@ import "core:unicode"
|
|||||||
// TODO(Paul): change the key-value format to allow for true arbitrary string values with the use of quotes (maybe?) and make the rules clean and easy to remember.
|
// TODO(Paul): change the key-value format to allow for true arbitrary string values with the use of quotes (maybe?) and make the rules clean and easy to remember.
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
if len(os.args) < 2 {
|
args := parse_args()
|
||||||
fmt.eprintf(
|
if args.type == .None do return
|
||||||
"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
|
ltx: Ltx
|
||||||
|
|
||||||
parse_err := ltx_parse_file(<x, os.args[1])
|
parse_err := parse_file(<x, args.file_path)
|
||||||
defer ltx_free_file(<x)
|
defer free_file(<x)
|
||||||
if parse_err != .None {
|
if parse_err != .None {
|
||||||
fmt.eprintln(ltx_get_error(<x, parse_err, context.temp_allocator))
|
fmt.eprintln(get_error(<x, parse_err, context.temp_allocator))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for node in ltx.nodes {
|
|
||||||
print_node(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
sb := strings.builder_make()
|
sb := strings.builder_make()
|
||||||
defer strings.builder_destroy(&sb)
|
defer strings.builder_destroy(&sb)
|
||||||
|
|
||||||
fmt.println("--- BEGIN XML ---")
|
switch args.type {
|
||||||
strings.builder_reset(&sb)
|
case .None:
|
||||||
ltx_to_xml(&sb, ltx.nodes)
|
return
|
||||||
|
case .XML:
|
||||||
|
to_xml(&sb, ltx.nodes)
|
||||||
|
case .PlainText:
|
||||||
|
to_plain_text(&sb, ltx.nodes)
|
||||||
|
case .SmartIndentedText:
|
||||||
|
to_text_with_smart_indent(&sb, ltx.nodes)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.println(strings.to_string(sb))
|
fmt.println(strings.to_string(sb))
|
||||||
fmt.println("--- END XML ---")
|
|
||||||
|
|
||||||
fmt.println("--- BEGIN PLAIN TEXT ---")
|
// for node in ltx.nodes {
|
||||||
strings.builder_reset(&sb)
|
// print_node(node)
|
||||||
ltx_to_text_with_smart_indent(&sb, ltx.nodes)
|
// }
|
||||||
fmt.println(strings.to_string(sb))
|
|
||||||
fmt.println("--- END PLAIN TEXT ---")
|
// sb := strings.builder_make()
|
||||||
|
// defer strings.builder_destroy(&sb)
|
||||||
|
|
||||||
|
// fmt.println("--- BEGIN XML ---")
|
||||||
|
// strings.builder_reset(&sb)
|
||||||
|
// to_xml(&sb, ltx.nodes)
|
||||||
|
// fmt.println(strings.to_string(sb))
|
||||||
|
// fmt.println("--- END XML ---")
|
||||||
|
|
||||||
|
// fmt.println("--- BEGIN PLAIN TEXT ---")
|
||||||
|
// strings.builder_reset(&sb)
|
||||||
|
// to_text_with_smart_indent(&sb, ltx.nodes)
|
||||||
|
// fmt.println(strings.to_string(sb))
|
||||||
|
// fmt.println("--- END PLAIN TEXT ---")
|
||||||
}
|
}
|
||||||
|
|
||||||
Tokens :: enum {
|
print_help :: proc() {
|
||||||
Backslash,
|
fmt.eprintln(
|
||||||
LeftBrace,
|
"A naive implementation of TeX-based arbitrary markup. Prints syntax tree and space-preserving XML substitution",
|
||||||
RightBrace,
|
)
|
||||||
LeftBracket,
|
fmt.eprintln("Usage:")
|
||||||
RightBracket,
|
fmt.eprintf("\t%s <filepath>\n", os.args[0])
|
||||||
Assign,
|
|
||||||
Quote,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenArray := [Tokens]rune {
|
parse_args :: proc() -> Cli_Args {
|
||||||
.Assign = '=',
|
args := Cli_Args{}
|
||||||
.Backslash = '\\',
|
|
||||||
.LeftBrace = '{',
|
if len(os.args) < 2 {
|
||||||
.RightBrace = '}',
|
print_help()
|
||||||
.LeftBracket = '[',
|
return args
|
||||||
.RightBracket = ']',
|
}
|
||||||
.Quote = '"',
|
|
||||||
|
output_flags_used := 0
|
||||||
|
output_type := Cli_Output_Type.None
|
||||||
|
file_path: string
|
||||||
|
|
||||||
|
for arg in os.args[1:] {
|
||||||
|
if strings.starts_with(arg, "--") {
|
||||||
|
output_flags_used += 1
|
||||||
|
switch arg[2:] {
|
||||||
|
case "xml":
|
||||||
|
output_type = .XML
|
||||||
|
case "text":
|
||||||
|
output_type = .PlainText
|
||||||
|
case "smart":
|
||||||
|
output_type = .SmartIndentedText
|
||||||
|
case:
|
||||||
|
fmt.eprintln("Unknown flag:", arg)
|
||||||
|
print_help()
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if file_path != "" {
|
||||||
|
fmt.eprintln("Unknown argument:", arg)
|
||||||
|
print_help()
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
file_path = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if output_flags_used > 1 {
|
||||||
|
fmt.eprintln("Too many flags passed")
|
||||||
|
print_help()
|
||||||
|
} else {
|
||||||
|
args.file_path = file_path
|
||||||
|
args.type = output_flags_used == 0 ? .PlainText : output_type
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
Node_Kind :: enum {
|
seek :: proc(ltx: ^Ltx) -> (rune, Ltx_Error) {
|
||||||
Text,
|
|
||||||
Tag,
|
|
||||||
}
|
|
||||||
|
|
||||||
Attr_Type :: enum {
|
|
||||||
Flag,
|
|
||||||
Attribute,
|
|
||||||
}
|
|
||||||
|
|
||||||
Field :: struct {
|
|
||||||
value: string,
|
|
||||||
type: Attr_Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
Node :: struct {
|
|
||||||
name: string,
|
|
||||||
kind: Node_Kind,
|
|
||||||
text: string,
|
|
||||||
attributes: map[string]Field,
|
|
||||||
children: [dynamic]Node,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ltx_Error :: enum u32 {
|
|
||||||
None = 0,
|
|
||||||
EOF,
|
|
||||||
KeyExpected,
|
|
||||||
ClosingBracketExpected,
|
|
||||||
ClosingBraceExpected,
|
|
||||||
UnexpectedRightBrace,
|
|
||||||
ValueExpected,
|
|
||||||
KeyAlreadyExists,
|
|
||||||
InvalidKey,
|
|
||||||
CannotReadFile,
|
|
||||||
// InvalidKeyStart,
|
|
||||||
}
|
|
||||||
|
|
||||||
ltx_seek :: proc(ltx: ^Ltx) -> (rune, Ltx_Error) {
|
|
||||||
next_idx := ltx.idx + 1
|
next_idx := ltx.idx + 1
|
||||||
if next_idx >= len(ltx.source) do return 0, .EOF
|
if next_idx >= len(ltx.source) do return 0, .EOF
|
||||||
if ltx_current_char(ltx) == '\n' {
|
if current_char(ltx) == '\n' {
|
||||||
ltx.pos = {
|
ltx.pos = {
|
||||||
col = 0,
|
col = 0,
|
||||||
line = ltx.pos.line + 1,
|
line = ltx.pos.line + 1,
|
||||||
@@ -113,26 +125,26 @@ ltx_seek :: proc(ltx: ^Ltx) -> (rune, Ltx_Error) {
|
|||||||
ltx.pos.col += 1
|
ltx.pos.col += 1
|
||||||
}
|
}
|
||||||
ltx.idx = next_idx
|
ltx.idx = next_idx
|
||||||
c := ltx_current_char(ltx)
|
c := current_char(ltx)
|
||||||
return c, .None
|
return c, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_has_next :: proc(ltx: ^Ltx) -> b32 {
|
has_next :: proc(ltx: ^Ltx) -> b32 {
|
||||||
return len(ltx.source) > ltx.idx + 1
|
return len(ltx.source) > ltx.idx + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_peek :: proc(ltx: ^Ltx) -> (rune, Ltx_Error) {
|
peek :: proc(ltx: ^Ltx) -> (rune, Ltx_Error) {
|
||||||
if !ltx_has_next(ltx) do return 0, .EOF
|
if !has_next(ltx) do return 0, .EOF
|
||||||
return rune(ltx.source[ltx.idx + 1]), .None
|
return rune(ltx.source[ltx.idx + 1]), .None
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_current_char :: proc(ltx: ^Ltx) -> rune {
|
current_char :: proc(ltx: ^Ltx) -> rune {
|
||||||
assert(ltx.idx < len(ltx.source), "index cannot be greater than string length")
|
assert(ltx.idx < len(ltx.source), "index cannot be greater than string length")
|
||||||
return rune(ltx.source[ltx.idx])
|
return rune(ltx.source[ltx.idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_consume_whitespace :: proc(ltx: ^Ltx) -> Ltx_Error {
|
consume_whitespace :: proc(ltx: ^Ltx) -> Ltx_Error {
|
||||||
for unicode.is_white_space(ltx_current_char(ltx)) do ltx_seek(ltx) or_return // TODO: do prop error handling
|
for unicode.is_white_space(current_char(ltx)) do seek(ltx) or_return // TODO: do prop error handling
|
||||||
return .None
|
return .None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,25 +157,14 @@ validate_key :: proc(key: string) -> Ltx_Error {
|
|||||||
return .None
|
return .None
|
||||||
}
|
}
|
||||||
|
|
||||||
Ltx :: struct {
|
parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
||||||
source: string,
|
|
||||||
source_path: string,
|
|
||||||
nodes: [dynamic]Node,
|
|
||||||
idx: int,
|
|
||||||
pos: struct {
|
|
||||||
line: u32,
|
|
||||||
col: u32,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
|
||||||
ltx.idx = 0
|
ltx.idx = 0
|
||||||
stack := make([dynamic]Node)
|
stack := make([dynamic]Node)
|
||||||
defer delete(stack)
|
defer delete(stack)
|
||||||
|
|
||||||
text_node_start_idx := 0
|
text_node_start_idx := 0
|
||||||
for ltx_has_next(ltx) {
|
for has_next(ltx) {
|
||||||
if ltx_current_char(ltx) == TokenArray[.Backslash] {
|
if current_char(ltx) == TokenArray[.Backslash] {
|
||||||
// TODO: cehck if the char after slash is a token
|
// TODO: cehck if the char after slash is a token
|
||||||
if text_node_start_idx < ltx.idx {
|
if text_node_start_idx < ltx.idx {
|
||||||
node := Node {
|
node := Node {
|
||||||
@@ -173,10 +174,10 @@ ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
|||||||
if len(stack) == 0 do append(<x.nodes, node)
|
if len(stack) == 0 do append(<x.nodes, node)
|
||||||
else do append(&stack[len(stack) - 1].children, node)
|
else do append(&stack[len(stack) - 1].children, node)
|
||||||
}
|
}
|
||||||
ltx_seek(ltx) or_return // skip \
|
seek(ltx) or_return // skip \
|
||||||
|
|
||||||
tag_start_idx := ltx.idx
|
tag_start_idx := ltx.idx
|
||||||
for unicode.is_letter(ltx_current_char(ltx)) do ltx_seek(ltx) or_return
|
for unicode.is_letter(current_char(ltx)) do seek(ltx) or_return
|
||||||
assert(tag_start_idx < ltx.idx)
|
assert(tag_start_idx < ltx.idx)
|
||||||
tag_name := ltx.source[tag_start_idx:ltx.idx]
|
tag_name := ltx.source[tag_start_idx:ltx.idx]
|
||||||
node := Node {
|
node := Node {
|
||||||
@@ -193,18 +194,18 @@ ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
|||||||
content = 0,
|
content = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_consume_whitespace(ltx) or_return
|
consume_whitespace(ltx) or_return
|
||||||
for ltx_current_char(ltx) == TokenArray[.LeftBracket] {
|
for current_char(ltx) == TokenArray[.LeftBracket] {
|
||||||
c := ltx_seek(ltx) or_return
|
c := seek(ltx) or_return
|
||||||
field_start := ltx.idx
|
field_start := ltx.idx
|
||||||
key: string
|
key: string
|
||||||
for ltx_has_next(ltx) {
|
for has_next(ltx) {
|
||||||
if c == TokenArray[.Assign] {
|
if c == TokenArray[.Assign] {
|
||||||
if field_start >= ltx.idx do return .KeyExpected
|
if field_start >= ltx.idx do return .KeyExpected
|
||||||
key = strings.trim(ltx.source[field_start:ltx.idx], " \t")
|
key = strings.trim(ltx.source[field_start:ltx.idx], " \t")
|
||||||
if len(key) <= 0 do return .KeyExpected
|
if len(key) <= 0 do return .KeyExpected
|
||||||
validate_key(key) or_return
|
validate_key(key) or_return
|
||||||
ltx_seek(ltx) or_return
|
seek(ltx) or_return
|
||||||
field_start = ltx.idx
|
field_start = ltx.idx
|
||||||
} else if c == TokenArray[.RightBracket] {
|
} else if c == TokenArray[.RightBracket] {
|
||||||
if field_start >= ltx.idx do return .ValueExpected
|
if field_start >= ltx.idx do return .ValueExpected
|
||||||
@@ -220,17 +221,17 @@ ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
|||||||
}
|
}
|
||||||
_, _, found := map_upsert(&node.attributes, key, value)
|
_, _, found := map_upsert(&node.attributes, key, value)
|
||||||
if found do return .KeyAlreadyExists
|
if found do return .KeyAlreadyExists
|
||||||
ltx_seek(ltx) or_return
|
seek(ltx) or_return
|
||||||
end_pos.fields = ltx.idx
|
end_pos.fields = ltx.idx
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
c = ltx_seek(ltx) or_return
|
c = seek(ltx) or_return
|
||||||
}
|
}
|
||||||
ltx_consume_whitespace(ltx) or_return
|
consume_whitespace(ltx) or_return
|
||||||
}
|
}
|
||||||
ltx_consume_whitespace(ltx) or_return
|
consume_whitespace(ltx) or_return
|
||||||
if ltx_current_char(ltx) == TokenArray[.LeftBrace] {
|
if current_char(ltx) == TokenArray[.LeftBrace] {
|
||||||
ltx_seek(ltx) or_return // consume {
|
seek(ltx) or_return // consume {
|
||||||
append(&stack, node)
|
append(&stack, node)
|
||||||
end_pos.content = ltx.idx
|
end_pos.content = ltx.idx
|
||||||
} else {
|
} else {
|
||||||
@@ -239,7 +240,7 @@ ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
|||||||
if end_pos.content != 0 do text_node_start_idx = end_pos.content
|
if end_pos.content != 0 do text_node_start_idx = end_pos.content
|
||||||
else if end_pos.fields != 0 do text_node_start_idx = end_pos.fields
|
else if end_pos.fields != 0 do text_node_start_idx = end_pos.fields
|
||||||
else do text_node_start_idx = end_pos.tag_name
|
else do text_node_start_idx = end_pos.tag_name
|
||||||
} else if ltx_current_char(ltx) == TokenArray[.RightBrace] {
|
} else if current_char(ltx) == TokenArray[.RightBrace] {
|
||||||
if len(stack) <= 0 do return .UnexpectedRightBrace
|
if len(stack) <= 0 do return .UnexpectedRightBrace
|
||||||
node := pop(&stack)
|
node := pop(&stack)
|
||||||
if text_node_start_idx < ltx.idx {
|
if text_node_start_idx < ltx.idx {
|
||||||
@@ -254,10 +255,10 @@ ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
|||||||
} else {
|
} else {
|
||||||
append(<x.nodes, node)
|
append(<x.nodes, node)
|
||||||
}
|
}
|
||||||
ltx_seek(ltx) or_return // consume }
|
seek(ltx) or_return // consume }
|
||||||
text_node_start_idx = ltx.idx
|
text_node_start_idx = ltx.idx
|
||||||
} else {
|
} else {
|
||||||
ltx_seek(ltx) or_return
|
seek(ltx) or_return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if text_node_start_idx < ltx.idx {
|
if text_node_start_idx < ltx.idx {
|
||||||
@@ -267,17 +268,17 @@ ltx_parse :: proc(ltx: ^Ltx) -> (err: Ltx_Error) {
|
|||||||
return .None
|
return .None
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_parse_file :: proc(ltx: ^Ltx, file_path: string) -> Ltx_Error {
|
parse_file :: proc(ltx: ^Ltx, file_path: string, allocator := context.allocator) -> Ltx_Error {
|
||||||
source, err := os.read_entire_file_from_path(file_path, context.allocator)
|
source, err := os.read_entire_file_from_path(file_path, allocator)
|
||||||
if err != nil do return .CannotReadFile
|
if err != nil do return .CannotReadFile
|
||||||
abs_path, abs_err := filepath.abs(file_path)
|
abs_path, abs_err := filepath.abs(file_path)
|
||||||
if abs_err != nil 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 parse(ltx)
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_free_file :: proc(ltx: ^Ltx) -> runtime.Allocator_Error {
|
free_file :: proc(ltx: ^Ltx) -> runtime.Allocator_Error {
|
||||||
delete(ltx.source) or_return
|
delete(ltx.source) or_return
|
||||||
delete(ltx.source_path) or_return
|
delete(ltx.source_path) or_return
|
||||||
return .None
|
return .None
|
||||||
@@ -314,7 +315,7 @@ print_node :: proc(node: Node, indent_level := 0) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ltx_to_text_with_smart_indent :: proc (sb: ^strings.Builder, nodes: [dynamic]Node, indent_level := 0) {
|
to_text_with_smart_indent :: proc(sb: ^strings.Builder, nodes: [dynamic]Node, indent_level := 0) {
|
||||||
should_inc_indent_level := true
|
should_inc_indent_level := true
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
if node.kind == .Text {
|
if node.kind == .Text {
|
||||||
@@ -326,32 +327,40 @@ ltx_to_text_with_smart_indent :: proc (sb: ^strings.Builder, nodes: [dynamic]Nod
|
|||||||
for node in nodes {
|
for node in nodes {
|
||||||
switch node.kind {
|
switch node.kind {
|
||||||
case .Text:
|
case .Text:
|
||||||
for i in 0..<indent_level-1 do strings.write_rune(sb, ' ')
|
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")
|
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
|
// TODO(Paul): do something about newlines; have them be limited to 2 max if they appear consecutively perhaps
|
||||||
if text != "" {
|
if text != "" {
|
||||||
fmt.sbprintln(sb, text)
|
fmt.sbprintln(sb, text)
|
||||||
}
|
}
|
||||||
case .Tag:
|
case .Tag:
|
||||||
ltx_to_text_with_smart_indent(sb, node.children, should_inc_indent_level ? indent_level + 1 : indent_level)
|
to_text_with_smart_indent(
|
||||||
|
sb,
|
||||||
|
node.children,
|
||||||
|
should_inc_indent_level ? indent_level + 1 : indent_level,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ltx to plain text; strip ltx tags
|
// Ltx to plain text; strip ltx tags
|
||||||
ltx_to_plain_text :: proc(sb: ^strings.Builder, nodes: [dynamic]Node) {
|
to_plain_text :: proc(sb: ^strings.Builder, nodes: [dynamic]Node) {
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
switch node.kind {
|
switch node.kind {
|
||||||
case .Text:
|
case .Text:
|
||||||
fmt.sbprint(sb, node.text)
|
fmt.sbprint(sb, node.text)
|
||||||
case .Tag:
|
case .Tag:
|
||||||
ltx_to_plain_text(sb, node.children)
|
to_plain_text(sb, node.children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ltx to XML
|
// Ltx to XML
|
||||||
ltx_to_xml :: proc(sb: ^strings.Builder, nodes: [dynamic]Node, depth := 0) {
|
// TODO: CDATA garbage
|
||||||
|
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>")
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
@@ -363,18 +372,18 @@ ltx_to_xml :: proc(sb: ^strings.Builder, nodes: [dynamic]Node, depth := 0) {
|
|||||||
for k, v in node.attributes do fmt.sbprintf(sb, " %s=\"%s\"", k, v.value)
|
for k, v in node.attributes do fmt.sbprintf(sb, " %s=\"%s\"", k, v.value)
|
||||||
if len(node.children) > 0 {
|
if len(node.children) > 0 {
|
||||||
fmt.sbprint(sb, ">")
|
fmt.sbprint(sb, ">")
|
||||||
ltx_to_xml(sb, node.children, depth + 1)
|
to_xml(sb, node.children, depth + 1)
|
||||||
fmt.sbprintf(sb, "</%s>", node.name)
|
fmt.sbprintf(sb, "</%s>", node.name)
|
||||||
} else {
|
} else {
|
||||||
fmt.sbprint(sb, " />")
|
fmt.sbprint(sb, " />")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if depth == 0 do fmt.sbprintln(sb, "</ltx>")
|
if depth == 0 do fmt.sbprint(sb, "\n</ltx>")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ltx_Error to english string
|
// Ltx_Error to english string
|
||||||
ltx_error_to_string :: proc(error: Ltx_Error) -> string {
|
error_to_string :: proc(error: Ltx_Error) -> string {
|
||||||
switch error {
|
switch error {
|
||||||
case .None:
|
case .None:
|
||||||
return ""
|
return ""
|
||||||
@@ -401,29 +410,41 @@ ltx_error_to_string :: proc(error: Ltx_Error) -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pretty format LTX error
|
// Pretty format LTX error
|
||||||
ltx_get_error :: proc(ltx: ^Ltx, error: Ltx_Error, allocator := context.allocator) -> string {
|
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 : ""
|
||||||
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 := error_to_string(error)
|
||||||
return fmt.aprintf("%s(%d,%d): error: %s", file_path, line, col, error_msg, allocator = allocator)
|
return fmt.aprintf(
|
||||||
|
"%s(%d,%d): error: %s",
|
||||||
|
file_path,
|
||||||
|
line,
|
||||||
|
col,
|
||||||
|
error_msg,
|
||||||
|
allocator = allocator,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escapes white space characters with in their c-string slash form
|
// Escapes white space characters with in their c-string slash form
|
||||||
escape_white_space :: proc(s: string, allocator := context.allocator) -> string {
|
escape_white_space :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
sb := strings.builder_make(allocator)
|
sb := strings.builder_make(allocator)
|
||||||
|
|
||||||
for c in s {
|
for c in s {
|
||||||
switch c {
|
switch c {
|
||||||
case '\t': strings.write_string(&sb, "\\t")
|
case '\t':
|
||||||
case '\n': strings.write_string(&sb, "\\n")
|
strings.write_string(&sb, "\\t")
|
||||||
case '\r': strings.write_string(&sb, "\\r")
|
case '\n':
|
||||||
case '\\': strings.write_string(&sb, "\\\\")
|
strings.write_string(&sb, "\\n")
|
||||||
case: strings.write_rune(&sb, c)
|
case '\r':
|
||||||
}
|
strings.write_string(&sb, "\\r")
|
||||||
}
|
case '\\':
|
||||||
return strings.to_string(sb)
|
strings.write_string(&sb, "\\\\")
|
||||||
|
case:
|
||||||
|
strings.write_rune(&sb, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.to_string(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processes and normalizes whitespace in a multi-line string.
|
// Processes and normalizes whitespace in a multi-line string.
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package ltx
|
||||||
|
|
||||||
|
Ltx :: struct {
|
||||||
|
source: string,
|
||||||
|
source_path: string,
|
||||||
|
nodes: [dynamic]Node,
|
||||||
|
idx: int,
|
||||||
|
pos: struct {
|
||||||
|
line: u32,
|
||||||
|
col: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Tokens :: enum {
|
||||||
|
Backslash,
|
||||||
|
LeftBrace,
|
||||||
|
RightBrace,
|
||||||
|
LeftBracket,
|
||||||
|
RightBracket,
|
||||||
|
Assign,
|
||||||
|
Quote,
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenArray := [Tokens]rune {
|
||||||
|
.Assign = '=',
|
||||||
|
.Backslash = '\\',
|
||||||
|
.LeftBrace = '{',
|
||||||
|
.RightBrace = '}',
|
||||||
|
.LeftBracket = '[',
|
||||||
|
.RightBracket = ']',
|
||||||
|
.Quote = '"',
|
||||||
|
}
|
||||||
|
|
||||||
|
Node_Kind :: enum {
|
||||||
|
Text,
|
||||||
|
Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
Attr_Type :: enum {
|
||||||
|
Flag,
|
||||||
|
Attribute,
|
||||||
|
}
|
||||||
|
|
||||||
|
Field :: struct {
|
||||||
|
value: string,
|
||||||
|
type: Attr_Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
Node :: struct {
|
||||||
|
name: string,
|
||||||
|
kind: Node_Kind,
|
||||||
|
text: string,
|
||||||
|
attributes: map[string]Field,
|
||||||
|
children: [dynamic]Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ltx_Error :: enum u32 {
|
||||||
|
None = 0,
|
||||||
|
EOF,
|
||||||
|
KeyExpected,
|
||||||
|
ClosingBracketExpected,
|
||||||
|
ClosingBraceExpected,
|
||||||
|
UnexpectedRightBrace,
|
||||||
|
ValueExpected,
|
||||||
|
KeyAlreadyExists,
|
||||||
|
InvalidKey,
|
||||||
|
CannotReadFile,
|
||||||
|
// InvalidKeyStart,
|
||||||
|
}
|
||||||
|
|
||||||
|
Cli_Output_Type :: enum {
|
||||||
|
None,
|
||||||
|
PlainText,
|
||||||
|
XML,
|
||||||
|
SmartIndentedText,
|
||||||
|
}
|
||||||
|
|
||||||
|
Cli_Args :: struct {
|
||||||
|
type: Cli_Output_Type,
|
||||||
|
file_path: string,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user