mirror of
https://github.com/odin-lang/Odin.git
synced 2026-06-07 11:04:17 +00:00
Merge pull request #997 from DanielGavin/prototype-fmt
Add odin/format and odin/printer packages
This commit is contained in:
35
core/odin/format/format.odin
Normal file
35
core/odin/format/format.odin
Normal file
@@ -0,0 +1,35 @@
|
||||
package odin_format
|
||||
|
||||
import "core:odin/printer"
|
||||
import "core:odin/parser"
|
||||
import "core:odin/ast"
|
||||
|
||||
default_style := printer.default_style;
|
||||
|
||||
simplify :: proc(file: ^ast.File) {
|
||||
|
||||
}
|
||||
|
||||
format :: proc(source: [] u8, config: printer.Config, allocator := context.allocator) -> ([] u8, bool) {
|
||||
|
||||
pkg := ast.Package {
|
||||
kind = .Normal,
|
||||
};
|
||||
|
||||
file := ast.File {
|
||||
pkg = &pkg,
|
||||
src = source,
|
||||
};
|
||||
|
||||
p := parser.default_parser();
|
||||
|
||||
ok := parser.parse_file(&p, &file);
|
||||
|
||||
if !ok || file.syntax_error_count > 0 {
|
||||
return {}, false;
|
||||
}
|
||||
|
||||
prnt := printer.make_printer(config, allocator);
|
||||
|
||||
return transmute([]u8) printer.print(&prnt, &file), true;
|
||||
}
|
||||
@@ -2279,6 +2279,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
p.expr_level = -1;
|
||||
where_clauses = parse_rhs_expr_list(p);
|
||||
p.expr_level = prev_level;
|
||||
tags = parse_proc_tags(p);
|
||||
}
|
||||
if p.allow_type && p.expr_level < 0 {
|
||||
if where_token.kind != .Invalid {
|
||||
@@ -3212,6 +3213,7 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt {
|
||||
case ast.For_Stmt: n.label = label;
|
||||
case ast.Switch_Stmt: n.label = label;
|
||||
case ast.Type_Switch_Stmt: n.label = label;
|
||||
case ast.Range_Stmt: n.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
921
core/odin/printer/printer.odin
Normal file
921
core/odin/printer/printer.odin
Normal file
@@ -0,0 +1,921 @@
|
||||
package odin_printer
|
||||
|
||||
import "core:odin/ast"
|
||||
import "core:odin/tokenizer"
|
||||
import "core:strings"
|
||||
import "core:runtime"
|
||||
import "core:fmt"
|
||||
import "core:unicode/utf8"
|
||||
import "core:mem"
|
||||
|
||||
Type_Enum :: enum {Line_Comment, Value_Decl, Switch_Stmt, Struct, Assign, Call, Enum, If, For, Proc_Lit}
|
||||
|
||||
Line_Type :: bit_set[Type_Enum];
|
||||
|
||||
/*
|
||||
Represents an unwrapped line
|
||||
*/
|
||||
Line :: struct {
|
||||
format_tokens: [dynamic]Format_Token,
|
||||
finalized: bool,
|
||||
used: bool,
|
||||
depth: int,
|
||||
types: Line_Type, //for performance, so you don't have to verify what types are in it by going through the tokens - might give problems when adding linebreaking
|
||||
}
|
||||
|
||||
/*
|
||||
Represents a singular token in a unwrapped line
|
||||
*/
|
||||
Format_Token :: struct {
|
||||
kind: tokenizer.Token_Kind,
|
||||
text: string,
|
||||
type: Type_Enum,
|
||||
spaces_before: int,
|
||||
parameter_count: int,
|
||||
}
|
||||
|
||||
Printer :: struct {
|
||||
string_builder: strings.Builder,
|
||||
config: Config,
|
||||
depth: int, //the identation depth
|
||||
comments: [dynamic]^ast.Comment_Group,
|
||||
latest_comment_index: int,
|
||||
allocator: mem.Allocator,
|
||||
file: ^ast.File,
|
||||
source_position: tokenizer.Pos,
|
||||
last_source_position: tokenizer.Pos,
|
||||
lines: [dynamic]Line, //need to look into a better data structure, one that can handle inserting lines rather than appending
|
||||
skip_semicolon: bool,
|
||||
current_line: ^Line,
|
||||
current_line_index: int,
|
||||
last_line_index: int,
|
||||
last_token: ^Format_Token,
|
||||
merge_next_token: bool,
|
||||
space_next_token: bool,
|
||||
debug: bool,
|
||||
}
|
||||
|
||||
Config :: struct {
|
||||
spaces: int, //Spaces per indentation
|
||||
newline_limit: int, //The limit of newlines between statements and declarations.
|
||||
tabs: bool, //Enable or disable tabs
|
||||
convert_do: bool, //Convert all do statements to brace blocks
|
||||
semicolons: bool, //Enable semicolons
|
||||
split_multiple_stmts: bool,
|
||||
align_switch: bool,
|
||||
brace_style: Brace_Style,
|
||||
align_assignments: bool,
|
||||
align_structs: bool,
|
||||
align_style: Alignment_Style,
|
||||
align_enums: bool,
|
||||
align_length_break: int,
|
||||
indent_cases: bool,
|
||||
newline_style: Newline_Style,
|
||||
}
|
||||
|
||||
Brace_Style :: enum {
|
||||
_1TBS,
|
||||
Allman,
|
||||
Stroustrup,
|
||||
K_And_R,
|
||||
}
|
||||
|
||||
Block_Type :: enum {
|
||||
None,
|
||||
If_Stmt,
|
||||
Proc,
|
||||
Generic,
|
||||
Comp_Lit,
|
||||
Switch_Stmt,
|
||||
}
|
||||
|
||||
Alignment_Style :: enum {
|
||||
Align_On_Colon_And_Equals,
|
||||
Align_On_Type_And_Equals,
|
||||
}
|
||||
|
||||
Newline_Style :: enum {
|
||||
CRLF,
|
||||
LF,
|
||||
}
|
||||
|
||||
default_style := Config {
|
||||
spaces = 4,
|
||||
newline_limit = 2,
|
||||
convert_do = false,
|
||||
semicolons = true,
|
||||
tabs = true,
|
||||
brace_style = ._1TBS,
|
||||
split_multiple_stmts = true,
|
||||
align_assignments = true,
|
||||
align_style = .Align_On_Type_And_Equals,
|
||||
indent_cases = false,
|
||||
align_switch = true,
|
||||
align_structs = true,
|
||||
align_enums = true,
|
||||
newline_style = .CRLF,
|
||||
align_length_break = 9,
|
||||
};
|
||||
|
||||
make_printer :: proc(config: Config, allocator := context.allocator) -> Printer {
|
||||
return {
|
||||
config = config,
|
||||
allocator = allocator,
|
||||
debug = false,
|
||||
};
|
||||
}
|
||||
|
||||
print :: proc(p: ^Printer, file: ^ast.File) -> string {
|
||||
|
||||
p.comments = file.comments;
|
||||
|
||||
if len(file.decls) > 0 {
|
||||
p.lines = make([dynamic]Line, 0, (file.decls[len(file.decls) - 1].end.line - file.decls[0].pos.line) * 2, context.temp_allocator);
|
||||
}
|
||||
|
||||
set_source_position(p, file.pkg_token.pos);
|
||||
|
||||
p.last_source_position.line = 1;
|
||||
|
||||
set_line(p, 0);
|
||||
|
||||
push_generic_token(p, .Package, 0);
|
||||
push_ident_token(p, file.pkg_name, 1);
|
||||
|
||||
for decl in file.decls {
|
||||
visit_decl(p, cast(^ast.Decl)decl);
|
||||
}
|
||||
|
||||
if len(p.comments) > 0 {
|
||||
infinite := p.comments[len(p.comments) - 1].end;
|
||||
infinite.offset = 9999999;
|
||||
push_comments(p, infinite);
|
||||
}
|
||||
|
||||
fix_lines(p);
|
||||
|
||||
builder := strings.make_builder(0, mem.megabytes(5), p.allocator);
|
||||
|
||||
last_line := 0;
|
||||
|
||||
newline: string;
|
||||
|
||||
if p.config.newline_style == .LF {
|
||||
newline = "\n";
|
||||
} else {
|
||||
newline = "\r\n";
|
||||
}
|
||||
|
||||
for line, line_index in p.lines {
|
||||
diff_line := line_index - last_line;
|
||||
|
||||
for i := 0; i < diff_line; i += 1 {
|
||||
strings.write_string(&builder, newline);
|
||||
}
|
||||
|
||||
if p.config.tabs {
|
||||
for i := 0; i < line.depth; i += 1 {
|
||||
strings.write_byte(&builder, '\t');
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < line.depth * p.config.spaces; i += 1 {
|
||||
strings.write_byte(&builder, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
if p.debug {
|
||||
strings.write_string(&builder, fmt.tprintf("line %v: ", line_index));
|
||||
}
|
||||
|
||||
for format_token in line.format_tokens {
|
||||
|
||||
for i := 0; i < format_token.spaces_before; i += 1 {
|
||||
strings.write_byte(&builder, ' ');
|
||||
}
|
||||
|
||||
strings.write_string(&builder, format_token.text);
|
||||
}
|
||||
|
||||
last_line = line_index;
|
||||
}
|
||||
|
||||
strings.write_string(&builder, newline);
|
||||
|
||||
return strings.to_string(builder);
|
||||
}
|
||||
|
||||
fix_lines :: proc(p: ^Printer) {
|
||||
align_var_decls(p);
|
||||
format_generic(p);
|
||||
align_comments(p); //align them last since they rely on the other alignments
|
||||
}
|
||||
|
||||
format_value_decl :: proc(p: ^Printer, index: int) {
|
||||
|
||||
eq_found := false;
|
||||
eq_token: Format_Token;
|
||||
eq_line: int;
|
||||
largest := 0;
|
||||
|
||||
found_eq: for line, line_index in p.lines[index:] {
|
||||
for format_token in line.format_tokens {
|
||||
|
||||
largest += len(format_token.text) + format_token.spaces_before;
|
||||
|
||||
if format_token.kind == .Eq {
|
||||
eq_token = format_token;
|
||||
eq_line = line_index + index;
|
||||
eq_found = true;
|
||||
break found_eq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !eq_found {
|
||||
return;
|
||||
}
|
||||
|
||||
align_next := false;
|
||||
|
||||
//check to see if there is a binary operator in the last token(this is guaranteed by the ast visit), otherwise it's not multilined
|
||||
for line, line_index in p.lines[eq_line:] {
|
||||
|
||||
if len(line.format_tokens) == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if align_next {
|
||||
line.format_tokens[0].spaces_before = largest + 1;
|
||||
align_next = false;
|
||||
}
|
||||
|
||||
kind := find_last_token(line.format_tokens).kind;
|
||||
|
||||
if tokenizer.Token_Kind.B_Operator_Begin < kind && kind <= tokenizer.Token_Kind.Cmp_Or {
|
||||
align_next = true;
|
||||
}
|
||||
|
||||
if !align_next {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
find_last_token :: proc(format_tokens: [dynamic]Format_Token) -> Format_Token {
|
||||
|
||||
for i := len(format_tokens) - 1; i >= 0; i -= 1 {
|
||||
|
||||
if format_tokens[i].kind != .Comment {
|
||||
return format_tokens[i];
|
||||
}
|
||||
}
|
||||
|
||||
panic("not possible");
|
||||
}
|
||||
|
||||
format_assignment :: proc(p: ^Printer, index: int) {
|
||||
}
|
||||
|
||||
format_call :: proc(p: ^Printer, line_index: int, format_index: int) {
|
||||
|
||||
paren_found := false;
|
||||
paren_token: Format_Token;
|
||||
paren_line: int;
|
||||
paren_token_index: int;
|
||||
largest := 0;
|
||||
|
||||
found_paren: for line, i in p.lines[line_index:] {
|
||||
for format_token, j in line.format_tokens {
|
||||
|
||||
largest += len(format_token.text) + format_token.spaces_before;
|
||||
|
||||
if i == 0 && j < format_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
if format_token.kind == .Open_Paren && format_token.type == .Call {
|
||||
paren_token = format_token;
|
||||
paren_line = line_index + i;
|
||||
paren_found = true;
|
||||
paren_token_index = j;
|
||||
break found_paren;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !paren_found {
|
||||
panic("Should not be possible");
|
||||
}
|
||||
|
||||
paren_count := 1;
|
||||
done := false;
|
||||
|
||||
for line, line_index in p.lines[paren_line:] {
|
||||
|
||||
if len(line.format_tokens) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
for format_token, i in line.format_tokens {
|
||||
|
||||
if format_token.kind == .Comment {
|
||||
continue;
|
||||
}
|
||||
|
||||
if line_index == 0 && i <= paren_token_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
if format_token.kind == .Open_Paren {
|
||||
paren_count += 1;
|
||||
} else if format_token.kind == .Close_Paren {
|
||||
paren_count -= 1;
|
||||
}
|
||||
|
||||
if paren_count == 0 {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if line_index != 0 {
|
||||
line.format_tokens[0].spaces_before = largest;
|
||||
}
|
||||
|
||||
if done {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format_keyword_to_brace :: proc(p: ^Printer, line_index: int, format_index: int, keyword: tokenizer.Token_Kind) {
|
||||
|
||||
keyword_found := false;
|
||||
keyword_token: Format_Token;
|
||||
keyword_line: int;
|
||||
|
||||
largest := 0;
|
||||
brace_count := 0;
|
||||
done := false;
|
||||
|
||||
found_keyword: for line, i in p.lines[line_index:] {
|
||||
for format_token in line.format_tokens {
|
||||
|
||||
largest += len(format_token.text) + format_token.spaces_before;
|
||||
|
||||
if format_token.kind == keyword {
|
||||
keyword_token = format_token;
|
||||
keyword_line = line_index + i;
|
||||
keyword_found = true;
|
||||
break found_keyword;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !keyword_found {
|
||||
panic("Should not be possible");
|
||||
}
|
||||
|
||||
for line, line_index in p.lines[keyword_line:] {
|
||||
|
||||
if len(line.format_tokens) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
for format_token, i in line.format_tokens {
|
||||
|
||||
if format_token.kind == .Comment {
|
||||
break;
|
||||
} else if format_token.kind == .Undef {
|
||||
return;
|
||||
}
|
||||
|
||||
if line_index == 0 && i <= format_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
if format_token.kind == .Open_Brace {
|
||||
brace_count += 1;
|
||||
} else if format_token.kind == .Close_Brace {
|
||||
brace_count -= 1;
|
||||
}
|
||||
|
||||
if brace_count == 1 {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if line_index != 0 {
|
||||
line.format_tokens[0].spaces_before = largest + 1;
|
||||
}
|
||||
|
||||
if done {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format_generic :: proc(p: ^Printer) {
|
||||
|
||||
next_struct_line := 0;
|
||||
|
||||
for line, line_index in p.lines {
|
||||
|
||||
if len(line.format_tokens) <= 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
for format_token, token_index in line.format_tokens {
|
||||
if format_token.kind == .For || format_token.kind == .If ||
|
||||
format_token.kind == .When || format_token.kind == .Switch ||
|
||||
(format_token.kind == .Proc && format_token.type == .Proc_Lit) {
|
||||
format_keyword_to_brace(p, line_index, token_index, format_token.kind);
|
||||
} else if format_token.type == .Call {
|
||||
format_call(p, line_index, token_index);
|
||||
}
|
||||
}
|
||||
|
||||
if .Switch_Stmt in line.types && p.config.align_switch {
|
||||
align_switch_stmt(p, line_index);
|
||||
}
|
||||
|
||||
if .Enum in line.types && p.config.align_enums {
|
||||
align_enum(p, line_index);
|
||||
}
|
||||
|
||||
if .Struct in line.types && p.config.align_structs && next_struct_line <= 0 {
|
||||
next_struct_line = align_struct(p, line_index);
|
||||
}
|
||||
|
||||
if .Value_Decl in line.types {
|
||||
format_value_decl(p, line_index);
|
||||
}
|
||||
|
||||
if .Assign in line.types {
|
||||
format_assignment(p, line_index);
|
||||
}
|
||||
|
||||
next_struct_line -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
align_var_decls :: proc(p: ^Printer) {
|
||||
|
||||
current_line: int;
|
||||
current_typed: bool;
|
||||
current_not_mutable: bool;
|
||||
|
||||
largest_lhs := 0;
|
||||
largest_rhs := 0;
|
||||
|
||||
TokenAndLength :: struct {
|
||||
format_token: ^Format_Token,
|
||||
length: int,
|
||||
};
|
||||
|
||||
colon_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator);
|
||||
type_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator);
|
||||
equal_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator);
|
||||
|
||||
for line, line_index in p.lines {
|
||||
|
||||
//It is only possible to align value decls that are one one line, otherwise just ignore them
|
||||
if .Value_Decl not_in line.types {
|
||||
continue;
|
||||
}
|
||||
|
||||
typed := true;
|
||||
not_mutable := false;
|
||||
continue_flag := false;
|
||||
|
||||
for i := 0; i < len(line.format_tokens); i += 1 {
|
||||
if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Eq {
|
||||
typed = false;
|
||||
}
|
||||
|
||||
if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Colon {
|
||||
not_mutable = true;
|
||||
}
|
||||
|
||||
if line.format_tokens[i].kind == .Union ||
|
||||
line.format_tokens[i].kind == .Enum ||
|
||||
line.format_tokens[i].kind == .Struct ||
|
||||
line.format_tokens[i].kind == .For ||
|
||||
line.format_tokens[i].kind == .If ||
|
||||
line.format_tokens[i].kind == .Comment {
|
||||
continue_flag = true;
|
||||
}
|
||||
|
||||
//enforced undef is always on the last line, if it exists
|
||||
if line.format_tokens[i].kind == .Proc && line.format_tokens[len(line.format_tokens)-1].kind != .Undef {
|
||||
continue_flag = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if continue_flag {
|
||||
continue;
|
||||
}
|
||||
|
||||
if line_index != current_line + 1 || typed != current_typed || not_mutable != current_not_mutable {
|
||||
|
||||
if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
|
||||
for colon_token in colon_tokens {
|
||||
colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1;
|
||||
}
|
||||
} else if p.config.align_style == .Align_On_Type_And_Equals {
|
||||
for type_token in type_tokens {
|
||||
type_token.format_token.spaces_before = largest_lhs - type_token.length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if current_typed {
|
||||
for equal_token in equal_tokens {
|
||||
equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1;
|
||||
}
|
||||
} else {
|
||||
for equal_token in equal_tokens {
|
||||
equal_token.format_token.spaces_before = 0;
|
||||
}
|
||||
}
|
||||
|
||||
clear(&colon_tokens);
|
||||
clear(&type_tokens);
|
||||
clear(&equal_tokens);
|
||||
|
||||
largest_rhs = 0;
|
||||
largest_lhs = 0;
|
||||
current_typed = typed;
|
||||
current_not_mutable = not_mutable;
|
||||
}
|
||||
|
||||
current_line = line_index;
|
||||
|
||||
current_token_index := 0;
|
||||
lhs_length := 0;
|
||||
rhs_length := 0;
|
||||
|
||||
//calcuate the length of lhs of a value decl i.e. `a, b:`
|
||||
for; current_token_index < len(line.format_tokens); current_token_index += 1 {
|
||||
|
||||
lhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before;
|
||||
|
||||
if line.format_tokens[current_token_index].kind == .Colon {
|
||||
append(&colon_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = lhs_length});
|
||||
|
||||
if len(line.format_tokens) > current_token_index && line.format_tokens[current_token_index + 1].kind != .Eq {
|
||||
append(&type_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index + 1], length = lhs_length});
|
||||
}
|
||||
|
||||
current_token_index += 1;
|
||||
largest_lhs = max(largest_lhs, lhs_length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//calcuate the length of the rhs i.e. `[dynamic]int = 123123`
|
||||
for; current_token_index < len(line.format_tokens); current_token_index += 1 {
|
||||
|
||||
rhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before;
|
||||
|
||||
if line.format_tokens[current_token_index].kind == .Eq {
|
||||
append(&equal_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = rhs_length});
|
||||
largest_rhs = max(largest_rhs, rhs_length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//repeating myself, move to sub procedure
|
||||
if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
|
||||
for colon_token in colon_tokens {
|
||||
colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1;
|
||||
}
|
||||
} else if p.config.align_style == .Align_On_Type_And_Equals {
|
||||
for type_token in type_tokens {
|
||||
type_token.format_token.spaces_before = largest_lhs - type_token.length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if current_typed {
|
||||
for equal_token in equal_tokens {
|
||||
equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1;
|
||||
}
|
||||
} else {
|
||||
for equal_token in equal_tokens {
|
||||
equal_token.format_token.spaces_before = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
align_switch_stmt :: proc(p: ^Printer, index: int) {
|
||||
switch_found := false;
|
||||
brace_token: Format_Token;
|
||||
brace_line: int;
|
||||
|
||||
found_switch_brace: for line, line_index in p.lines[index:] {
|
||||
for format_token in line.format_tokens {
|
||||
if format_token.kind == .Open_Brace && switch_found {
|
||||
brace_token = format_token;
|
||||
brace_line = line_index + index;
|
||||
break found_switch_brace;
|
||||
} else if format_token.kind == .Open_Brace {
|
||||
break;
|
||||
} else if format_token.kind == .Switch {
|
||||
switch_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !switch_found {
|
||||
return;
|
||||
}
|
||||
|
||||
largest := 0;
|
||||
case_count := 0;
|
||||
|
||||
TokenAndLength :: struct {
|
||||
format_token: ^Format_Token,
|
||||
length: int,
|
||||
};
|
||||
|
||||
format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator);
|
||||
|
||||
//find all the switch cases that are one lined
|
||||
for line, line_index in p.lines[brace_line + 1:] {
|
||||
|
||||
case_found := false;
|
||||
colon_found := false;
|
||||
length := 0;
|
||||
|
||||
for format_token, i in line.format_tokens {
|
||||
|
||||
if format_token.kind == .Comment {
|
||||
break;
|
||||
}
|
||||
|
||||
//this will only happen if the case is one lined
|
||||
if case_found && colon_found {
|
||||
append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length});
|
||||
largest = max(length, largest);
|
||||
break;
|
||||
}
|
||||
|
||||
if format_token.kind == .Case {
|
||||
case_found = true;
|
||||
case_count += 1;
|
||||
} else if format_token.kind == .Colon {
|
||||
colon_found = true;
|
||||
}
|
||||
|
||||
length += len(format_token.text) + format_token.spaces_before;
|
||||
}
|
||||
|
||||
if case_count >= brace_token.parameter_count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for token in format_tokens {
|
||||
token.format_token.spaces_before = largest - token.length + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
align_enum :: proc(p: ^Printer, index: int) {
|
||||
enum_found := false;
|
||||
brace_token: Format_Token;
|
||||
brace_line: int;
|
||||
|
||||
found_enum_brace: for line, line_index in p.lines[index:] {
|
||||
for format_token in line.format_tokens {
|
||||
if format_token.kind == .Open_Brace && enum_found {
|
||||
brace_token = format_token;
|
||||
brace_line = line_index + index;
|
||||
break found_enum_brace;
|
||||
} else if format_token.kind == .Open_Brace {
|
||||
break;
|
||||
} else if format_token.kind == .Enum {
|
||||
enum_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !enum_found {
|
||||
return;
|
||||
}
|
||||
|
||||
largest := 0;
|
||||
comma_count := 0;
|
||||
|
||||
TokenAndLength :: struct {
|
||||
format_token: ^Format_Token,
|
||||
length: int,
|
||||
};
|
||||
|
||||
format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator);
|
||||
|
||||
for line, line_index in p.lines[brace_line + 1:] {
|
||||
length := 0;
|
||||
|
||||
for format_token, i in line.format_tokens {
|
||||
if format_token.kind == .Comment {
|
||||
break;
|
||||
}
|
||||
|
||||
if format_token.kind == .Eq {
|
||||
append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length});
|
||||
largest = max(length, largest);
|
||||
break;
|
||||
} else if format_token.kind == .Comma {
|
||||
comma_count += 1;
|
||||
}
|
||||
|
||||
length += len(format_token.text) + format_token.spaces_before;
|
||||
}
|
||||
|
||||
if comma_count >= brace_token.parameter_count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for token in format_tokens {
|
||||
token.format_token.spaces_before = largest - token.length + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
align_struct :: proc(p: ^Printer, index: int) -> int {
|
||||
struct_found := false;
|
||||
brace_token: Format_Token;
|
||||
brace_line: int;
|
||||
|
||||
found_struct_brace: for line, line_index in p.lines[index:] {
|
||||
for format_token in line.format_tokens {
|
||||
if format_token.kind == .Open_Brace && struct_found {
|
||||
brace_token = format_token;
|
||||
brace_line = line_index + index;
|
||||
break found_struct_brace;
|
||||
} else if format_token.kind == .Open_Brace {
|
||||
break;
|
||||
} else if format_token.kind == .Struct {
|
||||
struct_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !struct_found {
|
||||
return 0;
|
||||
}
|
||||
|
||||
largest := 0;
|
||||
colon_count := 0;
|
||||
nested := false;
|
||||
seen_brace := false;
|
||||
|
||||
TokenAndLength :: struct {
|
||||
format_token: ^Format_Token,
|
||||
length: int,
|
||||
};
|
||||
|
||||
format_tokens := make([]TokenAndLength, brace_token.parameter_count, context.temp_allocator);
|
||||
|
||||
if brace_token.parameter_count == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
end_line_index := 0;
|
||||
|
||||
for line, line_index in p.lines[brace_line + 1:] {
|
||||
length := 0;
|
||||
|
||||
for format_token, i in line.format_tokens {
|
||||
|
||||
//give up on nested structs
|
||||
if format_token.kind == .Comment {
|
||||
break;
|
||||
} else if format_token.kind == .Open_Paren {
|
||||
break;
|
||||
} else if format_token.kind == .Open_Brace {
|
||||
seen_brace = true;
|
||||
} else if format_token.kind == .Close_Brace {
|
||||
seen_brace = false;
|
||||
} else if seen_brace {
|
||||
continue;
|
||||
}
|
||||
|
||||
if format_token.kind == .Colon {
|
||||
format_tokens[colon_count] = {format_token = &line.format_tokens[i + 1], length = length};
|
||||
|
||||
if format_tokens[colon_count].format_token.kind == .Struct {
|
||||
nested = true;
|
||||
}
|
||||
|
||||
colon_count += 1;
|
||||
largest = max(length, largest);
|
||||
}
|
||||
|
||||
length += len(format_token.text) + format_token.spaces_before;
|
||||
}
|
||||
|
||||
if nested {
|
||||
end_line_index = line_index + brace_line + 1;
|
||||
}
|
||||
|
||||
if colon_count >= brace_token.parameter_count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//give up aligning nested, it never looks good
|
||||
if nested {
|
||||
for line, line_index in p.lines[end_line_index:] {
|
||||
for format_token in line.format_tokens {
|
||||
if format_token.kind == .Close_Brace {
|
||||
return end_line_index + line_index - index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for token in format_tokens {
|
||||
token.format_token.spaces_before = largest - token.length + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
align_comments :: proc(p: ^Printer) {
|
||||
|
||||
Comment_Align_Info :: struct {
|
||||
length: int,
|
||||
begin: int,
|
||||
end: int,
|
||||
depth: int,
|
||||
};
|
||||
|
||||
comment_infos := make([dynamic]Comment_Align_Info, 0, context.temp_allocator);
|
||||
|
||||
current_info: Comment_Align_Info;
|
||||
|
||||
for line, line_index in p.lines {
|
||||
if len(line.format_tokens) <= 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if .Line_Comment in line.types {
|
||||
if current_info.end + 1 != line_index || current_info.depth != line.depth ||
|
||||
(current_info.begin == current_info.end && current_info.length == 0) {
|
||||
|
||||
if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
|
||||
append(&comment_infos, current_info);
|
||||
}
|
||||
|
||||
current_info.begin = line_index;
|
||||
current_info.end = line_index;
|
||||
current_info.depth = line.depth;
|
||||
current_info.length = 0;
|
||||
}
|
||||
|
||||
length := 0;
|
||||
|
||||
for format_token, i in line.format_tokens {
|
||||
if format_token.kind == .Comment {
|
||||
current_info.length = max(current_info.length, length);
|
||||
current_info.end = line_index;
|
||||
}
|
||||
|
||||
length += format_token.spaces_before + len(format_token.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
|
||||
append(&comment_infos, current_info);
|
||||
}
|
||||
|
||||
for info in comment_infos {
|
||||
|
||||
if info.begin == info.end || info.length == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
for i := info.begin; i <= info.end; i += 1 {
|
||||
l := p.lines[i];
|
||||
|
||||
length := 0;
|
||||
|
||||
for format_token, i in l.format_tokens {
|
||||
if format_token.kind == .Comment {
|
||||
if len(l.format_tokens) == 1 {
|
||||
l.format_tokens[i].spaces_before = info.length + 1;
|
||||
} else {
|
||||
l.format_tokens[i].spaces_before = info.length - length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
length += format_token.spaces_before + len(format_token.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1525
core/odin/printer/visit.odin
Normal file
1525
core/odin/printer/visit.odin
Normal file
File diff suppressed because it is too large
Load Diff
@@ -608,7 +608,7 @@ scan :: proc(t: ^Tokenizer) -> Token {
|
||||
kind = switch3(t, .And, .And_Eq, '&', .Cmp_And);
|
||||
}
|
||||
case '|': kind = switch3(t, .Or, .Or_Eq, '|', .Cmp_Or);
|
||||
case '~': kind = .Xor;
|
||||
case '~': kind = switch2(t, .Xor, .Xor_Eq);
|
||||
case '<': kind = switch4(t, .Lt, .Lt_Eq, '<', .Shl, .Shl_Eq);
|
||||
case '>': kind = switch4(t, .Gt, .Gt_Eq, '>', .Shr,.Shr_Eq);
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ is_file :: proc(path: string) -> bool {
|
||||
attribs := win32.GetFileAttributesW(wpath);
|
||||
|
||||
if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
|
||||
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == win32.FILE_ATTRIBUTE_DIRECTORY;
|
||||
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -283,7 +283,7 @@ is_dir :: proc(path: string) -> bool {
|
||||
attribs := win32.GetFileAttributesW(wpath);
|
||||
|
||||
if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
|
||||
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != win32.FILE_ATTRIBUTE_DIRECTORY;
|
||||
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
216
tools/odinfmt/flag/flag.odin
Normal file
216
tools/odinfmt/flag/flag.odin
Normal file
@@ -0,0 +1,216 @@
|
||||
package flag
|
||||
|
||||
import "core:runtime"
|
||||
import "core:strings"
|
||||
import "core:reflect"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:strconv"
|
||||
|
||||
Flag_Error :: enum {
|
||||
None,
|
||||
No_Base_Struct,
|
||||
Arg_Error,
|
||||
Arg_Unsupported_Field_Type,
|
||||
Arg_Not_Defined,
|
||||
Arg_Non_Optional,
|
||||
Value_Parse_Error,
|
||||
Tag_Error,
|
||||
}
|
||||
|
||||
Flag :: struct {
|
||||
optional: bool,
|
||||
type: ^runtime.Type_Info,
|
||||
data: rawptr,
|
||||
tag_ptr: rawptr,
|
||||
parsed: bool,
|
||||
}
|
||||
|
||||
Flag_Context :: struct {
|
||||
seen_flags: map[string]Flag,
|
||||
}
|
||||
|
||||
parse_args :: proc(ctx: ^Flag_Context, args: []string) -> Flag_Error {
|
||||
|
||||
using runtime;
|
||||
|
||||
args := args;
|
||||
|
||||
for true {
|
||||
|
||||
if len(args) == 0 {
|
||||
return .None;
|
||||
}
|
||||
|
||||
arg := args[0];
|
||||
|
||||
if len(arg) < 2 || arg[0] != '-' {
|
||||
return .Arg_Error;
|
||||
}
|
||||
|
||||
minus_count := 1;
|
||||
|
||||
if arg[1] == '-' {
|
||||
minus_count += 1;
|
||||
|
||||
if len(arg) == 2 {
|
||||
return .Arg_Error;
|
||||
}
|
||||
}
|
||||
|
||||
name := arg[minus_count:];
|
||||
|
||||
if len(name) == 0 {
|
||||
return .Arg_Error;
|
||||
}
|
||||
|
||||
args = args[1:];
|
||||
|
||||
assign_index := strings.index(name, "=");
|
||||
|
||||
value := "";
|
||||
|
||||
if assign_index > 0 {
|
||||
value = name[assign_index + 1:];
|
||||
name = name[0:assign_index];
|
||||
}
|
||||
|
||||
flag := &ctx.seen_flags[name];
|
||||
|
||||
if flag == nil {
|
||||
return .Arg_Not_Defined;
|
||||
}
|
||||
|
||||
if reflect.is_boolean(flag.type) {
|
||||
tmp := true;
|
||||
mem.copy(flag.data, &tmp, flag.type.size);
|
||||
flag.parsed = true;
|
||||
continue;
|
||||
} else
|
||||
|
||||
//must be in the next argument
|
||||
if value == "" {
|
||||
|
||||
if len(args) == 0 {
|
||||
return .Arg_Error;
|
||||
}
|
||||
|
||||
value = args[0];
|
||||
args = args[1:];
|
||||
}
|
||||
|
||||
#partial switch _ in flag.type.variant {
|
||||
case Type_Info_Integer:
|
||||
if v, ok := strconv.parse_int(value); ok {
|
||||
mem.copy(flag.data, &v, flag.type.size);
|
||||
} else {
|
||||
return .Value_Parse_Error;
|
||||
}
|
||||
case Type_Info_String:
|
||||
raw_string := cast(^mem.Raw_String)flag.data;
|
||||
raw_string.data = strings.ptr_from_string(value);
|
||||
raw_string.len = len(value);
|
||||
case Type_Info_Float:
|
||||
switch flag.type.size {
|
||||
case 32:
|
||||
if v, ok := strconv.parse_f32(value); ok {
|
||||
mem.copy(flag.data, &v, flag.type.size);
|
||||
} else {
|
||||
return .Value_Parse_Error;
|
||||
}
|
||||
case 64:
|
||||
if v, ok := strconv.parse_f64(value); ok {
|
||||
mem.copy(flag.data, &v, flag.type.size);
|
||||
} else {
|
||||
return .Value_Parse_Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flag.parsed = true;
|
||||
}
|
||||
|
||||
return .None;
|
||||
}
|
||||
|
||||
reflect_args_structure :: proc(ctx: ^Flag_Context, v: any) -> Flag_Error {
|
||||
using runtime;
|
||||
|
||||
if !reflect.is_struct(type_info_of(v.id)) {
|
||||
return .No_Base_Struct;
|
||||
}
|
||||
|
||||
names := reflect.struct_field_names(v.id);
|
||||
types := reflect.struct_field_types(v.id);
|
||||
offsets := reflect.struct_field_offsets(v.id);
|
||||
tags := reflect.struct_field_tags(v.id);
|
||||
|
||||
for name, i in names {
|
||||
flag: Flag;
|
||||
|
||||
type := types[i];
|
||||
|
||||
if named_type, ok := type.variant.(Type_Info_Named); ok {
|
||||
if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && union_type.maybe && len(union_type.variants) == 1 {
|
||||
flag.optional = true;
|
||||
flag.tag_ptr = rawptr(uintptr(union_type.tag_offset) + uintptr(v.data) + uintptr(offsets[i]));
|
||||
type = union_type.variants[0];
|
||||
} else {
|
||||
return .Arg_Unsupported_Field_Type;
|
||||
}
|
||||
}
|
||||
|
||||
#partial switch _ in type.variant {
|
||||
case Type_Info_Integer, Type_Info_String, Type_Info_Boolean, Type_Info_Float:
|
||||
flag.type = type;
|
||||
flag.data = rawptr(uintptr(v.data) + uintptr(offsets[i]));
|
||||
case:
|
||||
return .Arg_Unsupported_Field_Type;
|
||||
}
|
||||
|
||||
flag_name: string;
|
||||
|
||||
if value, ok := reflect.struct_tag_lookup(tags[i], "flag"); ok {
|
||||
flag_name = cast(string)value;
|
||||
} else {
|
||||
return .Tag_Error;
|
||||
}
|
||||
|
||||
ctx.seen_flags[flag_name] = flag;
|
||||
}
|
||||
|
||||
return .None;
|
||||
}
|
||||
|
||||
parse :: proc(v: any, args: []string) -> Flag_Error {
|
||||
|
||||
if v == nil {
|
||||
return .None;
|
||||
}
|
||||
|
||||
ctx: Flag_Context;
|
||||
|
||||
if res := reflect_args_structure(&ctx, v); res != .None {
|
||||
return res;
|
||||
}
|
||||
|
||||
if res := parse_args(&ctx, args); res != .None {
|
||||
return res;
|
||||
}
|
||||
|
||||
//validate that the required flags were actually set
|
||||
for k, v in ctx.seen_flags {
|
||||
if v.optional && v.parsed {
|
||||
tag_value: i32 = 1;
|
||||
mem.copy(v.tag_ptr, &tag_value, 4); //4 constant is probably not portable, but it works for me currently
|
||||
} else if !v.parsed && !v.optional {
|
||||
return .Arg_Non_Optional;
|
||||
}
|
||||
}
|
||||
|
||||
return .None;
|
||||
}
|
||||
|
||||
usage :: proc(v: any) -> string {
|
||||
return "failed";
|
||||
}
|
||||
122
tools/odinfmt/main.odin
Normal file
122
tools/odinfmt/main.odin
Normal file
@@ -0,0 +1,122 @@
|
||||
package odinfmt
|
||||
|
||||
import "core:os"
|
||||
import "core:odin/format"
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
import "core:path/filepath"
|
||||
import "core:time"
|
||||
import "core:mem"
|
||||
|
||||
import "flag"
|
||||
|
||||
Args :: struct {
|
||||
write: Maybe(bool) `flag:"w" usage:"write the new format to file"`,
|
||||
}
|
||||
|
||||
print_help :: proc() {
|
||||
}
|
||||
|
||||
print_arg_error :: proc(error: flag.Flag_Error) {
|
||||
fmt.println(error);
|
||||
}
|
||||
|
||||
format_file :: proc(filepath: string) -> ([]u8, bool) {
|
||||
|
||||
if data, ok := os.read_entire_file(filepath); ok {
|
||||
return format.format(data, format.default_style);
|
||||
} else {
|
||||
return {}, false;
|
||||
}
|
||||
}
|
||||
|
||||
files: [dynamic]string;
|
||||
|
||||
walk_files :: proc(info: os.File_Info, in_err: os.Errno) -> (err: os.Errno, skip_dir: bool) {
|
||||
|
||||
if info.is_dir {
|
||||
return 0, false;
|
||||
}
|
||||
|
||||
if filepath.ext(info.name) != ".odin" {
|
||||
return 0, false;
|
||||
}
|
||||
|
||||
append(&files, strings.clone(info.fullpath));
|
||||
|
||||
return 0, false;
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
|
||||
init_global_temporary_allocator(mem.megabytes(100));
|
||||
|
||||
args: Args;
|
||||
|
||||
if len(os.args) < 2 {
|
||||
print_help();
|
||||
os.exit(1);
|
||||
}
|
||||
|
||||
if res := flag.parse(args, os.args[1:len(os.args) - 1]); res != .None {
|
||||
print_arg_error(res);
|
||||
os.exit(1);
|
||||
}
|
||||
|
||||
path := os.args[len(os.args) - 1];
|
||||
|
||||
tick_time := time.tick_now();
|
||||
|
||||
if os.is_file(path) {
|
||||
if _, ok := args.write.(bool); ok {
|
||||
backup_path := strings.concatenate({path, "_bk"}, context.temp_allocator);
|
||||
|
||||
if data, ok := format_file(path); ok {
|
||||
|
||||
os.rename(path, backup_path);
|
||||
|
||||
if os.write_entire_file(path, data) {
|
||||
os.remove(backup_path);
|
||||
}
|
||||
} else {
|
||||
fmt.eprintf("failed to write %v", path);
|
||||
}
|
||||
} else {
|
||||
if data, ok := format_file(path); ok {
|
||||
fmt.println(transmute(string)data);
|
||||
}
|
||||
}
|
||||
} else if os.is_dir(path) {
|
||||
filepath.walk(path, walk_files);
|
||||
|
||||
for file in files {
|
||||
fmt.println(file);
|
||||
|
||||
backup_path := strings.concatenate({file, "_bk"}, context.temp_allocator);
|
||||
|
||||
if data, ok := format_file(file); ok {
|
||||
|
||||
if _, ok := args.write.(bool); ok {
|
||||
os.rename(file, backup_path);
|
||||
|
||||
if os.write_entire_file(file, data) {
|
||||
os.remove(backup_path);
|
||||
}
|
||||
} else {
|
||||
fmt.println(transmute(string)data);
|
||||
}
|
||||
} else {
|
||||
fmt.eprintf("failed to format %v", file);
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator);
|
||||
}
|
||||
|
||||
fmt.printf("formatted %v files in %vms", len(files), time.duration_milliseconds(time.tick_lap_time(&tick_time)));
|
||||
} else {
|
||||
fmt.eprintf("%v is neither a directory nor a file \n", path);
|
||||
os.exit(1);
|
||||
}
|
||||
|
||||
os.exit(0);
|
||||
}
|
||||
Reference in New Issue
Block a user