mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-08 22:13:17 +00:00
sync with main
This commit is contained in:
266
core/bindgen/c-parser-evaluate.odin
Normal file
266
core/bindgen/c-parser-evaluate.odin
Normal file
@@ -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;
|
||||
}
|
||||
267
core/bindgen/c-parser-helpers.odin
Normal file
267
core/bindgen/c-parser-helpers.odin
Normal file
@@ -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;
|
||||
}
|
||||
132
core/bindgen/c-parser-nodes.odin
Normal file
132
core/bindgen/c-parser-nodes.odin
Normal file
@@ -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,
|
||||
}
|
||||
840
core/bindgen/c-parser.odin
Normal file
840
core/bindgen/c-parser.odin
Normal file
@@ -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, ¶meter.type);
|
||||
token = peek_token(data);
|
||||
}
|
||||
|
||||
if token == "," {
|
||||
eat_token(data);
|
||||
token = peek_token(data);
|
||||
}
|
||||
|
||||
append(parameters, parameter);
|
||||
}
|
||||
|
||||
check_and_eat_token(data, ")");
|
||||
}
|
||||
44
core/bindgen/errors.odin
Normal file
44
core/bindgen/errors.odin
Normal file
@@ -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);
|
||||
}
|
||||
284
core/bindgen/generator-clean.odin
Normal file
284
core/bindgen/generator-clean.odin
Normal file
@@ -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;
|
||||
}
|
||||
166
core/bindgen/generator-export.odin
Normal file
166
core/bindgen/generator-export.odin
Normal file
@@ -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");
|
||||
}
|
||||
}
|
||||
392
core/bindgen/generator-helpers.odin
Normal file
392
core/bindgen/generator-helpers.odin
Normal file
@@ -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;
|
||||
}
|
||||
205
core/bindgen/generator.odin
Normal file
205
core/bindgen/generator.odin
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
vendor/stb/lib/darwin/libstb_image.a
vendored
Normal file
BIN
vendor/stb/lib/darwin/libstb_image.a
vendored
Normal file
Binary file not shown.
BIN
vendor/stb/lib/darwin/stb_image.a
vendored
Normal file
BIN
vendor/stb/lib/darwin/stb_image.a
vendored
Normal file
Binary file not shown.
BIN
vendor/stb/lib/darwin/stb_image_resize.a
vendored
Normal file
BIN
vendor/stb/lib/darwin/stb_image_resize.a
vendored
Normal file
Binary file not shown.
BIN
vendor/stb/lib/darwin/stb_image_write.a
vendored
Normal file
BIN
vendor/stb/lib/darwin/stb_image_write.a
vendored
Normal file
Binary file not shown.
BIN
vendor/stb/lib/darwin/stb_rect_pack.a
vendored
Normal file
BIN
vendor/stb/lib/darwin/stb_rect_pack.a
vendored
Normal file
Binary file not shown.
BIN
vendor/stb/lib/darwin/stb_truetype.a
vendored
Normal file
BIN
vendor/stb/lib/darwin/stb_truetype.a
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user