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 + 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 ; + */ +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]; +} + +/** + * { + * = , + * , + * } + */ +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, "}"); +} + +/** + * { + * ; + * , ; + * []; + * } + */ +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, ¶meter.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 ""; +} + +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 Binary files /dev/null and b/vendor/stb/lib/darwin/libstb_image.a 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 Binary files /dev/null and b/vendor/stb/lib/darwin/stb_image.a 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 Binary files /dev/null and b/vendor/stb/lib/darwin/stb_image_resize.a 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 Binary files /dev/null and b/vendor/stb/lib/darwin/stb_image_write.a 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 Binary files /dev/null and b/vendor/stb/lib/darwin/stb_rect_pack.a 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 Binary files /dev/null and b/vendor/stb/lib/darwin/stb_truetype.a 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