Skip to content

Instantly share code, notes, and snippets.

@kevinw
Last active November 12, 2019 22:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevinw/5747511506729338ddbed517a5c64668 to your computer and use it in GitHub Desktop.
Save kevinw/5747511506729338ddbed517a5c64668 to your computer and use it in GitHub Desktop.
@tweak odin attribute
// An example file using the @tweak attribute.
package main
using import "core:math/linalg"
@tweak
editor_settings : struct {
grid_offset: Vector3,
test_string: string,
test_int: int,
};
show_tweakable_ui :: proc() {
for tweakable in all_tweakables {
ui_label(tweakable.name);
any_ptr := tweakable.ptr();
ui_edit_struct(ctx, tweakable.name, any_ptr.data, type_info_of(any_ptr.id));
}
}
package preprocess
using import "core:fmt"
using import "core:odin/ast"
import "core:mem"
import "core:os"
import "core:odin/parser"
import "core:strings"
// TODO: This won't work for multiple attributes, and for lots of types of declarations.
TWEAKABLE_ATTRIBUTE_NAME :: "tweak";
get_tweakable :: proc (s: ^Stmt) -> (Value_Decl, bool) {
value, ok := s.derived.(Value_Decl);
if !ok do return {}, false;
for attr in value.attributes {
if len(attr.elems) == 0 do continue;
if len(attr.elems) > 1 {
fmt.eprintln("warning: attribute has more than one element");
}
attr_ident, attr_ok := attr.elems[0].derived.(Ident);
if attr_ok && attr_ident.name == TWEAKABLE_ATTRIBUTE_NAME {
return value, true;
}
}
return {}, false;
}
process_tweakable :: proc(builder: ^strings.Builder, value: Value_Decl) {
for name in value.names {
switch ident in name.derived {
case Ident:
identifier_name := ident.name;
sbprintf(builder, " { \"%s\", proc() -> any { return %s; } },\n", identifier_name, identifier_name);
case:
fmt.eprintln("unhandled name type:", ident);
}
}
}
parse_odin_file :: proc(filename: string, p: ^parser.Parser) -> bool {
assert(len(filename) > 0);
assert(p != nil);
data, success := os.read_entire_file(filename);
if !success {
fmt.eprintln("error reading", filename);
return false;
}
pkg := mem.new_clone(ast.Package {
kind = .Init,
name="main",
fullpath=dirname(filename),
});
file := mem.new_clone(ast.File{
id=0, pkg=pkg, fullpath=filename, src=data,
});
files: [1]^ast.File = { file };
pkg.files = files[:];
res := parser.parse_file(p, file);
if !res {
fmt.eprintln("error parsing file", filename);
return false;
}
return true;
}
// TODO: This should be able to accept a package as input, and walk files
// recursively in it. Currently it will only parse one odin source file.
process_tweakable_file :: proc(input_odin_file: string, output_file: string) -> bool {
p := parser.default_parser();
ok := parse_odin_file(input_odin_file, &p);
if !ok {
fmt.eprintln("could not parse", input_odin_file);
return false;
}
builder := strings.make_builder(context.temp_allocator);
sbprintln(&builder, `
// THIS FILE WAS AUTOGENERATED
package main
Tweakable :: struct {
name: string,
ptr: proc() -> any,
};
all_tweakables := [?]Tweakable {`);
for _, index in p.file.decls {
if value, is_tweak := get_tweakable(p.file.decls[index]); is_tweak {
process_tweakable(&builder, value);
}
}
sbprintln(&builder, "};");
write_file_if_different(output_file, builder.buf[:]);
return true;
}
write_file_if_different :: proc(filename: string, contents: []u8) -> bool {
size_on_disk := os.file_size_from_path(filename);
needs_write := false;
if size_on_disk == -1 do needs_write = true;
else if cast(i64)len(contents) != size_on_disk do needs_write = true;
else {
disk_contents, ok := os.read_entire_file(filename);
defer if disk_contents != nil do delete(disk_contents);
if !ok do needs_write = true;
else if 0 != mem.compare(disk_contents, contents) do needs_write = true;
}
if !needs_write do return false;
os.write_entire_file(filename, contents);
fmt.println("-> wrote", filename);
return true;
}
main :: proc() {
process_tweakable_file("src/editor.odin", "src/tweakables.odin");
println("preprocessor finished.");
}
dirname :: proc(path: string, allocator := context.temp_allocator) -> string {
assert(strings.index(path, "/") == -1, "need to handle forward slashes in paths");
// if there's no slash, return ./
last_index := strings.last_index(path, "\\");
if last_index == -1 do return strings.clone(".\\", allocator);
// if it's already a dir
if last_index == len(path) - 1 do return strings.clone(path, allocator);
// otherwise, return the dir part, including the slash
return strings.clone(path[0:last_index + 1], allocator);
}
// THIS FILE WAS AUTOGENERATED
package main
Tweakable :: struct {
name: string,
ptr: proc() -> any,
};
all_tweakables := [?]Tweakable {
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment