diff options
| -rw-r--r-- | odinfmt.bat | 1 | ||||
| -rw-r--r-- | odinfmt.sh | 1 | ||||
| -rw-r--r-- | src/odin/format/format.odin | 40 | ||||
| -rw-r--r-- | tools/odinfmt/flag/flag.odin | 211 | ||||
| -rw-r--r-- | tools/odinfmt/main.odin | 146 |
5 files changed, 399 insertions, 0 deletions
diff --git a/odinfmt.bat b/odinfmt.bat new file mode 100644 index 0000000..a50406c --- /dev/null +++ b/odinfmt.bat @@ -0,0 +1 @@ +odin build tools/odinfmt -show-timings -collection:shared=src -out:odinfmt -opt:2
\ No newline at end of file diff --git a/odinfmt.sh b/odinfmt.sh new file mode 100644 index 0000000..a50406c --- /dev/null +++ b/odinfmt.sh @@ -0,0 +1 @@ +odin build tools/odinfmt -show-timings -collection:shared=src -out:odinfmt -opt:2
\ No newline at end of file diff --git a/src/odin/format/format.odin b/src/odin/format/format.odin new file mode 100644 index 0000000..2abfbec --- /dev/null +++ b/src/odin/format/format.odin @@ -0,0 +1,40 @@ +package odin_format + +import "shared:odin/printer" +import "core:odin/parser" +import "core:odin/ast" + +default_style := printer.default_style + +simplify :: proc(file: ^ast.File) { + +} + +format :: proc(filepath: string, source: string, config: printer.Config, parser_flags := parser.Flags{.Optional_Semicolons}, allocator := context.allocator) -> (string, bool) { + config := config + + pkg := ast.Package { + kind = .Normal, + } + + file := ast.File { + pkg = &pkg, + src = source, + fullpath = filepath, + } + + config.newline_limit = clamp(config.newline_limit, 0, 16) + config.spaces = clamp(config.spaces, 1, 16) + + p := parser.default_parser(parser_flags) + + ok := parser.parse_file(&p, &file) + + if !ok || file.syntax_error_count > 0 { + return {}, false + } + + prnt := printer.make_printer(config, allocator) + + return printer.print(&prnt, &file), true +} 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); +} |