aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorDaniel Gavin <danielgavin5@hotmail.com>2021-12-05 20:14:07 +0100
committerDaniel Gavin <danielgavin5@hotmail.com>2021-12-05 20:14:07 +0100
commit8f73e4e4f64b46bee43fe659cad8ea4d505fb3f0 (patch)
tree1a6822ebc2b522c7f4eec8442ac2deca6efb2c53 /tools
parent40f45aa7568b664d9bfc5b1d89e47e46104af2b5 (diff)
Add odinfmt builder
Diffstat (limited to 'tools')
-rw-r--r--tools/odinfmt/flag/flag.odin211
-rw-r--r--tools/odinfmt/main.odin146
2 files changed, 357 insertions, 0 deletions
diff --git a/tools/odinfmt/flag/flag.odin b/tools/odinfmt/flag/flag.odin
new file mode 100644
index 0000000..8d57a06
--- /dev/null
+++ b/tools/odinfmt/flag/flag.odin
@@ -0,0 +1,211 @@
+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 {
+ 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: b64 = true;
+ mem.copy(flag.data, &tmp, flag.type.size);
+ flag.parsed = true;
+ continue;
+ } else if value == "" { // must be in the next argument
+ 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";
+}
diff --git a/tools/odinfmt/main.odin b/tools/odinfmt/main.odin
new file mode 100644
index 0000000..45bbf81
--- /dev/null
+++ b/tools/odinfmt/main.odin
@@ -0,0 +1,146 @@
+package odinfmt
+
+import "core:os"
+import "core:odin/tokenizer"
+import "shared: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(args: []string) {
+ fmt.eprintln("usage: odinfmt -w {filepath}")
+}
+
+print_arg_error :: proc(args: []string, error: flag.Flag_Error) {
+ switch error {
+ case .None:
+ print_help(args);
+ case .No_Base_Struct:
+ fmt.eprintln(args[0], "no base struct");
+ case .Arg_Error:
+ fmt.eprintln(args[0], "argument error");
+ case .Arg_Unsupported_Field_Type:
+ fmt.eprintln(args[0], "argument: unsupported field type");
+ case .Arg_Not_Defined:
+ fmt.eprintln(args[0], "argument: no defined");
+ case .Arg_Non_Optional:
+ fmt.eprintln(args[0], "argument: non optional");
+ case .Value_Parse_Error:
+ fmt.eprintln(args[0], "argument: value parse error");
+ case .Tag_Error:
+ fmt.eprintln(args[0], "argument: tag error");
+ }
+}
+
+format_file :: proc(filepath: string, allocator := context.allocator) -> (string, bool) {
+ if data, ok := os.read_entire_file(filepath, allocator); ok {
+ return format.format(filepath, string(data), format.default_style, {.Optional_Semicolons}, allocator);
+ } 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() {
+
+ arena: mem.Arena;
+ mem.init_arena(&arena, make([]byte, mem.megabytes(20)));
+
+ arena_allocator := mem.arena_allocator(&arena);
+
+ args: Args;
+
+ if len(os.args) < 2 {
+ print_help(os.args);
+ os.exit(1);
+ }
+
+ if res := flag.parse(args, os.args[1:len(os.args) - 1]); res != .None {
+ print_arg_error(os.args, res);
+ os.exit(1);
+ }
+
+ path := os.args[len(os.args) - 1];
+
+ tick_time := time.tick_now();
+
+ write_failure := false;
+
+ if os.is_file(path) {
+ if _, ok := args.write.(bool); ok {
+ backup_path := strings.concatenate({path, "_bk"});
+ defer delete(backup_path);
+
+ if data, ok := format_file(path, arena_allocator); ok {
+ os.rename(path, backup_path);
+
+ if os.write_entire_file(path, transmute([]byte)data) {
+ os.remove(backup_path);
+ }
+ } else {
+ fmt.eprintf("failed to write %v", path);
+ write_failure = true;
+ }
+ } else {
+ if data, ok := format_file(path, arena_allocator); ok {
+ fmt.println(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"});
+ defer delete(backup_path);
+
+ if data, ok := format_file(file, arena_allocator); ok {
+
+ if _, ok := args.write.(bool); ok {
+ os.rename(file, backup_path);
+
+ if os.write_entire_file(file, transmute([]byte)data) {
+ os.remove(backup_path);
+ }
+ } else {
+ fmt.println(data);
+ }
+ } else {
+ fmt.eprintf("failed to format %v", file);
+ write_failure = true;
+ }
+
+ free_all(arena_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(1 if write_failure else 0);
+}