Initial commit
This commit is contained in:
commit
fa2a864365
5 changed files with 403 additions and 0 deletions
75
example/example.odin
Normal file
75
example/example.odin
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package xoark_db
|
||||||
|
|
||||||
|
Date :: struct {
|
||||||
|
year: i16,
|
||||||
|
month: i8,
|
||||||
|
day: i8,
|
||||||
|
}
|
||||||
|
|
||||||
|
Time :: struct {
|
||||||
|
hour: i8,
|
||||||
|
minute: i8,
|
||||||
|
second: i8,
|
||||||
|
}
|
||||||
|
|
||||||
|
Genre :: enum u8 {
|
||||||
|
IDM,
|
||||||
|
Noise,
|
||||||
|
Glitch,
|
||||||
|
Breakcore,
|
||||||
|
SoCalledDnb,
|
||||||
|
Ambient,
|
||||||
|
Placeholder01,
|
||||||
|
Placeholder02,
|
||||||
|
Placeholder03,
|
||||||
|
Placeholder04,
|
||||||
|
Placeholder05,
|
||||||
|
Placeholder06,
|
||||||
|
Placeholder07,
|
||||||
|
Placeholder08,
|
||||||
|
Placeholder09,
|
||||||
|
Placeholder10,
|
||||||
|
Placeholder11,
|
||||||
|
Placeholder12,
|
||||||
|
Placeholder13,
|
||||||
|
Placeholder14,
|
||||||
|
Placeholder15,
|
||||||
|
Placeholder16,
|
||||||
|
}
|
||||||
|
|
||||||
|
Type :: enum u8 {
|
||||||
|
Album,
|
||||||
|
EP,
|
||||||
|
Split,
|
||||||
|
VA,
|
||||||
|
Journey,
|
||||||
|
Single,
|
||||||
|
}
|
||||||
|
|
||||||
|
@export_js Series :: enum u8 {
|
||||||
|
None = -2,
|
||||||
|
Hex,
|
||||||
|
Base64,
|
||||||
|
P,
|
||||||
|
Missing_Broken,
|
||||||
|
Satellits,
|
||||||
|
Praspis,
|
||||||
|
Gnocchi,
|
||||||
|
Runtime,
|
||||||
|
};
|
||||||
|
|
||||||
|
Album :: struct {
|
||||||
|
genre: bit_set[Genre],
|
||||||
|
type: Type,
|
||||||
|
series: Series,
|
||||||
|
rating: u8,
|
||||||
|
listened: Date,
|
||||||
|
released: Date,
|
||||||
|
duration: Time,
|
||||||
|
title: string,
|
||||||
|
art: string,
|
||||||
|
url: string,
|
||||||
|
favorite: string,
|
||||||
|
comment: string,
|
||||||
|
listens: i32,
|
||||||
|
}
|
||||||
1
example/generate.sh
Executable file
1
example/generate.sh
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
odin run ../../odin_js_enum_gen -custom-attribute:"export_js" -- . output.js
|
||||||
11
example/output.js
Normal file
11
example/output.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
const Series = Object.freeze({
|
||||||
|
None: -2,
|
||||||
|
Hex: -1,
|
||||||
|
Base64: 0,
|
||||||
|
P: 1,
|
||||||
|
Missing_Broken: 2,
|
||||||
|
Satellits: 3,
|
||||||
|
Praspis: 4,
|
||||||
|
Gnocchi: 5,
|
||||||
|
Runtime: 6,
|
||||||
|
});
|
||||||
209
main.odin
Normal file
209
main.odin
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "core:odin/ast"
|
||||||
|
import "core:odin/parser"
|
||||||
|
import "core:odin/tokenizer"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:strconv"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
concat :: strings.concatenate
|
||||||
|
|
||||||
|
// TOOD (synthas): Make changeable
|
||||||
|
ATTRIBUTE_NAME :: "export_js"
|
||||||
|
|
||||||
|
@rodata program_switch_definitions := []SwitchDefinition {
|
||||||
|
{
|
||||||
|
name = "Editor",
|
||||||
|
fullname = "editor",
|
||||||
|
shortname = "e",
|
||||||
|
usage = "",
|
||||||
|
description = "Opens the game in editor mode",
|
||||||
|
no_argument = true,
|
||||||
|
callback = proc(value: string, args: rawptr) -> bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
print_help :: proc() {
|
||||||
|
fmt.println(
|
||||||
|
"--- ODIN JS ENUM GEN ---",
|
||||||
|
"Usage: <program> <odin module path> <js output file> path",
|
||||||
|
sep = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
if len(os.args) < 2 {
|
||||||
|
fmt.eprint("Too few arguments")
|
||||||
|
print_help();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
odin_module_path := os.args[1]
|
||||||
|
js_output_file_path := os.args[2]
|
||||||
|
if result := program(odin_module_path, js_output_file_path); !result.ok {
|
||||||
|
fmt.eprintln("ERROR ", result.error, ": ", result.message, sep = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Result :: struct {
|
||||||
|
ok: bool,
|
||||||
|
error: Error,
|
||||||
|
message: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
Error :: union {
|
||||||
|
ProgramError,
|
||||||
|
os.Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgramError :: enum {
|
||||||
|
OdinModuleNotExist,
|
||||||
|
TooManyDeclarations,
|
||||||
|
InvalidAttributeUse,
|
||||||
|
CouldntParseEnumValue,
|
||||||
|
EnumValueSmaller,
|
||||||
|
}
|
||||||
|
|
||||||
|
program :: proc(odin_module_path, js_output_file_path: string) -> Result {
|
||||||
|
if !os.exists(odin_module_path) {
|
||||||
|
message := strings.concatenate({"Module didn't exist at location '", odin_module_path, "'"}, context.temp_allocator)
|
||||||
|
return {false, ProgramError.OdinModuleNotExist, message}
|
||||||
|
}
|
||||||
|
|
||||||
|
walker := os.walker_create_path(odin_module_path)
|
||||||
|
js := strings.builder_make()
|
||||||
|
for file_info in os.walker_walk(&walker) {
|
||||||
|
if file_info.type == .Directory {
|
||||||
|
walker.skip_dir = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ext := os.split_filename(file_info.name); ext != "odin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src, error := os.read_entire_file(file_info.fullpath, context.allocator)
|
||||||
|
if error != nil {
|
||||||
|
return {false, error, concat({"Couldn't read file '", file_info.fullpath, "'"})}
|
||||||
|
}
|
||||||
|
file := ast.File{fullpath = file_info.fullpath, src = string(src)}
|
||||||
|
file.derived = &file
|
||||||
|
p: parser.Parser
|
||||||
|
parser.parse_file(&p, &file)
|
||||||
|
if result := generate_enums_from_ast(&file, &js); !result.ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if error := os.write_entire_file(js_output_file_path, strings.to_string(js)); error != nil {
|
||||||
|
return {false, error, concat({"Failed to write file to '", js_output_file_path, "'"})}
|
||||||
|
}
|
||||||
|
return {true, nil, ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
AstWalkerData :: struct {
|
||||||
|
result: Result,
|
||||||
|
js: ^strings.Builder,
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_pos_to_string :: proc(pos: tokenizer.Pos) -> string {
|
||||||
|
_, filename := os.split_path(pos.file)
|
||||||
|
return fmt.tprint("{'", filename, "', L:", pos.line, ":", pos.column, "}", sep = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_enums_from_ast :: proc(file: ^ast.File, js: ^strings.Builder) -> Result {
|
||||||
|
data := AstWalkerData{
|
||||||
|
result = {true, nil, ""},
|
||||||
|
js = js,
|
||||||
|
}
|
||||||
|
visitor := ast.Visitor {
|
||||||
|
visit = proc(v: ^ast.Visitor, node: ^ast.Node) -> ^ast.Visitor {
|
||||||
|
if node == nil do return v
|
||||||
|
data := cast(^AstWalkerData)v.data
|
||||||
|
#partial switch derived in node.derived {
|
||||||
|
case ^ast.Value_Decl:
|
||||||
|
if !node_value_decl_has_attribute(derived^, ATTRIBUTE_NAME) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if len(derived.values) != 1 {
|
||||||
|
data.result = {false, ProgramError.TooManyDeclarations, ""}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
type, is_enum := derived.values[0].derived.(^ast.Enum_Type);
|
||||||
|
if !is_enum {
|
||||||
|
msg := concat({"'", ATTRIBUTE_NAME, "' attribute can only be used on enums ", ast_pos_to_string(node.pos)})
|
||||||
|
data.result = {false, ProgramError.InvalidAttributeUse, msg}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if result := write_enum_js(type, derived, data.js); !result.ok {
|
||||||
|
data.result = result
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
},
|
||||||
|
data = &data,
|
||||||
|
}
|
||||||
|
ast.walk(&visitor, file)
|
||||||
|
return data.result
|
||||||
|
}
|
||||||
|
|
||||||
|
node_value_decl_has_attribute :: proc(node: ast.Value_Decl, attribute_name: string) -> bool {
|
||||||
|
for attribute in node.attributes {
|
||||||
|
for elem in attribute.elems {
|
||||||
|
if ident, is_ident := elem.derived.(^ast.Ident); is_ident && ident.name == attribute_name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lit_value_to_string :: proc(expr: ^ast.Expr) -> (text: string, ok: bool) {
|
||||||
|
#partial switch e in expr.derived {
|
||||||
|
case ^ast.Basic_Lit:
|
||||||
|
return e.tok.text, true
|
||||||
|
case ^ast.Unary_Expr:
|
||||||
|
inner, inner_ok := lit_value_to_string(e.expr)
|
||||||
|
if !inner_ok do return "", false
|
||||||
|
return fmt.tprint(e.op.text, inner, sep = ""), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
write_enum_js :: proc(type: ^ast.Enum_Type, node: ^ast.Value_Decl, js: ^strings.Builder) -> Result {
|
||||||
|
enum_value := min(i128)
|
||||||
|
fmt.sbprint(js, "const ", node.names[0].derived.(^ast.Ident).name, " = Object.freeze({\n", sep = "")
|
||||||
|
for field in type.fields {
|
||||||
|
#partial switch field in field.derived {
|
||||||
|
case ^ast.Field_Value:
|
||||||
|
ident, is_ident := field.field.derived.(^ast.Ident)
|
||||||
|
if string_value, ok := lit_value_to_string(field.value); ok {
|
||||||
|
if value, ok := strconv.parse_i128(string_value); ok {
|
||||||
|
if value <= enum_value {
|
||||||
|
msg := concat({"Enum values smaller than previous are not supported ", ast_pos_to_string(node.pos)})
|
||||||
|
return {false, ProgramError.EnumValueSmaller, msg}
|
||||||
|
}
|
||||||
|
enum_value = value
|
||||||
|
fmt.sbprint(js, "\t", ident.name, ": ", enum_value, ",\n", sep = "")
|
||||||
|
enum_value += 1
|
||||||
|
} else {
|
||||||
|
msg := concat({"Couldn't parse value of enum member' ", ident.name, "' ", ast_pos_to_string(node.pos)})
|
||||||
|
return {false, ProgramError.CouldntParseEnumValue, msg}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ^ast.Ident:
|
||||||
|
if enum_value == min(i128) {
|
||||||
|
enum_value = 0
|
||||||
|
}
|
||||||
|
fmt.sbprint(js, "\t", field.name, ": ", enum_value, ",\n", sep = "")
|
||||||
|
enum_value += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.sbprint(js, "});\n", sep = "")
|
||||||
|
return {true, nil, ""}
|
||||||
|
}
|
||||||
107
switches.odin
Normal file
107
switches.odin
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
SwitchDefinition :: struct {
|
||||||
|
name: string,
|
||||||
|
fullname: string,
|
||||||
|
shortname: string,
|
||||||
|
usage: string,
|
||||||
|
description: string,
|
||||||
|
no_argument: bool,
|
||||||
|
callback: proc(value: string, data: rawptr) -> (ok: bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchSplitError :: enum {
|
||||||
|
None,
|
||||||
|
Empty,
|
||||||
|
MissingDash,
|
||||||
|
MissingStartQuote,
|
||||||
|
MissingEndQuote,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_split :: proc(str: string) -> (name, value: string, err: SwitchSplitError) {
|
||||||
|
name = ""
|
||||||
|
value = ""
|
||||||
|
err = .None
|
||||||
|
|
||||||
|
if len(str) == 0 {
|
||||||
|
err = .Empty
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if str[0] != '-' {
|
||||||
|
err = .MissingDash
|
||||||
|
return
|
||||||
|
}
|
||||||
|
colon_index := strings.index_rune(str, ':')
|
||||||
|
if colon_index == -1 {
|
||||||
|
name = str[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name = str[1:colon_index]
|
||||||
|
value = str[colon_index + 1:]
|
||||||
|
if value[0] != '"' && value[len(value) - 1] == '"' {
|
||||||
|
err = .MissingStartQuote
|
||||||
|
return
|
||||||
|
} else if value[0] == '"' && value[len(value) - 1] != '"' {
|
||||||
|
err = .MissingEndQuote
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if value[0] == '"' && value[len(value) - 1] == '"' {
|
||||||
|
value = value[1:len(value) - 2]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
process_switches :: proc(args: []string, switch_definitions: []SwitchDefinition, allocator := context.allocator, data: rawptr) -> bool {
|
||||||
|
ok := true
|
||||||
|
error_state: bool
|
||||||
|
switch_map := make(map[string]SwitchDefinition)
|
||||||
|
for switch_definition in switch_definitions {
|
||||||
|
shortname := switch_definition.shortname
|
||||||
|
shortname_length := len(shortname)
|
||||||
|
if shortname_length > 0 {
|
||||||
|
if shortname in switch_map {
|
||||||
|
ok = false
|
||||||
|
existing_switch_definition := switch_map[shortname]
|
||||||
|
fmt.eprintln("Argument error: Switch for ", switch_definition.name, " (", shortname, ") already existed for '", existing_switch_definition.name, sep = "")
|
||||||
|
} else {
|
||||||
|
switch_map[shortname] = switch_definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fullname := switch_definition.fullname
|
||||||
|
fullname_length := len(fullname)
|
||||||
|
if fullname_length > 0 {
|
||||||
|
if fullname in switch_map {
|
||||||
|
ok = false
|
||||||
|
existing_switch_definition := switch_map[fullname]
|
||||||
|
fmt.eprintln("Argument error: Switch for ", switch_definition.name, " (", fullname, ") already existed for '", existing_switch_definition.name, sep = "")
|
||||||
|
} else {
|
||||||
|
switch_map[fullname] = switch_definition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shortname_length == 0 && fullname_length == 0 {
|
||||||
|
ok = false
|
||||||
|
fmt.eprintln("Switch definition error: ", switch_definition.name, " had no shortname or fullname", sep = "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for arg in args {
|
||||||
|
name, value, err := switch_split(arg)
|
||||||
|
if err != .None {
|
||||||
|
ok = false
|
||||||
|
fmt.eprintln("Argument error: ", err, " (", arg, ")", sep = "")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name not_in switch_map {
|
||||||
|
ok = false
|
||||||
|
fmt.eprintln("Argument error: Non-existent switch", name, sep = "")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch_map[name].callback(value, data)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue