aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorphillvancejr <phillipvancejr@gmail.com>2022-02-04 13:15:43 -0500
committerphillvancejr <phillipvancejr@gmail.com>2022-02-04 13:16:40 -0500
commit42364f2fcee1f0f588b006bf2b7e9bc6f88acb93 (patch)
treecb285ae39974d72e2f384922204b27acc67317e7
parent8f600798ef201ce3f34e29d7cdbf07b9bfc5db21 (diff)
sync with main
-rw-r--r--core/bindgen/c-parser-evaluate.odin266
-rw-r--r--core/bindgen/c-parser-helpers.odin267
-rw-r--r--core/bindgen/c-parser-nodes.odin132
-rw-r--r--core/bindgen/c-parser.odin840
-rw-r--r--core/bindgen/errors.odin44
-rw-r--r--core/bindgen/generator-clean.odin284
-rw-r--r--core/bindgen/generator-export.odin166
-rw-r--r--core/bindgen/generator-helpers.odin392
-rw-r--r--core/bindgen/generator.odin205
-rw-r--r--vendor/stb/lib/darwin/libstb_image.abin0 -> 55744 bytes
-rw-r--r--vendor/stb/lib/darwin/stb_image.abin0 -> 97544 bytes
-rw-r--r--vendor/stb/lib/darwin/stb_image_resize.abin0 -> 36824 bytes
-rw-r--r--vendor/stb/lib/darwin/stb_image_write.abin0 -> 32896 bytes
-rw-r--r--vendor/stb/lib/darwin/stb_rect_pack.abin0 -> 5064 bytes
-rw-r--r--vendor/stb/lib/darwin/stb_truetype.abin0 -> 67008 bytes
l---------wasm-ld1
16 files changed, 2597 insertions, 0 deletions
diff --git a/core/bindgen/c-parser-evaluate.odin b/core/bindgen/c-parser-evaluate.odin
new file mode 100644
index 000000000..13cb5042c
--- /dev/null
+++ b/core/bindgen/c-parser-evaluate.odin
@@ -0,0 +1,266 @@
+package bindgen
+
+import "core:fmt"
+import "core:strconv"
+
+// Evaluates an expression to a i64, without checking.
+evaluate_i64 :: proc(data : ^ParserData) -> i64 {
+ ok : bool;
+ value : LiteralValue;
+
+ value, ok = evaluate(data);
+ return value.(i64);
+}
+
+// Evaluate an expression, returns whether it succeeded.
+evaluate :: proc(data : ^ParserData) -> (LiteralValue, bool) {
+ return evaluate_level_5(data);
+}
+
+// @note Evaluate levels numbers are based on
+// https://en.cppreference.com/w/c/language/operator_precedence.
+
+// Bitwise shift level.
+evaluate_level_5 :: proc(data : ^ParserData) -> (value : LiteralValue, ok : bool) {
+ value, ok = evaluate_level_4(data);
+ if !ok do return;
+
+ invalid_value : LiteralValue;
+ token := peek_token(data);
+
+ if token == "<<" {
+ v : LiteralValue;
+ eat_token(data);
+
+ v, ok = evaluate_level_5(data);
+ if is_i64(v) do value = value.(i64) << cast(u64) v.(i64);
+ else do invalid_value = v;
+ } else if token == ">>" {
+ v : LiteralValue;
+ eat_token(data);
+
+ v, ok = evaluate_level_5(data);
+ if is_i64(v) do value = value.(i64) >> cast(u64) v.(i64);
+ else do invalid_value = v;
+ }
+
+ if invalid_value != nil {
+ print_warning("Invalid operand for bitwise shift ", invalid_value);
+ }
+
+ return;
+}
+
+// Additive level.
+evaluate_level_4 :: proc(data : ^ParserData) -> (value : LiteralValue, ok : bool) {
+ value, ok = evaluate_level_3(data);
+ if !ok do return;
+
+ token := peek_token(data);
+ if token == "+" {
+ v : LiteralValue;
+ eat_token(data);
+ v, ok = evaluate_level_4(data);
+ if is_i64(v) do value = value.(i64) + v.(i64);
+ else if is_f64(v) do value = value.(f64) + v.(f64);
+ }
+ else if token == "-" {
+ v : LiteralValue;
+ eat_token(data);
+ v, ok = evaluate_level_4(data);
+ if is_i64(v) do value = value.(i64) - v.(i64);
+ else if is_f64(v) do value = value.(f64) - v.(f64);
+ }
+
+ return;
+}
+
+// Multiplicative level.
+evaluate_level_3 :: proc(data : ^ParserData) -> (value : LiteralValue, ok : bool) {
+ value, ok = evaluate_level_2(data);
+ if !ok do return;
+
+ token := peek_token(data);
+ if token == "*" {
+ v : LiteralValue;
+ eat_token(data);
+ v, ok = evaluate_level_3(data);
+ if is_i64(v) do value = value.(i64) * v.(i64);
+ else if is_f64(v) do value = value.(f64) * v.(f64);
+ }
+ else if token == "/" {
+ v : LiteralValue;
+ eat_token(data);
+ v, ok = evaluate_level_3(data);
+ if is_i64(v) do value = value.(i64) / v.(i64);
+ else if is_f64(v) do value = value.(f64) / v.(f64);
+ }
+
+ return;
+}
+
+// Prefix level.
+evaluate_level_2 :: proc(data : ^ParserData) -> (value : LiteralValue, ok : bool) {
+ token := peek_token(data);
+
+ // Bitwise not
+ if token == "~" {
+ check_and_eat_token(data, "~");
+ value, ok = evaluate_level_2(data);
+ value = ~value.(i64);
+ }
+ else {
+ // @note Should call evaluate_level_1, but we don't have that because we do not dereferenciation.
+ value, ok = evaluate_level_0(data);
+ }
+
+ return;
+}
+
+// Does not try to compose with arithmetics, it just evaluates one single expression.
+evaluate_level_0 :: proc(data : ^ParserData) -> (value : LiteralValue, ok : bool) {
+ ok = true;
+ value = 0;
+ token := peek_token(data);
+
+ // Parentheses
+ if token == "(" {
+ value, ok = evaluate_parentheses(data);
+ } // Number literal
+ else if (token[0] == '-') || (token[0] >= '0' && token[0] <= '9') {
+ value, ok = evaluate_number_literal(data);
+ } // String literal
+ else if token[0] == '"' {
+ value = evaluate_string_literal(data);
+ } // Function-like
+ else if token == "sizeof" {
+ value = evaluate_sizeof(data);
+ } // Knowned literal
+ else if token in data.knownedLiterals {
+ value = evaluate_knowned_literal(data);
+ } // Custom expression
+ else if token in data.options.customExpressionHandlers {
+ value = data.options.customExpressionHandlers[token](data);
+ }
+ else {
+ print_warning("Unknown token ", token, " for expression evaluation.");
+ ok = false;
+ }
+
+ return;
+}
+
+evaluate_sizeof :: proc(data : ^ParserData) -> LiteralValue {
+ print_warning("Using 'sizeof()'. Currently not able to precompute that. Please check generated code.");
+
+ check_and_eat_token(data, "sizeof");
+ check_and_eat_token(data, "(");
+ for data.bytes[data.offset] != ')' {
+ data.offset += 1;
+ }
+ check_and_eat_token(data, ")");
+ return 1;
+}
+
+evaluate_parentheses :: proc(data : ^ParserData) -> (value : LiteralValue, ok : bool) {
+ check_and_eat_token(data, "(");
+
+ // Cast to int (via "(int)" syntax)
+ token := peek_token(data);
+ if token == "int" {
+ check_and_eat_token(data, "int");
+ check_and_eat_token(data, ")");
+ value, ok = evaluate(data);
+ return;
+ } // Cast to enum value (via "(enum XXX)" syntax)
+ else if token == "enum" {
+ check_and_eat_token(data, "enum");
+ eat_token(data);
+ check_and_eat_token(data, ")");
+ value, ok = evaluate(data);
+ return;
+ }
+
+ value, ok = evaluate(data);
+ check_and_eat_token(data, ")");
+ return;
+}
+
+evaluate_number_literal :: proc(data : ^ParserData, loc := #caller_location) -> (value : LiteralValue, ok : bool) {
+ token := parse_any(data);
+
+ // Unary - before numbers
+ numberLitteral := token;
+ for token == "-" {
+ token = parse_any(data);
+ numberLitteral = tcat(numberLitteral, token);
+ }
+ token = numberLitteral;
+
+ // Check if any point or scientific notation in number
+ foundPointOrExp := false;
+ for c in token {
+ if c == '.' || c == 'e' || c == 'E' {
+ foundPointOrExp = true;
+ break;
+ }
+ }
+
+ isHexadecimal := len(token) >= 2 && token[:2] == "0x";
+
+ // Computing postfix
+ tokenLength := len(token);
+ l := tokenLength - 1;
+ for l > 0 {
+ c := token[l];
+ if c >= '0' && c <= '9' { break; }
+ if isHexadecimal && ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { break; }
+ l -= 1;
+ }
+
+ postfix : string;
+ if l != tokenLength - 1 {
+ postfix = token[l+1:];
+ token = token[:l+1];
+ }
+
+ if postfix != "" && (postfix[0] == 'u' || postfix[0] == 'U') {
+ print_warning("Found number litteral '", token, "' with unsigned postfix, we cast it to an int64 internally.");
+ }
+
+ // Floating point
+ if !isHexadecimal && (foundPointOrExp || postfix == "f") {
+ value, ok = strconv.parse_f64(token);
+ } // Integer
+ else {
+ value, ok = strconv.parse_i64(token);
+ }
+
+ if !ok {
+ print_error(data, loc, "Expected number litteral but got '", token, "'.");
+ }
+
+ return value, ok;
+}
+
+evaluate_string_literal :: proc(data : ^ParserData) -> string {
+ token := parse_any(data);
+ return token;
+}
+
+evaluate_knowned_literal :: proc(data : ^ParserData) -> LiteralValue {
+ token := parse_any(data);
+ return data.knownedLiterals[token];
+}
+
+is_i64 :: proc(value : LiteralValue) -> (ok : bool) {
+ v : i64;
+ v, ok = value.(i64);
+ return ok;
+}
+
+is_f64 :: proc(value : LiteralValue) -> (ok : bool) {
+ v : f64;
+ v, ok = value.(f64);
+ return ok;
+}
diff --git a/core/bindgen/c-parser-helpers.odin b/core/bindgen/c-parser-helpers.odin
new file mode 100644
index 000000000..a99d83dd2
--- /dev/null
+++ b/core/bindgen/c-parser-helpers.odin
@@ -0,0 +1,267 @@
+package bindgen
+
+import "core:os"
+import "core:fmt"
+import "core:strings"
+import "core:strconv"
+
+// Extract from start (included) to end (excluded) offsets
+extract_string :: proc(data : ^ParserData, startOffset : u32, endOffset : u32) -> string {
+ return strings.string_from_ptr(&data.bytes[startOffset], cast(int) (endOffset - startOffset));
+}
+
+// Peek the end offset of the next token
+peek_token_end :: proc(data : ^ParserData) -> u32 {
+ offset : u32;
+
+ for true {
+ eat_whitespaces_and_comments(data);
+ if data.offset >= data.bytesLength {
+ return data.bytesLength;
+ }
+ offset = data.offset;
+
+ // Identifier
+ if (data.bytes[offset] >= 'a' && data.bytes[offset] <= 'z') ||
+ (data.bytes[offset] >= 'A' && data.bytes[offset] <= 'Z') ||
+ (data.bytes[offset] == '_') {
+ offset += 1;
+ for (data.bytes[offset] >= 'a' && data.bytes[offset] <= 'z') ||
+ (data.bytes[offset] >= 'A' && data.bytes[offset] <= 'Z') ||
+ (data.bytes[offset] >= '0' && data.bytes[offset] <= '9') ||
+ (data.bytes[offset] == '_') {
+ offset += 1;
+ }
+ }
+ if offset != data.offset {
+ // Nothing to do: we found an identifier
+ } // Number literal
+ else if (data.bytes[offset] >= '0' && data.bytes[offset] <= '9') {
+ offset += 1;
+ // Hexademical literal
+ if data.bytes[offset - 1] == '0' && data.bytes[offset] == 'x' {
+ offset += 1;
+ for (data.bytes[offset] >= '0' && data.bytes[offset] <= '9') ||
+ (data.bytes[offset] >= 'a' && data.bytes[offset] <= 'f') ||
+ (data.bytes[offset] >= 'A' && data.bytes[offset] <= 'F') {
+ offset += 1;
+ }
+ } // Basic number literal
+ else {
+ for (data.bytes[offset] >= '0' && data.bytes[offset] <= '9') ||
+ data.bytes[offset] == '.' {
+ offset += 1;
+ }
+
+ if (data.bytes[offset] == 'e' || data.bytes[offset] == 'E') {
+ offset += 1;
+ if data.bytes[offset] == '-' {
+ offset += 1;
+ }
+ }
+
+ for (data.bytes[offset] >= '0' && data.bytes[offset] <= '9') {
+ offset += 1;
+ }
+ }
+
+ // Number suffix?
+ for (data.bytes[offset] == 'u' || data.bytes[offset] == 'U') ||
+ (data.bytes[offset] == 'l' || data.bytes[offset] == 'L') ||
+ (data.bytes[offset] == 'f') {
+ offset += 1;
+ }
+ } // String literal
+ else if data.bytes[offset] == '"' {
+ offset += 1;
+ for data.bytes[offset-1] == '\\' || data.bytes[offset] != '"' {
+ offset += 1;
+ }
+ offset += 1;
+ } // Possible shifts
+ else if data.bytes[offset] == '<' || data.bytes[offset] == '>' {
+ offset += 1;
+ if data.bytes[offset] == data.bytes[offset-1] {
+ offset += 1;
+ }
+ } // Single character
+ else {
+ offset += 1;
+ }
+
+ token := extract_string(data, data.offset, offset);
+
+ // Ignore __attribute__
+ if token == "__attribute__" {
+ print_warning("__attribute__ is ignored.");
+
+ for data.bytes[offset] != '(' {
+ offset += 1;
+ }
+
+ parenthesesCount := 1;
+ for true {
+ offset += 1;
+ if data.bytes[offset] == '(' do parenthesesCount += 1;
+ else if data.bytes[offset] == ')' do parenthesesCount -= 1;
+ if parenthesesCount == 0 do break;
+ }
+ offset += 1;
+
+ data.offset = offset;
+ } // Ignore certain keywords
+ else if (token == "inline" || token == "__inline" || token == "static"
+ || token == "restrict" || token == "__restrict"
+ || token == "volatile"
+ || token == "__extension__") {
+ data.offset = offset;
+ } // Ignore ignored tokens ;)
+ else {
+ for ignoredToken in data.options.ignoredTokens {
+ if token == ignoredToken {
+ data.offset = offset;
+ break;
+ }
+ }
+ }
+
+ if data.offset != offset {
+ break;
+ }
+ }
+
+ return offset;
+}
+
+// Peek the next token (just eating whitespaces and comment)
+peek_token :: proc(data : ^ParserData) -> string {
+ tokenEnd := peek_token_end(data);
+ if tokenEnd == data.bytesLength {
+ return "EOF";
+ }
+ return extract_string(data, data.offset, tokenEnd);
+}
+
+// Find the end of the define directive (understanding endline backslashes)
+// @note Tricky cases like comments hiding a backslash effect are not handled.
+peek_define_end :: proc(data : ^ParserData) -> u32 {
+ defineEndOffset := data.offset;
+ for data.bytes[defineEndOffset-1] == '\\' || data.bytes[defineEndOffset] != '\n' {
+ defineEndOffset += 1;
+ }
+ return defineEndOffset;
+}
+
+eat_comment :: proc(data : ^ParserData) {
+ if data.offset >= data.bytesLength || data.bytes[data.offset] != '/' {
+ return;
+ }
+
+ // Line comment
+ if data.bytes[data.offset + 1] == '/' {
+ eat_line(data);
+ } // Range comment
+ else if data.bytes[data.offset + 1] == '*' {
+ data.offset += 2;
+ for data.bytes[data.offset] != '*' || data.bytes[data.offset + 1] != '/' {
+ data.offset += 1;
+ }
+ data.offset += 2;
+ }
+}
+
+// Eat whitespaces
+eat_whitespaces :: proc(data : ^ParserData) {
+ // Effective whitespace
+ for data.offset < data.bytesLength &&
+ (data.bytes[data.offset] == ' ' || data.bytes[data.offset] == '\t' ||
+ data.bytes[data.offset] == '\r' || data.bytes[data.offset] == '\n') {
+ if data.bytes[data.offset] == '\n' && data.bytes[data.offset] != '\\' {
+ data.foundFullReturn = true;
+ }
+ data.offset += 1;
+ }
+}
+
+// Removes whitespaces and comments
+eat_whitespaces_and_comments :: proc(data : ^ParserData) {
+ startOffset : u32 = 0xFFFFFFFF;
+ for startOffset != data.offset {
+ startOffset = data.offset;
+ eat_whitespaces(data);
+ eat_comment(data);
+ }
+}
+
+// Eat full line
+eat_line :: proc(data : ^ParserData) {
+ for ; data.bytes[data.offset] != '\n'; data.offset += 1 {
+ }
+}
+
+// Eat a line, and repeat if it ends with a backslash
+eat_define_lines :: proc(data : ^ParserData) {
+ for data.bytes[data.offset-1] == '\\' || data.bytes[data.offset] != '\n' {
+ data.offset += 1;
+ }
+}
+
+// Eat next token
+eat_token :: proc(data : ^ParserData) {
+ data.offset = peek_token_end(data);
+}
+
+// Eat next token
+check_and_eat_token :: proc(data : ^ParserData, expectedToken : string, loc := #caller_location) {
+ token := peek_token(data);
+ if token != expectedToken {
+ print_error(data, loc, "Expected ", expectedToken, " but found ", token, ".");
+ }
+ data.offset += cast(u32) len(token);
+}
+
+// Check whether the next token is outside #define range
+is_define_end :: proc(data : ^ParserData) -> bool {
+ defineEnd := peek_define_end(data);
+ tokenEnd := peek_token_end(data);
+
+ return (defineEnd < tokenEnd);
+}
+
+// Check if the current #define is a macro definition
+is_define_macro :: proc(data : ^ParserData) -> bool {
+ startOffset := data.offset;
+ defer data.offset = startOffset;
+
+ token := parse_any(data);
+ if token != "(" do return false;
+
+ // Find the other parenthesis
+ parenthesesCount := 1;
+ for parenthesesCount != 0 {
+ token = parse_any(data);
+ if token == "(" do parenthesesCount += 1;
+ else if token == ")" do parenthesesCount -= 1;
+ }
+
+ // Its a macro if after the parentheses, it's not the end
+ return !is_define_end(data);
+}
+
+// @note Very slow function to get line number,
+// use only for errors.
+// @todo Well, this does not seem to work properly, UTF-8 problem?
+get_line_column :: proc(data : ^ParserData) -> (u32, u32) {
+ line : u32 = 1;
+ column : u32 = 0;
+ for i : u32 = 0; i < data.offset; i += 1 {
+ if data.bytes[i] == '\n' {
+ column = 0;
+ line += 1;
+ }
+ else {
+ column += 1;
+ }
+ }
+ return line, column;
+}
diff --git a/core/bindgen/c-parser-nodes.odin b/core/bindgen/c-parser-nodes.odin
new file mode 100644
index 000000000..0620e0187
--- /dev/null
+++ b/core/bindgen/c-parser-nodes.odin
@@ -0,0 +1,132 @@
+package bindgen
+
+DefineNode :: struct {
+ name : string,
+ value : LiteralValue,
+}
+
+StructDefinitionNode :: struct {
+ name : string,
+ members : [dynamic]StructOrUnionMember,
+ forwardDeclared : bool,
+}
+
+UnionDefinitionNode :: struct {
+ name : string,
+ members : [dynamic]StructOrUnionMember,
+}
+
+EnumDefinitionNode :: struct {
+ name : string,
+ members : [dynamic]EnumMember,
+}
+
+FunctionDeclarationNode :: struct {
+ name : string,
+ returnType : Type,
+ parameters : [dynamic]FunctionParameter,
+}
+
+TypedefNode :: struct {
+ name : string,
+ type : Type,
+}
+
+Nodes :: struct {
+ defines : [dynamic]DefineNode,
+ enumDefinitions : [dynamic]EnumDefinitionNode,
+ unionDefinitions : [dynamic]UnionDefinitionNode,
+ structDefinitions : [dynamic]StructDefinitionNode,
+ functionDeclarations : [dynamic]FunctionDeclarationNode,
+ typedefs : [dynamic]TypedefNode,
+}
+
+LiteralValue :: union {
+ i64,
+ f64,
+ string,
+}
+
+// Type, might be an array
+Type :: struct {
+ base : BaseType,
+ dimensions : [dynamic]u64, // Array dimensions
+}
+
+BaseType :: union {
+ BuiltinType,
+ PointerType,
+ IdentifierType,
+ FunctionType,
+ FunctionPointerType,
+}
+
+BuiltinType :: enum {
+ Unknown,
+ Void,
+ Int,
+ UInt,
+ LongInt,
+ ULongInt,
+ LongLongInt,
+ ULongLongInt,
+ ShortInt,
+ UShortInt,
+ Char,
+ SChar,
+ UChar,
+ Float,
+ Double,
+ LongDouble,
+
+ // Not defined by C language but in <stdint.h>
+ Int8,
+ Int16,
+ Int32,
+ Int64,
+ UInt8,
+ UInt16,
+ UInt32,
+ UInt64,
+ Size,
+ SSize,
+ PtrDiff,
+ UIntPtr,
+ IntPtr,
+}
+
+PointerType :: struct {
+ type : ^Type, // Pointer is there to prevent definition cycle. Null means void.
+}
+
+IdentifierType :: struct {
+ name : string,
+ anonymous : bool, // An anonymous identifier can be hard-given a name in some contexts.
+}
+
+FunctionType :: struct {
+ returnType : ^Type, // Pointer is there to prevent definition cycle. Null means void.
+ parameters : [dynamic]FunctionParameter,
+}
+
+FunctionPointerType :: struct {
+ name : string,
+ returnType : ^Type, // Pointer is there to prevent definition cycle. Null means void.
+ parameters : [dynamic]FunctionParameter,
+}
+
+EnumMember :: struct {
+ name : string,
+ value : i64,
+ hasValue : bool,
+}
+
+StructOrUnionMember :: struct {
+ name : string,
+ type : Type,
+}
+
+FunctionParameter :: struct {
+ name : string,
+ type : Type,
+}
diff --git a/core/bindgen/c-parser.odin b/core/bindgen/c-parser.odin
new file mode 100644
index 000000000..c3ef4937f
--- /dev/null
+++ b/core/bindgen/c-parser.odin
@@ -0,0 +1,840 @@
+package bindgen
+
+import "core:os"
+import "core:fmt"
+import "core:strings"
+import "core:strconv"
+
+// Global counters
+anonymousStructCount := 0;
+anonymousUnionCount := 0;
+anonymousEnumCount := 0;
+
+knownTypeAliases : map[string]Type;
+
+CustomHandler :: proc(data : ^ParserData);
+CustomExpressionHandler :: proc(data : ^ParserData) -> LiteralValue;
+
+ParserOptions :: struct {
+ ignoredTokens : []string,
+
+ // Handlers
+ customHandlers : map[string]CustomHandler,
+ customExpressionHandlers : map[string]CustomExpressionHandler,
+}
+
+ParserData :: struct {
+ bytes : []u8,
+ bytesLength : u32,
+ offset : u32,
+
+ // References
+ nodes : Nodes,
+ options : ^ParserOptions,
+
+ // Knowned values
+ knownedLiterals : map[string]LiteralValue,
+
+ // Whether we have eaten a '\n' character that has no backslash just before
+ foundFullReturn : bool,
+}
+
+is_identifier :: proc(token : string) -> bool {
+ return (token[0] >= 'a' && token[0] <= 'z') ||
+ (token[0] >= 'A' && token[0] <= 'Z') ||
+ (token[0] == '_');
+}
+
+parse :: proc(bytes : []u8, options : ParserOptions, loc := #caller_location) -> Nodes {
+ options := options;
+
+ data : ParserData;
+ data.bytes = bytes;
+ data.bytesLength = cast(u32) len(bytes);
+ data.options = &options;
+
+ for data.offset = 0; data.offset < data.bytesLength; {
+ token := peek_token(&data);
+ if data.offset == data.bytesLength do break;
+
+ if token in options.customHandlers {
+ options.customHandlers[token](&data);
+ }
+ else if token == "{" || token == "}" || token == ";" {
+ eat_token(&data);
+ }
+ else if token == "extern" {
+ check_and_eat_token(&data, "extern");
+ }
+ else if token == "\"C\"" {
+ check_and_eat_token(&data, "\"C\"");
+ }
+ else if token == "#" {
+ parse_directive(&data);
+ }
+ else if token == "typedef" {
+ parse_typedef(&data);
+ }
+ else if is_identifier(token) {
+ parse_variable_or_function_declaration(&data);
+ }
+ else {
+ print_error(&data, loc, "Unexpected token: ", token, ".");
+ return data.nodes;
+ }
+ }
+
+ return data.nodes;
+}
+
+parse_any :: proc(data : ^ParserData) -> string {
+ offset := peek_token_end(data);
+ identifier := extract_string(data, data.offset, offset);
+ data.offset = offset;
+ return identifier;
+}
+
+parse_identifier :: proc(data : ^ParserData, loc := #caller_location) -> string {
+ identifier := parse_any(data);
+
+ if (identifier[0] < 'a' || identifier[0] > 'z') &&
+ (identifier[0] < 'A' || identifier[0] > 'Z') &&
+ (identifier[0] != '_') {
+ print_error(data, loc, "Expected identifier but found ", identifier, ".");
+ }
+
+ return identifier;
+}
+
+parse_type_dimensions :: proc(data : ^ParserData, type : ^Type) {
+ token := peek_token(data);
+ for token == "[" {
+ eat_token(data);
+ token = peek_token(data);
+ if token == "]" {
+ pointerType : PointerType;
+ pointerType.type = new(Type);
+ pointerType.type^ = type^; // Copy
+ type.base = pointerType;
+ delete(type.dimensions);
+ } else {
+ dimension := evaluate_i64(data);
+ append(&type.dimensions, cast(u64) dimension);
+ }
+ check_and_eat_token(data, "]");
+ token = peek_token(data);
+ }
+}
+
+// This will parse anything that look like a type:
+// Builtin: char/int/float/...
+// Struct-like: struct A/struct { ... }/enum E
+// Function pointer: void (*f)(...)
+//
+// Definition permitted: If a struct-like definition is found, it will generate
+// the according Node and return a corresponding type.
+parse_type :: proc(data : ^ParserData, definitionPermitted := false) -> Type {
+ type : Type;
+
+ // Eat qualifiers
+ token := peek_token(data);
+ if token == "const" {
+ eat_token(data);
+ token = peek_token(data);
+ }
+
+ // Parse main type
+ if token == "struct" {
+ type.base = parse_struct_type(data, definitionPermitted);
+ }
+ else if token == "union" {
+ type.base = parse_union_type(data);
+ }
+ else if token == "enum" {
+ type.base = parse_enum_type(data);
+ }
+ else {
+ // Test builtin type
+ type.base = parse_builtin_type(data);
+ if type.base.(BuiltinType) == BuiltinType.Unknown {
+ // Basic identifier type
+ identifierType : IdentifierType;
+ identifierType.name = parse_identifier(data);
+ type.base = identifierType;
+ }
+ }
+
+ // Eat qualifiers
+ token = peek_token(data);
+ if token == "const" {
+ eat_token(data);
+ token = peek_token(data);
+ }
+
+ // Check if pointer
+ for token == "*" {
+ check_and_eat_token(data, "*");
+ token = peek_token(data);
+
+ pointerType : PointerType;
+ pointerType.type = new(Type);
+ pointerType.type^ = type; // Copy
+
+ type.base = pointerType;
+
+ // Eat qualifiers
+ if token == "const" {
+ eat_token(data);
+ token = peek_token(data);
+ }
+ }
+
+ // Parse array dimensions if any.
+ parse_type_dimensions(data, &type);
+
+ // ----- Function pointer type
+
+ if token == "(" {
+ check_and_eat_token(data, "(");
+ check_and_eat_token(data, "*");
+
+ functionPointerType : FunctionPointerType;
+ functionPointerType.returnType = new(Type);
+ functionPointerType.returnType^ = type;
+ functionPointerType.name = parse_identifier(data);
+
+ check_and_eat_token(data, ")");
+ parse_function_parameters(data, &functionPointerType.parameters);
+
+ type.base = functionPointerType;
+ }
+
+ return type;
+}
+
+parse_builtin_type :: proc(data : ^ParserData) -> BuiltinType {
+ previousBuiltinType := BuiltinType.Unknown;
+ intFound := false;
+ shortFound := false;
+ signedFound := false;
+ unsignedFound := false;
+ longCount := 0;
+
+ for true {
+ token := peek_token(data);
+
+ // Attribute
+ attributeFound := true;
+ if token == "long" do longCount += 1;
+ else if token == "short" do shortFound = true;
+ else if token == "unsigned" do unsignedFound = true;
+ else if token == "signed" do signedFound = true;
+ else do attributeFound = false;
+ if attributeFound { eat_token(data); continue; }
+
+ // Known type alias
+ if token in knownTypeAliases {
+ builtinType, ok := knownTypeAliases[token].base.(BuiltinType);
+ if ok {
+ eat_token(data);
+ previousBuiltinType = builtinType;
+ }
+ break;
+ }
+
+ // Classic type and standard types
+ if token == "void" { eat_token(data); return BuiltinType.Void; }
+ else if token == "int" {
+ eat_token(data);
+ intFound = true;
+ }
+ else if token == "float" { eat_token(data); return BuiltinType.Float; }
+ else if token == "double" {
+ eat_token(data);
+ if longCount == 0 do return BuiltinType.Double;
+ else do return BuiltinType.LongDouble;
+ }
+ else if token == "char" {
+ eat_token(data);
+ if signedFound do return BuiltinType.SChar;
+ else if unsignedFound do return BuiltinType.UChar;
+ else do return BuiltinType.Char;
+ }
+ else if token == "__int8" {
+ // @note :MicrosoftDumminess __intX are Microsoft's fixed-size integers
+ // https://docs.microsoft.com/fr-fr/cpp/cpp/int8-int16-int32-int64
+ // and for unsigned version, they prefixed it with "unsigned"...
+ eat_token(data);
+ if unsignedFound do return BuiltinType.UInt8;
+ else do return BuiltinType.Int8;
+ }
+ else if token == "__int16" {
+ eat_token(data);
+ if unsignedFound do return BuiltinType.UInt16;
+ else do return BuiltinType.Int16;
+ }
+ else if token == "__int32" {
+ eat_token(data);
+ if unsignedFound do return BuiltinType.UInt32;
+ else do return BuiltinType.Int32;
+ }
+ else if token == "__int64" {
+ eat_token(data);
+ if unsignedFound do return BuiltinType.UInt64;
+ else do return BuiltinType.Int64;
+ }
+ else if token == "int8_t" { eat_token(data); return BuiltinType.Int8; }
+ else if token == "int16_t" { eat_token(data); return BuiltinType.Int16; }
+ else if token == "int32_t" { eat_token(data); return BuiltinType.Int32; }
+ else if token == "int64_t" { eat_token(data); return BuiltinType.Int64; }
+ else if token == "uint8_t" { eat_token(data); return BuiltinType.UInt8; }
+ else if token == "uint16_t" { eat_token(data); return BuiltinType.UInt16; }
+ else if token == "uint32_t" { eat_token(data); return BuiltinType.UInt32; }
+ else if token == "uint64_t" { eat_token(data); return BuiltinType.UInt64; }
+ else if token == "size_t" { eat_token(data); return BuiltinType.Size; }
+ else if token == "ssize_t" { eat_token(data); return BuiltinType.SSize; }
+ else if token == "ptrdiff_t" { eat_token(data); return BuiltinType.PtrDiff; }
+ else if token == "uintptr_t" { eat_token(data); return BuiltinType.UIntPtr; }
+ else if token == "intptr_t" { eat_token(data); return BuiltinType.IntPtr; }
+
+ break;
+ }
+
+ // Adapt previous builtin type
+ if previousBuiltinType == BuiltinType.ShortInt {
+ shortFound = true;
+ }
+ else if previousBuiltinType == BuiltinType.Int {
+ intFound = true;
+ }
+ else if previousBuiltinType == BuiltinType.LongInt {
+ longCount += 1;
+ }
+ else if previousBuiltinType == BuiltinType.LongLongInt {
+ longCount += 2;
+ }
+ else if previousBuiltinType == BuiltinType.UShortInt {
+ unsignedFound = true;
+ shortFound = true;
+ }
+ else if previousBuiltinType == BuiltinType.UInt {
+ unsignedFound = true;
+ }
+ else if previousBuiltinType == BuiltinType.ULongInt {
+ unsignedFound = true;
+ longCount += 1;
+ }
+ else if previousBuiltinType == BuiltinType.ULongLongInt {
+ unsignedFound = true;
+ longCount += 2;
+ }
+ else if (previousBuiltinType != BuiltinType.Unknown) {
+ return previousBuiltinType; // float, void, etc.
+ }
+
+ // Implicit and explicit int
+ if intFound || shortFound || unsignedFound || signedFound || longCount > 0 {
+ if unsignedFound {
+ if shortFound do return BuiltinType.UShortInt;
+ if longCount == 0 do return BuiltinType.UInt;
+ if longCount == 1 do return BuiltinType.ULongInt;
+ if longCount == 2 do return BuiltinType.ULongLongInt;
+ } else {
+ if shortFound do return BuiltinType.ShortInt;
+ if longCount == 0 do return BuiltinType.Int;
+ if longCount == 1 do return BuiltinType.LongInt;
+ if longCount == 2 do return BuiltinType.LongLongInt;
+ }
+ }
+
+ return BuiltinType.Unknown;
+}
+
+parse_struct_type :: proc(data : ^ParserData, definitionPermitted : bool) -> IdentifierType {
+ check_and_eat_token(data, "struct");
+
+ type : IdentifierType;
+ token := peek_token(data);
+
+ if !definitionPermitted || token != "{" {
+ type.name = parse_identifier(data);
+ token = peek_token(data);
+ } else {
+ type.name = tcat("AnonymousStruct", anonymousStructCount);
+ type.anonymous = true;
+ anonymousStructCount += 1;
+ }
+
+ if token == "{" {
+ node := parse_struct_definition(data);
+ node.name = type.name;
+ } else if definitionPermitted {
+ // @note Whatever happens, we create a definition of the struct,
+ // as it might be used to forward declare it and then use it only with a pointer.
+ // This for instance the pattern for xcb_connection_t which definition
+ // is never known from user API.
+ node : StructDefinitionNode;
+ node.forwardDeclared = false;
+ node.name = type.name;
+ append(&data.nodes.structDefinitions, node);
+ }
+
+ return type;
+}
+
+parse_union_type :: proc(data : ^ParserData) -> IdentifierType {
+ check_and_eat_token(data, "union");
+
+ type : IdentifierType;
+ token := peek_token(data);
+
+ if token != "{" {
+ type.name = parse_identifier(data);
+ token = peek_token(data);
+ } else {
+ type.name = tcat("AnonymousUnion", anonymousUnionCount);
+ type.anonymous = true;
+ anonymousUnionCount += 1;
+ }
+
+ if token == "{" {
+ node := parse_union_definition(data);
+ node.name = type.name;
+ }
+
+ return type;
+}
+
+parse_enum_type :: proc(data : ^ParserData) -> IdentifierType {
+ check_and_eat_token(data, "enum");
+
+ type : IdentifierType;
+ token := peek_token(data);
+
+ if token != "{" {
+ type.name = parse_identifier(data);
+ token = peek_token(data);
+ } else {
+ type.name = tcat("AnonymousEnum", anonymousEnumCount);
+ type.anonymous = true;
+ anonymousEnumCount += 1;
+ }
+
+ if token == "{" {
+ node := parse_enum_definition(data);
+ node.name = type.name;
+ }
+
+ return type;
+}
+
+/**
+ * We only care about defines of some value
+ */
+parse_directive :: proc(data : ^ParserData) {
+ check_and_eat_token(data, "#");
+
+ token := peek_token(data);
+ if token == "define" {
+ parse_define(data);
+ } // We ignore all other directives
+ else {
+ eat_line(data);
+ }
+}
+
+parse_define :: proc(data : ^ParserData) {
+ check_and_eat_token(data, "define");
+ data.foundFullReturn = false;
+
+ node : DefineNode;
+ node.name = parse_identifier(data);
+
+ // Does it look like end? It might be a #define with no expression
+ if is_define_end(data) {
+ node.value = 1;
+ append(&data.nodes.defines, node);
+ data.knownedLiterals[node.name] = node.value;
+ } // Macros are ignored
+ else if is_define_macro(data) {
+ print_warning("Ignoring define macro for ", node.name, ".");
+ }
+ else {
+ literalValue, ok := evaluate(data);
+ if ok {
+ node.value = literalValue;
+ append(&data.nodes.defines, node);
+ data.knownedLiterals[node.name] = node.value;
+ }
+ else {
+ print_warning("Ignoring define expression for ", node.name, ".");
+ }
+ }
+
+ // Evaluating the expression, we might have already eaten a full return,
+ // if so, do nothing.
+ if !data.foundFullReturn {
+ eat_define_lines(data);
+ }
+}
+
+// @fixme Move
+change_anonymous_node_name :: proc (data : ^ParserData, oldName : string, newName : string) -> bool {
+ for i := 0; i < len(data.nodes.structDefinitions); i += 1 {
+ if data.nodes.structDefinitions[i].name == oldName {
+ data.nodes.structDefinitions[i].name = newName;
+ return true;
+ }
+ }
+
+ for i := 0; i < len(data.nodes.enumDefinitions); i += 1 {
+ if data.nodes.enumDefinitions[i].name == oldName {
+ data.nodes.enumDefinitions[i].name = newName;
+ return true;
+ }
+ }
+
+ for i := 0; i < len(data.nodes.unionDefinitions); i += 1 {
+ if data.nodes.unionDefinitions[i].name == oldName {
+ data.nodes.unionDefinitions[i].name = newName;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Type aliasing.
+ * typedef <sourceType> <name>;
+ */
+parse_typedef :: proc(data : ^ParserData) {
+ check_and_eat_token(data, "typedef");
+
+ // @note Struct-like definitions (and such)
+ // are generated within type parsing.
+ //
+ // So that typedef struct { int foo; }* Ap; is valid.
+
+ // Parsing type
+ node : TypedefNode;
+ node.type = parse_type(data, true);
+
+ if sourceType, ok := node.type.base.(FunctionPointerType); ok {
+ node.name = sourceType.name;
+ } else {
+ node.name = parse_identifier(data);
+ }
+
+ // Checking if function type
+ token := peek_token(data);
+ if token == "(" {
+ functionType : FunctionType;
+ functionType.returnType = new(Type);
+ functionType.returnType^ = node.type;
+
+ parse_function_parameters(data, &functionType.parameters);
+
+ node.type.base = functionType;
+ }
+
+ // Checking if array
+ parse_type_dimensions(data, &node.type);
+
+ // If the underlying type is anonymous,
+ // we just affect it the name.
+ addTypedefNode := true;
+ if identifierType, ok := node.type.base.(IdentifierType); ok {
+ if identifierType.anonymous {
+ addTypedefNode = !change_anonymous_node_name(data, identifierType.name, node.name);
+ }
+ }
+
+ if addTypedefNode {
+ knownTypeAliases[node.name] = node.type;
+ append(&data.nodes.typedefs, node);
+ }
+
+ check_and_eat_token(data, ";");
+
+ // @note Commented tool for debug
+ // fmt.println("Typedef: ", node.type, node.name);
+}
+
+parse_struct_definition :: proc(data : ^ParserData) -> ^StructDefinitionNode {
+ node : StructDefinitionNode;
+ node.forwardDeclared = false;
+ parse_struct_or_union_members(data, &node.members);
+
+ append(&data.nodes.structDefinitions, node);
+ return &data.nodes.structDefinitions[len(data.nodes.structDefinitions) - 1];
+}
+
+parse_union_definition :: proc(data : ^ParserData) -> ^UnionDefinitionNode {
+ node : UnionDefinitionNode;
+ parse_struct_or_union_members(data, &node.members);
+
+ append(&data.nodes.unionDefinitions, node);
+ return &data.nodes.unionDefinitions[len(data.nodes.unionDefinitions) - 1];
+}
+
+parse_enum_definition :: proc(data : ^ParserData) -> ^EnumDefinitionNode {
+ node : EnumDefinitionNode;
+ parse_enum_members(data, &node.members);
+
+ append(&data.nodes.enumDefinitions, node);
+ return &data.nodes.enumDefinitions[len(data.nodes.enumDefinitions) - 1];
+}
+
+/**
+ * {
+ * <name> = <value>,
+ * <name>,
+ * }
+ */
+parse_enum_members :: proc(data : ^ParserData, members : ^[dynamic]EnumMember) {
+ check_and_eat_token(data, "{");
+
+ nextMemberValue : i64 = 0;
+ token := peek_token(data);
+ for token != "}" {
+ member : EnumMember;
+ member.name = parse_identifier(data);
+ member.hasValue = false;
+
+ token = peek_token(data);
+ if token == "=" {
+ check_and_eat_token(data, "=");
+
+ member.hasValue = true;
+ member.value = evaluate_i64(data);
+ nextMemberValue = member.value;
+ token = peek_token(data);
+ } else {
+ member.value = nextMemberValue;
+ }
+
+ data.knownedLiterals[member.name] = member.value;
+ nextMemberValue += 1;
+
+ // Eat until end, as this might be a complex expression that we couldn't understand
+ if token != "," && token != "}" {
+ print_warning("Parser cannot understand fully the expression of enum member ", member.name, ".");
+ for token != "," && token != "}" {
+ eat_token(data);
+ token = peek_token(data);
+ }
+ }
+ if token == "," {
+ check_and_eat_token(data, ",");
+ token = peek_token(data);
+ }
+
+ append(members, member);
+ }
+
+ check_and_eat_token(data, "}");
+}
+
+/**
+ * {
+ * <type> <name>;
+ * <type> <name1>, <name2>;
+ * <type> <name>[<dimension>];
+ * }
+ */
+parse_struct_or_union_members :: proc(data : ^ParserData, structOrUnionMembers : ^[dynamic]StructOrUnionMember) {
+ check_and_eat_token(data, "{");
+
+ // To ensure unique id
+ unamedCount := 0;
+
+ token := peek_token(data);
+ for token != "}" {
+ member : StructOrUnionMember;
+ member.type = parse_type(data, true);
+
+ for true {
+ // In the case of function pointer types, the name has been parsed
+ // during type inspection.
+ if type, ok := member.type.base.(FunctionPointerType); ok {
+ member.name = type.name;
+ }
+ else {
+ // Unamed (struct or union)
+ token = peek_token(data);
+ if !is_identifier(token) {
+ member.name = tcat("unamed", unamedCount);
+ unamedCount += 1;
+ }
+ else {
+ member.name = parse_identifier(data);
+ }
+ }
+
+ parse_type_dimensions(data, &member.type);
+
+ token = peek_token(data);
+ if token == ":" {
+ check_and_eat_token(data, ":");
+ print_warning("Found bitfield in struct, which is not handled correctly.");
+ evaluate_i64(data);
+ token = peek_token(data);
+ }
+
+ append(structOrUnionMembers, member);
+
+ // Multiple declarations on one line
+ if token == "," {
+ check_and_eat_token(data, ",");
+ continue;
+ }
+
+ break;
+ }
+
+ check_and_eat_token(data, ";");
+ token = peek_token(data);
+ }
+
+ check_and_eat_token(data, "}");
+}
+
+parse_variable_or_function_declaration :: proc(data : ^ParserData) {
+ type := parse_type(data, true);
+
+ // If it's just a type, it might be a struct definition
+ token := peek_token(data);
+ if token == ";" {
+ check_and_eat_token(data, ";");
+ return;
+ }
+
+ // Eat array declaration if any
+ // @fixme The return type of a function declaration will be wrong!
+ for data.bytes[data.offset] == '[' {
+ for data.bytes[data.offset] != ']' {
+ data.offset += 1;
+ }
+ data.offset += 1;
+ }
+
+ name := parse_identifier(data);
+
+ token = peek_token(data);
+ if token == "(" {
+ functionDeclarationNode := parse_function_declaration(data);
+ functionDeclarationNode.returnType = type;
+ functionDeclarationNode.name = name;
+ return;
+ } else if token == "[" {
+ // Eat whole array declaration
+ for data.bytes[data.offset] == '[' {
+ for data.bytes[data.offset] != ']' {
+ data.offset += 1;
+ }
+ data.offset += 1;
+ }
+ }
+
+ // Global variable declaration (with possible multiple declarations)
+ token = peek_token(data);
+
+ for true {
+ if token == "," {
+ print_warning("Found global variable declaration '", name, "', we won't generated any binding for it.");
+ check_and_eat_token(data, ",");
+
+ name = parse_identifier(data);
+ token = peek_token(data);
+ continue;
+ }
+ else if token == ";" {
+ if name != "" {
+ print_warning("Found global variable declaration '", name, "', we won't generated any binding for it.");
+ }
+ check_and_eat_token(data, ";");
+ break;
+ }
+
+ // Global variable assignment, considered as constant define.
+ node : DefineNode;
+
+ check_and_eat_token(data, "=");
+ literalValue, ok := evaluate(data);
+ if ok {
+ node.name = name;
+ node.value = literalValue;
+ append(&data.nodes.defines, node);
+ }
+ else {
+ print_warning("Ignoring global variable expression for '", name, "'.");
+ }
+
+ name = "";
+ token = peek_token(data);
+ }
+}
+
+parse_function_declaration :: proc(data : ^ParserData) -> ^FunctionDeclarationNode {
+ node : FunctionDeclarationNode;
+
+ parse_function_parameters(data, &node.parameters);
+
+ // Function definition? Ignore it.
+ token := peek_token(data);
+ if token == "{" {
+ bracesCount := 1;
+ for true {
+ data.offset += 1;
+ if data.bytes[data.offset] == '{' do bracesCount += 1;
+ else if data.bytes[data.offset] == '}' do bracesCount -= 1;
+ if bracesCount == 0 do break;
+ }
+ data.offset += 1;
+ } // Function declaration
+ else {
+ check_and_eat_token(data, ";");
+ }
+
+ append(&data.nodes.functionDeclarations, node);
+ return &data.nodes.functionDeclarations[len(data.nodes.functionDeclarations) - 1];
+}
+
+parse_function_parameters :: proc(data : ^ParserData, parameters : ^[dynamic]FunctionParameter) {
+ check_and_eat_token(data, "(");
+
+ token := peek_token(data);
+ for token != ")" {
+ parameter : FunctionParameter;
+
+ token = peek_token(data);
+ if token == "." {
+ print_warning("A function accepts variadic arguments, this is currently not handled within generated code.");
+
+ check_and_eat_token(data, ".");
+ check_and_eat_token(data, ".");
+ check_and_eat_token(data, ".");
+ break;
+ } else {
+ parameter.type = parse_type(data);
+ }
+
+ // Check if named parameter
+ token = peek_token(data);
+ if token != ")" && token != "," {
+ parameter.name = parse_identifier(data);
+ parse_type_dimensions(data, &parameter.type);
+ token = peek_token(data);
+ }
+
+ if token == "," {
+ eat_token(data);
+ token = peek_token(data);
+ }
+
+ append(parameters, parameter);
+ }
+
+ check_and_eat_token(data, ")");
+}
diff --git a/core/bindgen/errors.odin b/core/bindgen/errors.odin
new file mode 100644
index 000000000..9564c5244
--- /dev/null
+++ b/core/bindgen/errors.odin
@@ -0,0 +1,44 @@
+package bindgen
+
+import "core:fmt"
+import "core:os"
+
+seenWarnings : map[string]bool;
+
+print_warning :: proc(args : ..any) {
+ message := tcat(..args);
+
+ if !seenWarnings[message] {
+ fmt.eprint("[bindgen] Warning: ", message, "\n");
+ seenWarnings[message] = true;
+ }
+}
+
+print_error :: proc(data : ^ParserData, loc := #caller_location, args : ..any) {
+ message := tcat(..args);
+
+ min : u32 = 0;
+ for i := data.offset - 1; i > 0; i -= 1 {
+ if data.bytes[i] == '\n' {
+ min = i + 1;
+ break;
+ }
+ }
+
+ max := min + 200;
+ for i := min + 1; i < max; i += 1 {
+ if data.bytes[i] == '\n' {
+ max = i;
+ break;
+ }
+ }
+
+ line, _ := get_line_column(data);
+
+ fmt.eprint("[bindgen] Error: ", message, "\n");
+ fmt.eprint("[bindgen] ... from ", loc.procedure, "\n");
+ fmt.eprint("[bindgen] ... at line ", line, " within this context:\n");
+ fmt.eprint("> ", extract_string(data, min, max), "\n");
+
+ os.exit(1);
+}
diff --git a/core/bindgen/generator-clean.odin b/core/bindgen/generator-clean.odin
new file mode 100644
index 000000000..8dd837b10
--- /dev/null
+++ b/core/bindgen/generator-clean.odin
@@ -0,0 +1,284 @@
+package bindgen
+
+import "core:fmt"
+
+// Prevent keywords clashes and other tricky cases
+clean_identifier :: proc(name : string) -> string {
+ name := name;
+
+ if name == "" {
+ return name;
+ }
+
+ // Starting with _? Try removing that.
+ for true {
+ if name[0] == '_' {
+ name = name[1:];
+ }
+ else {
+ break;
+ }
+ }
+
+ // Number
+ if name[0] >= '0' && name[0] <= '9' {
+ return tcat("_", name);
+ } // Keywords clash
+ else if name == "map" || name == "proc" || name == "opaque" || name == "in" {
+ return tcat("_", name);
+ } // Jai keywords clash
+ else if name == "context" ||
+ name == "float32" || name == "float64" ||
+ name == "s8" || name == "s16" || name == "s32" || name == "s64" ||
+ name == "u8" || name == "u16" || name == "u32" || name == "u64" {
+ return tcat("_", name);
+ }
+
+ return name;
+}
+
+clean_variable_name :: proc(name : string, options : ^GeneratorOptions) -> string {
+ name := name;
+ name = change_case(name, options.variableCase);
+ return clean_identifier(name);
+}
+
+clean_pseudo_type_name :: proc(structName : string, options : ^GeneratorOptions) -> string {
+ structName := structName;
+ structName = remove_postfixes(structName, options.pseudoTypePostfixes, options.pseudoTypeTransparentPostfixes);
+ structName = remove_prefixes(structName, options.pseudoTypePrefixes, options.pseudoTypeTransparentPrefixes);
+ structName = change_case(structName, options.pseudoTypeCase);
+ return structName;
+}
+
+// Clean up the enum name so that it can be used to remove the prefix from enum values.
+clean_enum_name_for_prefix_removal :: proc(enumName : string, options : ^GeneratorOptions) -> (string, [dynamic]string) {
+ enumName := enumName;
+
+ if !options.enumValueNameRemove {
+ return enumName, nil;
+ }
+
+ // Remove postfix and use same case convention as the enum values
+ removedPostfixes : [dynamic]string;
+ enumName, removedPostfixes = remove_postfixes_with_removed(enumName, options.enumValueNameRemovePostfixes);
+ enumName = change_case(enumName, options.enumValueCase);
+ return enumName, removedPostfixes;
+}
+
+clean_enum_value_name :: proc(valueName : string, enumName : string, postfixes : []string, options : ^GeneratorOptions) -> string {
+ valueName := valueName;
+
+ valueName = remove_prefixes(valueName, options.enumValuePrefixes, options.enumValueTransparentPrefixes);
+ valueName = remove_postfixes(valueName, postfixes, options.enumValueTransparentPostfixes);
+
+ if options.enumValueNameRemove {
+ valueName = remove_prefixes(valueName, []string{enumName});
+ }
+
+ valueName = change_case(valueName, options.enumValueCase);
+
+ return clean_identifier(valueName);
+}
+
+clean_function_name :: proc(functionName : string, options : ^GeneratorOptions) -> string {
+ functionName := functionName;
+ functionName = remove_prefixes(functionName, options.functionPrefixes, options.functionTransparentPrefixes);
+ functionName = remove_postfixes(functionName, options.definePostfixes, options.defineTransparentPostfixes);
+ functionName = change_case(functionName, options.functionCase);
+ return functionName;
+}
+
+clean_define_name :: proc(defineName : string, options : ^GeneratorOptions) -> string {
+ defineName := defineName;
+ defineName = remove_prefixes(defineName, options.definePrefixes, options.defineTransparentPrefixes);
+ defineName = remove_postfixes(defineName, options.definePostfixes, options.defineTransparentPostfixes);
+ defineName = change_case(defineName, options.defineCase);
+ return defineName;
+}
+
+// Convert to Odin's types
+clean_type :: proc(data : ^GeneratorData, type : Type, baseTab : string = "", explicitSharpType := true) -> string {
+ output := "";
+
+ for dimension in type.dimensions {
+ output = tcat(output, "[", dimension, "]");
+ }
+ output = tcat(output, clean_base_type(data, type.base, baseTab, explicitSharpType));
+
+ return output;
+}
+
+clean_base_type :: proc(data : ^GeneratorData, baseType : BaseType, baseTab : string = "", explicitSharpType := true) -> string {
+ options := data.options;
+
+ if _type, ok := baseType.(BuiltinType); ok {
+ if _type == BuiltinType.Void do return options.mode == "jai" ? "void" : "";
+ else if _type == BuiltinType.Int do return options.mode == "jai" ? "s64" : "_c.int";
+ else if _type == BuiltinType.UInt do return options.mode == "jai" ? "u64" :"_c.uint";
+ else if _type == BuiltinType.LongInt do return options.mode == "jai" ? "s64" :"_c.long";
+ else if _type == BuiltinType.ULongInt do return options.mode == "jai" ? "u64" :"_c.ulong";
+ else if _type == BuiltinType.LongLongInt do return options.mode == "jai" ? "s64" :"_c.longlong";
+ else if _type == BuiltinType.ULongLongInt do return options.mode == "jai" ? "u64" :"_c.ulonglong";
+ else if _type == BuiltinType.ShortInt do return options.mode == "jai" ? "s16" :"_c.short";
+ else if _type == BuiltinType.UShortInt do return options.mode == "jai" ? "u16" :"_c.ushort";
+ else if _type == BuiltinType.Char do return options.mode == "jai" ? "u8" :"_c.char";
+ else if _type == BuiltinType.SChar do return options.mode == "jai" ? "s8" :"_c.schar";
+ else if _type == BuiltinType.UChar do return options.mode == "jai" ? "u8" :"_c.uchar";
+ else if _type == BuiltinType.Float do return options.mode == "jai" ? "float32" :"_c.float";
+ else if _type == BuiltinType.Double do return options.mode == "jai" ? "float64" :"_c.double";
+ else if _type == BuiltinType.LongDouble {
+ print_warning("Found long double which is currently not supported. Fallback to double in generated code.");
+ return options.mode == "jai" ? "double" :"_c.double";
+ }
+ else if _type == BuiltinType.Int8 do return options.mode == "jai" ? "s8" :"i8";
+ else if _type == BuiltinType.Int16 do return options.mode == "jai" ? "s16" :"i16";
+ else if _type == BuiltinType.Int32 do return options.mode == "jai" ? "s32" :"i32";
+ else if _type == BuiltinType.Int64 do return options.mode == "jai" ? "s64" :"i64";
+ else if _type == BuiltinType.UInt8 do return options.mode == "jai" ? "u8" :"u8";
+ else if _type == BuiltinType.UInt16 do return options.mode == "jai" ? "u16" :"u16";
+ else if _type == BuiltinType.UInt32 do return options.mode == "jai" ? "u32" :"u32";
+ else if _type == BuiltinType.UInt64 do return options.mode == "jai" ? "u64" :"u64";
+ else if _type == BuiltinType.Size do return options.mode == "jai" ? "u64" :"_c.size_t";
+ else if _type == BuiltinType.SSize do return options.mode == "jai" ? "u64" :"_c.ssize_t";
+ else if _type == BuiltinType.PtrDiff do return options.mode == "jai" ? "s64" :"_c.ptrdiff_t";
+ else if _type == BuiltinType.UIntPtr do return options.mode == "jai" ? "u64" :"_c.uintptr_t";
+ else if _type == BuiltinType.IntPtr do return options.mode == "jai" ? "s64" :"_c.intptr_t";
+ }
+ else if _type, ok := baseType.(PointerType); ok {
+ if options.mode == "jai" {
+ // Hide pointers to types that were not declared.
+ if !is_known_base_type(data, _type.type.base) {
+ print_warning("*", _type.type.base.(IdentifierType).name, " replaced by *void as the pointed type is unknown.");
+ return "*void";
+ }
+ } else {
+ if __type, ok := _type.type.base.(BuiltinType); ok {
+ if __type == BuiltinType.Void do return "rawptr";
+ else if __type == BuiltinType.Char do return "cstring";
+ }
+ }
+ name := clean_type(data, _type.type^, baseTab);
+ return tcat(options.mode == "jai" ? "*" :"^", name);
+ }
+ else if _type, ok := baseType.(IdentifierType); ok {
+ return clean_pseudo_type_name(_type.name, options);
+ }
+ else if _type, ok := baseType.(FunctionType); ok {
+ output : string;
+ if explicitSharpType {
+ output = "#type ";
+ }
+ output = tcat(output, options.mode == "jai" ? "(" :"proc(");
+ parameters := clean_function_parameters(data, _type.parameters, baseTab);
+ output = tcat(output, parameters, ")");
+
+ returnType := clean_type(data, _type.returnType^);
+ if len(returnType) > 0 && returnType != "void" {
+ output = tcat(output, " -> ", returnType);
+ }
+ return output;
+ }
+ else if _type, ok := baseType.(FunctionPointerType); ok {
+ output : string;
+ if explicitSharpType {
+ output = "#type ";
+ }
+ output = tcat(output, options.mode == "jai" ? "(" :"proc(");
+ parameters := clean_function_parameters(data, _type.parameters, baseTab);
+ output = tcat(output, parameters, ")");
+
+ returnType := clean_type(data, _type.returnType^);
+ if len(returnType) > 0 && returnType != "void" {
+ output = tcat(output, " -> ", returnType);
+ }
+
+ if options.mode == "jai" {
+ output = tcat(output, " #foreign");
+ }
+ return output;
+ }
+
+ return "<niy>";
+}
+
+clean_function_parameters :: proc(data : ^GeneratorData, parameters : [dynamic]FunctionParameter, baseTab : string) -> string {
+ output := "";
+ options := data.options;
+
+ // Special case: function(void) does not really have a parameter
+ if len(parameters) == 1 {
+ if _type, ok := parameters[0].type.base.(BuiltinType); ok {
+ if _type == BuiltinType.Void {
+ return "";
+ }
+ }
+ }
+
+ tab := "";
+ if options.mode == "jai" { // @note :OdinCodingStyle Odin forces a coding style, now. Ugh.
+ if (len(parameters) > 1) {
+ output = tcat(output, "\n");
+ tab = tcat(baseTab, " ");
+ }
+ }
+
+ unamedParametersCount := 0;
+ for parameter, i in parameters {
+ type := clean_type(data, parameter.type);
+
+ name : string;
+ if len(parameter.name) != 0 {
+ name = clean_variable_name(parameter.name, options);
+ } else {
+ name = tcat("unamed", unamedParametersCount);
+ unamedParametersCount += 1;
+ }
+
+ output = tcat(output, tab, name, " : ", type);
+
+ if i != len(parameters) - 1 {
+ if options.mode == "jai" { // @note :OdinCodingStyle
+ output = tcat(output, ",\n");
+ } else {
+ output = tcat(output, ", ");
+ }
+ }
+ }
+
+ if (len(parameters) > 1) {
+ if options.mode == "jai" { // @note :OdinCodingStyle
+ output = tcat(output, "\n", baseTab);
+ }
+ }
+
+ return output;
+}
+
+is_known_base_type :: proc(data : ^GeneratorData, baseType : BaseType) -> bool {
+ if _type, ok := baseType.(IdentifierType); ok {
+ for it in data.nodes.typedefs {
+ if _type.name == it.name {
+ return true;
+ }
+ }
+ for it in data.nodes.structDefinitions {
+ if _type.name == it.name {
+ return true;
+ }
+ }
+ for it in data.nodes.enumDefinitions {
+ if _type.name == it.name {
+ return true;
+ }
+ }
+ for it in data.nodes.unionDefinitions {
+ if _type.name == it.name {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return true;
+}
diff --git a/core/bindgen/generator-export.odin b/core/bindgen/generator-export.odin
new file mode 100644
index 000000000..a04113ed9
--- /dev/null
+++ b/core/bindgen/generator-export.odin
@@ -0,0 +1,166 @@
+package bindgen
+
+import "core:os"
+import "core:fmt"
+
+export_defines :: proc(data : ^GeneratorData) {
+ for node in data.nodes.defines {
+ defineName := clean_define_name(node.name, data.options);
+
+ // @fixme fprint of float numbers are pretty badly handled,
+ // just has a 10^-3 precision.
+ fcat(data.handle, defineName, " :: ", node.value, ";\n");
+ }
+ fcat(data.handle, "\n");
+}
+
+export_typedefs :: proc(data : ^GeneratorData) {
+ for node in data.nodes.typedefs {
+ name := clean_pseudo_type_name(node.name, data.options);
+ type := clean_type(data, node.type, "", true);
+ if name == type do continue;
+ fcat(data.handle, name, " :: ", type, ";\n");
+ }
+ fcat(data.handle, "\n");
+}
+
+export_enums :: proc(data : ^GeneratorData) {
+ for node in data.nodes.enumDefinitions {
+ enumName := clean_pseudo_type_name(node.name, data.options);
+
+ if data.options.mode == "jai" {
+ consideredFlags := false;
+ for postfix in data.options.enumConsideredFlagsPostfixes {
+ if ends_with(node.name, postfix) {
+ consideredFlags = true;
+ break;
+ }
+ }
+
+ if consideredFlags {
+ fcat(data.handle, enumName, " :: enum_flags u32 {");
+ } else {
+ fcat(data.handle, enumName, " :: enum s32 {");
+ }
+ } else {
+ fcat(data.handle, enumName, " :: enum i32 {");
+ }
+
+ postfixes : [dynamic]string;
+ enumName, postfixes = clean_enum_name_for_prefix_removal(enumName, data.options);
+
+ // Changing the case of postfixes to the enum value one,
+ // so that they can be removed.
+ enumValueCase := find_case(node.members[0].name);
+ for postfix, i in postfixes {
+ postfixes[i] = change_case(postfix, enumValueCase);
+ }
+
+ // And changing the case of enumName to the enum value one
+ enumName = change_case(enumName, enumValueCase);
+
+ // Merging enum value postfixes with postfixes that have been removed from the enum name.
+ for postfix in data.options.enumValuePostfixes {
+ append(&postfixes, postfix);
+ }
+
+ export_enum_members(data, node.members, enumName, postfixes[:]);
+ fcat(data.handle, data.options.mode == "jai" ? "}\n" : "};\n");
+ fcat(data.handle, "\n");
+ }
+}
+
+export_structs :: proc(data : ^GeneratorData) {
+ for node in data.nodes.structDefinitions {
+ structName := clean_pseudo_type_name(node.name, data.options);
+ fcat(data.handle, structName, " :: struct {");
+ export_struct_or_union_members(data, node.members);
+ fcat(data.handle, data.options.mode == "jai" ? "}\n" : "};\n");
+ fcat(data.handle, "\n");
+ }
+}
+
+export_unions :: proc(data : ^GeneratorData) {
+ for node in data.nodes.unionDefinitions {
+ unionName := clean_pseudo_type_name(node.name, data.options);
+ fcat(data.handle, unionName, data.options.mode == "jai" ? " :: union {" : " :: struct #raw_union {");
+ export_struct_or_union_members(data, node.members);
+ fcat(data.handle, data.options.mode == "jai" ? "}\n" : "};\n");
+ fcat(data.handle, "\n");
+ }
+}
+
+export_functions :: proc(data : ^GeneratorData) {
+ for node in data.nodes.functionDeclarations {
+ functionName := clean_function_name(node.name, data.options);
+ if data.options.mode == "jai" {
+ fcat(data.handle, functionName, " :: (");
+ } else {
+ fcat(data.handle, " @(link_name=\"", node.name, "\")\n");
+ fcat(data.handle, " ", functionName, " :: proc(");
+ }
+ parameters := clean_function_parameters(data, node.parameters, data.options.mode == "jai" ? "" : " ");
+ fcat(data.handle, parameters, ")");
+ returnType := clean_type(data, node.returnType);
+ if len(returnType) > 0 {
+ fcat(data.handle, " -> ", returnType);
+ }
+ if data.options.mode == "jai" {
+ fcat(data.handle, " #foreign ", data.foreignLibrary, " \"", node.name ,"\";\n");
+ } else {
+ fcat(data.handle, " ---;\n");
+ }
+ fcat(data.handle, "\n");
+ }
+}
+
+export_enum_members :: proc(data : ^GeneratorData, members : [dynamic]EnumMember, enumName : string, postfixes : []string) {
+ if (len(members) > 0) {
+ fcat(data.handle, "\n");
+ }
+
+ cleanedMembers : [dynamic]EnumMember;
+ for member in members {
+ cleanedMember : EnumMember;
+ cleanedMember.hasValue = member.hasValue;
+ cleanedMember.value = member.value;
+ cleanedMember.name = clean_enum_value_name(member.name, enumName, postfixes, data.options);
+
+ if len(cleanedMember.name) == 0 {
+ // print_warning("Enum member ", member.name, " resolves to an empty name. Ignoring it.");
+ continue;
+ }
+
+ // Ensuring that we don't collide with an other enum member.
+ foundCopy := false;
+ for existingCleanedMember in cleanedMembers {
+ if cleanedMember.name == existingCleanedMember.name &&
+ cleanedMember.hasValue == existingCleanedMember.hasValue &&
+ cleanedMember.value == existingCleanedMember.value {
+ print_warning("Enum member ", member.name, " is duplicated once cleaned. Keeping only one copy.");
+ foundCopy = true;
+ break;
+ }
+ }
+ if foundCopy do continue;
+
+ fcat(data.handle, " ", cleanedMember.name);
+ if member.hasValue {
+ fcat(data.handle, data.options.mode == "jai" ? " :: " : " = ", member.value);
+ }
+ fcat(data.handle, data.options.mode == "jai" ? ";\n" : ",\n");
+
+ append(&cleanedMembers, cleanedMember);
+ }
+}
+
+export_struct_or_union_members :: proc(data : ^GeneratorData, members : [dynamic]StructOrUnionMember) {
+ if (len(members) > 0) {
+ fcat(data.handle, "\n");
+ }
+ for member in members {
+ type := clean_type(data, member.type, " ");
+ name := clean_variable_name(member.name, data.options);
+ fcat(data.handle, " ", name, " : ", type, data.options.mode == "jai" ? ";\n" : ",\n");
+ }
+}
diff --git a/core/bindgen/generator-helpers.odin b/core/bindgen/generator-helpers.odin
new file mode 100644
index 000000000..a3b37f4f6
--- /dev/null
+++ b/core/bindgen/generator-helpers.odin
@@ -0,0 +1,392 @@
+package bindgen
+
+import "core:fmt"
+import "core:os"
+import "core:io"
+import "core:strings"
+import "core:unicode/utf8"
+
+Case :: enum {
+ Unknown,
+ Camel,
+ Constant,
+ Kebab,
+ Pascal,
+ Snake,
+}
+
+WordCase :: enum {
+ Unknown,
+ Up,
+ Low,
+ FirstUp,
+ // When first upping, numbers are followed always by a capital
+ FirstUpNumberReset,
+}
+
+// Change a character to a capital.
+to_uppercase :: proc(c : rune) -> rune {
+ c := c;
+ if c >= 'a' && c <= 'z' {
+ c = c - 'a' + 'A';
+ }
+ return c;
+}
+
+// Change a character to lowercase.
+to_lowercase :: proc(c : rune) -> rune {
+ c := c;
+ if c >= 'A' && c <= 'Z' {
+ c = c - 'A' + 'a';
+ }
+ return c;
+}
+
+// @note Stolen tprint and fprint from fmt package, because it was confusing due to args: ..any and sep default parameter.
+tcat :: proc(args: ..any) -> string {
+ return fmt.tprint(args=args, sep="");
+}
+
+fcat :: proc(fd: os.Handle, args: ..any) -> int {
+ return fmt.fprint(fd=fd, args=args, sep="");
+}
+
+// Change the case convention of a word.
+change_word_case :: proc(str : string, targetCase : WordCase) -> string {
+ newStr : string;
+ if targetCase == WordCase.Up {
+ for c in str {
+ newStr = tcat(newStr, to_uppercase(c));
+ }
+ }
+ else if targetCase == WordCase.Low {
+ for c in str {
+ newStr = tcat(newStr, to_lowercase(c));
+ }
+ }
+ else if targetCase == WordCase.FirstUp {
+ for c, i in str {
+ if i == 0 {
+ newStr = tcat(newStr, to_uppercase(c));
+ } else {
+ newStr = tcat(newStr, to_lowercase(c));
+ }
+ }
+ }
+ else if targetCase == WordCase.FirstUpNumberReset {
+ for c, i in str {
+ if i == 0 || (str[i - 1] >= '0' && str[i - 1] <= '9') {
+ newStr = tcat(newStr, to_uppercase(c));
+ } else {
+ newStr = tcat(newStr, to_lowercase(c));
+ }
+ }
+ }
+ return newStr;
+}
+
+// Change the case convention of a string by detecting original convention,
+// then splitting it into words.
+change_case :: proc(str : string, targetCase : Case) -> string {
+ if targetCase == Case.Unknown {
+ return str;
+ }
+
+ // Split
+ parts := autosplit_string(str);
+
+ // Join
+ newStr : string;
+ if targetCase == Case.Pascal {
+ for part, i in parts {
+ newStr = tcat(newStr, change_word_case(part, WordCase.FirstUpNumberReset));
+ }
+ }
+ else if targetCase == Case.Snake {
+ for part, i in parts {
+ newStr = tcat(newStr, change_word_case(part, WordCase.Low), (i != len(parts) - 1) ? "_" : "");
+ }
+ }
+ else if targetCase == Case.Kebab {
+ for part, i in parts {
+ newStr = tcat(newStr, change_word_case(part, WordCase.Low), (i != len(parts) - 1) ? "-" : "");
+ }
+ }
+ else if targetCase == Case.Camel {
+ for part, i in parts {
+ if i == 0 {
+ newStr = tcat(newStr, change_word_case(part, WordCase.Low));
+ } else {
+ newStr = tcat(newStr, change_word_case(part, WordCase.FirstUpNumberReset));
+ }
+ }
+ }
+ else if targetCase == Case.Constant {
+ for part, i in parts {
+ newStr = tcat(newStr, change_word_case(part, WordCase.Up), (i != len(parts) - 1) ? "_" : "");
+ }
+ }
+
+ return newStr;
+}
+
+// Identify the case of the provided string.
+// Full lowercase with no separator is identified as camelCase.
+find_case :: proc(str : string) -> Case {
+ refuted : bool;
+
+ // CONSTANT_CASE
+ refuted = false;
+ for c in str {
+ if (c != '_') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') {
+ refuted = true;
+ break;
+ }
+ }
+ if !refuted do return Case.Constant;
+
+ for c in str {
+ // snake_case
+ if c == '_' {
+ return Case.Snake;
+ } // kebab-case
+ else if c == '-' {
+ return Case.Kebab;
+ }
+ }
+
+ // PascalCase
+ if str[0] >= 'A' && str[0] <= 'Z' {
+ return Case.Pascal;
+ }
+
+ // camelCase
+ return Case.Camel;
+}
+
+// Splits the string according to detected case.
+// HeyBuddy -> {"Hey", "Buddy"}
+// hey-buddy -> {"hey", "buddy"}
+// _hey_buddy -> {"", "hey", "buddy"}
+// and such...
+autosplit_string :: proc(str : string) -> [dynamic]string {
+ lowCount := 0;
+ upCount := 0;
+ for c in str {
+ // If any '_', split according to that (CONSTANT_CASE or snake_case)
+ if c == '_' {
+ return split_from_separator(str, '_');
+ } // If any '-', split according to that (kebab-case)
+ else if c == '-' {
+ return split_from_separator(str, '-');
+ }
+ else if c >= 'a' && c <= 'z' {
+ lowCount += 1;
+ }
+ else if c >= 'A' && c <= 'Z' {
+ upCount += 1;
+ }
+ }
+
+ // If it seems to be only one word
+ if lowCount == 0 || upCount == 0 {
+ parts : [dynamic]string;
+ append(&parts, str);
+ return parts;
+ }
+
+ // Split at each uppercase letter (PascalCase or camelCase)
+ return split_from_capital(str);
+}
+
+split_from_separator :: proc(str : string, sep : rune) -> [dynamic]string {
+ parts : [dynamic]string;
+
+ lastI := 0;
+
+ // Empty strings for starting separators in string
+ for c in str {
+ if c == sep {
+ append(&parts, "");
+ lastI += 1;
+ } else {
+ break;
+ }
+ }
+
+ // Ignore non letter prefix
+ if lastI == 0 {
+ for c in str {
+ if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') {
+ lastI += 1;
+ }
+ else {
+ break;
+ }
+ }
+ }
+
+ for c, i in str {
+ if i > lastI + 1 && c == sep {
+ append(&parts, str[lastI:i]);
+ lastI = i + 1;
+ }
+ }
+
+ append(&parts, str[lastI:]);
+
+ return parts;
+}
+
+split_from_capital :: proc(str : string) -> [dynamic]string {
+ parts : [dynamic]string;
+
+ // Ignore non letter prefix
+ lastI := 0;
+ for c in str {
+ if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') {
+ lastI += 1;
+ }
+ else {
+ break;
+ }
+ }
+
+ // We want to handle:
+ // myBrainIsCRAZY -> my Brain Is Crazy
+ // myCRAZYBrain -> my CRAZY Brain
+ // SOLO -> SOLO
+
+ // Do split
+ for i := 1; i < len(str); i += 1 {
+ if str[i] >= 'A' && str[i] <= 'Z' {
+ // Do not split too much if it seems to be a capitalized word
+ if (lastI == i - 1) && (str[lastI] >= 'A' && str[lastI] <= 'Z') {
+ for ; i + 1 < len(str); i += 1 {
+ if str[i + 1] < 'A' || str[i + 1] > 'Z' {
+ break;
+ }
+ }
+ if (i + 1 == len(str)) && (str[i] >= 'A' && str[i] <= 'Z') {
+ i += 1;
+ }
+ }
+
+ append(&parts, str[lastI:i]);
+ lastI = i;
+ }
+ }
+
+ if lastI != len(str) {
+ append(&parts, str[lastI:]);
+ }
+
+ return parts;
+}
+
+// Check if str if prefixed with any of the provided strings,
+// even combinaisons of those, and remove them.
+remove_prefixes :: proc(str : string, prefixes : []string, transparentPrefixes : []string = nil) -> string {
+ str := str;
+ transparentStr := "";
+
+ found := true;
+ for found {
+ found = false;
+
+ // Remove effective prefixes
+ for prefix in prefixes {
+ if len(str) >= len(prefix) &&
+ str[:len(prefix)] == prefix {
+ str = str[len(prefix):];
+ if len(str) != 0 && (str[0] == '_' || str[0] == '-') {
+ str = str[1:];
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if found do continue;
+
+ // Remove transparent ones, only one by one,
+ // as we want effective ones to be fully removed.
+ for prefix in transparentPrefixes {
+ if len(str) >= len(prefix) &&
+ str[:len(prefix)] == prefix {
+ str = str[len(prefix):];
+ transparentStr = tcat(transparentStr, prefix);
+ if len(str) != 0 && (str[0] == '_' || str[0] == '-') {
+ str = str[1:];
+ transparentStr = tcat(transparentStr, '_');
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+
+ return tcat(transparentStr, str);
+}
+
+// Check if str if postfixes with any of the provided strings,
+// even combinaisons of those, and remove them.
+remove_postfixes_with_removed :: proc(
+ str : string,
+ postfixes : []string,
+ transparentPostfixes : []string = nil) -> (string, [dynamic]string) {
+ str := str;
+ removedPostfixes : [dynamic]string;
+ transparentStr := "";
+
+ found := true;
+ for found {
+ found = false;
+
+ // Remove effective postfixes
+ for postfix in postfixes {
+ if ends_with(str, postfix) {
+ str = str[:len(str) - len(postfix)];
+ if len(str) != 0 && (str[len(str)-1] == '_' || str[len(str)-1] == '-') {
+ str = str[:len(str)-1];
+ }
+ append(&removedPostfixes, postfix);
+ found = true;
+ break;
+ }
+ }
+
+ if found do continue;
+
+ // Remove transparent ones, only one by one,
+ // as we want effective ones to be fully removed.
+ for postfix in transparentPostfixes {
+ if ends_with(str, postfix) {
+ str = str[:len(str) - len(postfix)];
+ transparentStr = tcat(postfix, transparentStr);
+ if len(str) != 0 && (str[len(str)-1] == '_' || str[len(str)-1] == '-') {
+ str = str[:len(str)-1];
+ transparentStr = tcat('_', transparentStr);
+ }
+ found = true;
+ break;
+ }
+ }
+ }
+
+ return tcat(str, transparentStr), removedPostfixes;
+}
+
+remove_postfixes :: proc(
+ str : string,
+ postfixes : []string,
+ transparentPostfixes : []string = nil) -> string {
+ str := str;
+ removedPostfixes : [dynamic]string;
+ str, removedPostfixes = remove_postfixes_with_removed(str, postfixes, transparentPostfixes);
+ return str;
+}
+
+ends_with :: proc(str : string, postfix : string) -> bool {
+ return len(str) >= len(postfix) && str[len(str) - len(postfix):] == postfix;
+}
diff --git a/core/bindgen/generator.odin b/core/bindgen/generator.odin
new file mode 100644
index 000000000..3ef3d69c0
--- /dev/null
+++ b/core/bindgen/generator.odin
@@ -0,0 +1,205 @@
+/**
+ * Odin binding generator from C header data.
+ */
+
+package bindgen
+
+import "core:os"
+import "core:fmt"
+import "core:runtime"
+
+GeneratorOptions :: struct {
+ mode : string, // "odin" or "jai"
+
+ // Variable
+ variableCase : Case,
+
+ // Defines
+ definePrefixes : []string,
+ defineTransparentPrefixes : []string,
+ definePostfixes : []string,
+ defineTransparentPostfixes : []string,
+ defineCase : Case,
+
+ // Pseudo-types
+ pseudoTypePrefixes : []string,
+ pseudoTypeTransparentPrefixes : []string,
+ pseudoTypePostfixes : []string,
+ pseudoTypeTransparentPostfixes : []string,
+ pseudoTypeCase : Case,
+
+ // Enums
+ enumConsideredFlagsPostfixes : []string,
+
+ // Functions
+ functionPrefixes : []string,
+ functionTransparentPrefixes : []string,
+ functionPostfixes : []string,
+ functionTransparentPostfixes : []string,
+ functionCase : Case,
+
+ // Enum values
+ enumValuePrefixes : []string,
+ enumValueTransparentPrefixes : []string,
+ enumValuePostfixes : []string,
+ enumValueTransparentPostfixes : []string,
+ enumValueCase : Case,
+ enumValueNameRemove : bool,
+ enumValueNameRemovePostfixes : []string,
+
+ parserOptions : ParserOptions,
+}
+
+GeneratorData :: struct {
+ handle : os.Handle,
+ nodes : Nodes,
+
+ // References
+ foreignLibrary : string,
+ options : ^GeneratorOptions,
+}
+
+generate :: proc(
+ packageName : string,
+ foreignLibrary : string,
+ outputFile : string,
+ headerFiles : []string,
+ options : GeneratorOptions,
+) {
+ options := options;
+ data : GeneratorData;
+ data.options = &options;
+ data.foreignLibrary = foreignLibrary;
+
+ if options.mode == "" {
+ options.mode = "odin";
+ }
+
+ // Outputing odin file
+ errno : os.Errno;
+
+ // chmod 664 when creating file
+ mode: int = 0;
+ when os.OS == "linux" || os.OS == "darwin" {
+ mode = os.S_IRUSR | os.S_IWUSR | os.S_IRGRP | os.S_IWGRP | os.S_IROTH;
+ }
+
+ data.handle, errno = os.open(outputFile, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, mode);
+ if errno != 0 {
+ fmt.eprint("[bindgen] Unable to write to output file ", outputFile, " (", errno ,")\n");
+ return;
+ }
+ defer os.close(data.handle);
+
+ if options.mode == "jai" {
+ fcat(data.handle, foreignLibrary, " :: #foreign_library \"", foreignLibrary, "\";\n");
+ fcat(data.handle, "\n");
+ } else {
+ fcat(data.handle, "package ", packageName, "\n");
+ fcat(data.handle, "\n");
+ fcat(data.handle, "foreign import \"", foreignLibrary, "\"\n");
+ fcat(data.handle, "\n");
+ fcat(data.handle, "import _c \"core:c\"\n");
+ fcat(data.handle, "\n");
+ }
+
+ // Parsing header files
+ anonymousStructCount = 0;
+ anonymousUnionCount = 0;
+ anonymousEnumCount = 0;
+
+ for headerFile in headerFiles {
+ bytes, ok := os.read_entire_file(headerFile);
+ if !ok {
+ fmt.eprint("[bindgen] Unable to read file ", headerFile, "\n");
+ return;
+ }
+
+ // We fuse the SOAs
+ headerNodes := parse(bytes, options.parserOptions);
+ merge_generic_nodes(&data.nodes.defines, &headerNodes.defines);
+ merge_generic_nodes(&data.nodes.enumDefinitions, &headerNodes.enumDefinitions);
+ merge_generic_nodes(&data.nodes.unionDefinitions, &headerNodes.unionDefinitions);
+ merge_forward_declared_nodes(&data.nodes.structDefinitions, &headerNodes.structDefinitions);
+ merge_generic_nodes(&data.nodes.functionDeclarations, &headerNodes.functionDeclarations);
+ merge_generic_nodes(&data.nodes.typedefs, &headerNodes.typedefs);
+ }
+
+ // Exporting
+ export_defines(&data);
+ export_typedefs(&data);
+ export_enums(&data);
+ export_structs(&data);
+ export_unions(&data);
+
+ // Foreign block for functions
+ if options.mode != "jai" {
+ foreignLibrarySimple := simplify_library_name(foreignLibrary);
+ fcat(data.handle, "@(default_calling_convention=\"c\")\n");
+ fcat(data.handle, "foreign ", foreignLibrarySimple, " {\n");
+ fcat(data.handle, "\n");
+ }
+
+ export_functions(&data);
+
+ if options.mode != "jai" {
+ fcat(data.handle, "}\n");
+ }
+}
+
+// system:foo.lib -> foo
+simplify_library_name :: proc(libraryName : string) -> string {
+ startOffset := 0;
+ endOffset := len(libraryName);
+
+ for c, i in libraryName {
+ if startOffset == 0 && c == ':' {
+ startOffset = i + 1;
+ }
+ else if c == '.' {
+ endOffset = i;
+ break;
+ }
+ }
+
+ return libraryName[startOffset:endOffset];
+}
+
+merge_generic_nodes :: proc(nodes : ^$T, headerNodes : ^T) {
+ for headerNode in headerNodes {
+ // Check that there are no duplicated nodes (due to forward declaration or such)
+ duplicatedIndex := -1;
+ for i := 0; i < len(nodes); i += 1 {
+ node := nodes[i];
+ if node.name == headerNode.name {
+ duplicatedIndex = i;
+ break;
+ }
+ }
+
+ if duplicatedIndex < 0 {
+ append(nodes, headerNode);
+ }
+ }
+}
+
+merge_forward_declared_nodes :: proc(nodes : ^$T, headerNodes : ^T) {
+ for headerNode in headerNodes {
+ // Check that there are no duplicated nodes (due to forward declaration or such)
+ duplicatedIndex := -1;
+ for i := 0; i < len(nodes); i += 1 {
+ node := nodes[i];
+ if node.name == headerNode.name {
+ duplicatedIndex = i;
+ break;
+ }
+ }
+
+ if duplicatedIndex < 0 {
+ append(nodes, headerNode);
+ }
+ else if !headerNode.forwardDeclared && len(headerNode.members) > 0 {
+ nodes[duplicatedIndex] = headerNode;
+ }
+ }
+}
diff --git a/vendor/stb/lib/darwin/libstb_image.a b/vendor/stb/lib/darwin/libstb_image.a
new file mode 100644
index 000000000..06ce44321
--- /dev/null
+++ b/vendor/stb/lib/darwin/libstb_image.a
Binary files differ
diff --git a/vendor/stb/lib/darwin/stb_image.a b/vendor/stb/lib/darwin/stb_image.a
new file mode 100644
index 000000000..1379d6f9e
--- /dev/null
+++ b/vendor/stb/lib/darwin/stb_image.a
Binary files differ
diff --git a/vendor/stb/lib/darwin/stb_image_resize.a b/vendor/stb/lib/darwin/stb_image_resize.a
new file mode 100644
index 000000000..f39c507a6
--- /dev/null
+++ b/vendor/stb/lib/darwin/stb_image_resize.a
Binary files differ
diff --git a/vendor/stb/lib/darwin/stb_image_write.a b/vendor/stb/lib/darwin/stb_image_write.a
new file mode 100644
index 000000000..bce02b33d
--- /dev/null
+++ b/vendor/stb/lib/darwin/stb_image_write.a
Binary files differ
diff --git a/vendor/stb/lib/darwin/stb_rect_pack.a b/vendor/stb/lib/darwin/stb_rect_pack.a
new file mode 100644
index 000000000..3b55ab802
--- /dev/null
+++ b/vendor/stb/lib/darwin/stb_rect_pack.a
Binary files differ
diff --git a/vendor/stb/lib/darwin/stb_truetype.a b/vendor/stb/lib/darwin/stb_truetype.a
new file mode 100644
index 000000000..c4a895b54
--- /dev/null
+++ b/vendor/stb/lib/darwin/stb_truetype.a
Binary files differ
diff --git a/wasm-ld b/wasm-ld
new file mode 120000
index 000000000..01ef2a7e7
--- /dev/null
+++ b/wasm-ld
@@ -0,0 +1 @@
+/Volumes/Phill_Backup/pers/programming/sdk/emsdk/upstream/bin/lld \ No newline at end of file