mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-13 09:22:22 -07:00
sync with main
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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, ")");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Reference in New Issue
Block a user