From 3157467e4becbdf0225df4f5d0004e2ddf98ea75 Mon Sep 17 00:00:00 2001 From: Daniel Gavin Date: Tue, 13 Apr 2021 23:59:40 +0200 Subject: [PATCH] bring over the odinfmt code --- core/odin/format/format.odin | 35 ++++++ tools/odinfmt/flag/flag.odin | 232 +++++++++++++++++++++++++++++++++++ tools/odinfmt/main.odin | 140 +++++++++++++++++++++ 3 files changed, 407 insertions(+) create mode 100644 core/odin/format/format.odin create mode 100644 tools/odinfmt/flag/flag.odin create mode 100644 tools/odinfmt/main.odin diff --git a/core/odin/format/format.odin b/core/odin/format/format.odin new file mode 100644 index 000000000..d86808669 --- /dev/null +++ b/core/odin/format/format.odin @@ -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; +} \ No newline at end of file diff --git a/tools/odinfmt/flag/flag.odin b/tools/odinfmt/flag/flag.odin new file mode 100644 index 000000000..543438678 --- /dev/null +++ b/tools/odinfmt/flag/flag.odin @@ -0,0 +1,232 @@ +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; + } + + //must be in the next argument + else 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"; +} \ No newline at end of file diff --git a/tools/odinfmt/main.odin b/tools/odinfmt/main.odin new file mode 100644 index 000000000..78624bd94 --- /dev/null +++ b/tools/odinfmt/main.odin @@ -0,0 +1,140 @@ +package odinfmt + +import "core:os" +import "core:odin/format" +import "core:fmt" +import "core:strings" +import "core:path/filepath" + +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() { + + 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]; + + 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); + } + + + } + + free_all(context.temp_allocator); + } + + fmt.printf("formatted %v files", len(files)); + + } + + else{ + fmt.eprintf("%v is neither a directory nor a file \n", path); + os.exit(1); + } + + os.exit(0); +} \ No newline at end of file