blob: 87bb642a36ce84ab361a65dcd5a03ae4cf6d6a09 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:ffi';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:ffi/ffi.dart';
import 'package:ffigen/src/code_generator.dart';
import 'package:ffigen/src/header_parser/data.dart';
import 'package:ffigen/src/header_parser/includer.dart';
import 'package:logging/logging.dart';
import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../data.dart';
import '../utils.dart';
var _logger = Logger('ffigen.header_parser.macro_parser');
/// Saved macros, Key: prefixedName, Value originalName.
final _savedMacros = <String, String>{};
/// Adds a macro definition to be parsed later.
void saveMacroDefinition(Pointer<clang_types.CXCursor> cursor) {
final originalMacroName = cursor.spelling();
if (shouldIncludeMacro(originalMacroName) &&
!isSeenMacro(originalMacroName) &&
clang.clang_Cursor_isMacroBuiltin_wrap(cursor) == 0 &&
clang.clang_Cursor_isMacroFunctionLike_wrap(cursor) == 0) {
// Parse macro only if it's not builtin or function-like.
_logger.fine(
"++++ Saved Macro '$originalMacroName' for later : ${cursor.completeStringRepr()}");
final prefixedName = config.macroDecl.getPrefixedName(originalMacroName);
addMacroToSeen(originalMacroName, prefixedName);
_saveMacro(prefixedName, originalMacroName);
}
}
/// Saves a macro to be parsed later.
///
/// Macros are parsed later in [parseSavedMacros()].
void _saveMacro(String name, String originalName) {
_savedMacros[name] = originalName;
}
List<Constant> _bindings;
/// Macros cannot be parsed directly, so we create a new `.hpp` file in which
/// they are assigned to a variable after which their value can be determined
/// by evaluating the value of the variable.
List<Constant> parseSavedMacros() {
_bindings = [];
if (_savedMacros.keys.isEmpty) {
return _bindings;
}
// Create a file for parsing macros;
final file = createFileForMacros();
final index = clang.clang_createIndex(0, 0);
Pointer<Pointer<Utf8>> clangCmdArgs = nullptr;
var cmdLen = 0;
if (config.compilerOpts != null) {
clangCmdArgs = createDynamicStringArray(config.compilerOpts);
cmdLen = config.compilerOpts.length;
}
final tu = clang.clang_parseTranslationUnit(
index,
Utf8.toUtf8(file.path).cast(),
clangCmdArgs.cast(),
cmdLen,
nullptr,
0,
clang_types.CXTranslationUnit_Flags.CXTranslationUnit_KeepGoing,
);
if (tu == nullptr) {
_logger.severe('Unable to parse Macros.');
} else {
final rootCursor = clang.clang_getTranslationUnitCursor_wrap(tu);
final resultCode = clang.clang_visitChildren_wrap(
rootCursor,
Pointer.fromFunction(_macroVariablevisitor,
clang_types.CXChildVisitResult.CXChildVisit_Break),
uid,
);
visitChildrenResultChecker(resultCode);
rootCursor.dispose();
}
clang.clang_disposeTranslationUnit(tu);
clang.clang_disposeIndex(index);
// Delete the temp file created for macros.
file.deleteSync();
return _bindings;
}
/// Child visitor invoked on translationUnitCursor for parsing macroVariables.
int _macroVariablevisitor(Pointer<clang_types.CXCursor> cursor,
Pointer<clang_types.CXCursor> parent, Pointer<Void> clientData) {
Constant constant;
try {
if (isFromGeneratedFile(cursor) &&
_macroVarNames.contains(cursor.spelling()) &&
cursor.kind() == clang_types.CXCursorKind.CXCursor_VarDecl) {
final e = clang.clang_Cursor_Evaluate_wrap(cursor);
final k = clang.clang_EvalResult_getKind(e);
_logger.fine('macroVariablevisitor: ${cursor.completeStringRepr()}');
/// Get macro name, the variable name starts with '<macro-name>_'.
final macroName = MacroVariableString.decode(cursor.spelling());
switch (k) {
case clang_types.CXEvalResultKind.CXEval_Int:
constant = Constant(
originalName: _savedMacros[macroName],
name: macroName,
rawType: 'int',
rawValue: clang.clang_EvalResult_getAsLongLong(e).toString(),
);
break;
case clang_types.CXEvalResultKind.CXEval_Float:
constant = Constant(
originalName: _savedMacros[macroName],
name: macroName,
rawType: 'double',
rawValue: clang.clang_EvalResult_getAsDouble(e).toString(),
);
break;
case clang_types.CXEvalResultKind.CXEval_StrLiteral:
var value = Utf8.fromUtf8(clang.clang_EvalResult_getAsStr(e).cast());
// Escape $ character.
value = value.replaceAll(r'$', r'\$');
// Escape ' character, because our strings are enclosed with '.
value = value.replaceAll("'", r"\'");
constant = Constant(
originalName: _savedMacros[macroName],
name: macroName,
rawType: 'String',
rawValue: "'${value}'",
);
break;
}
clang.clang_EvalResult_dispose(e);
if (constant != null) {
_bindings.add(constant);
}
}
cursor.dispose();
parent.dispose();
} catch (e, s) {
_logger.severe(e);
_logger.severe(s);
rethrow;
}
return clang_types.CXChildVisitResult.CXChildVisit_Continue;
}
/// Returns true if cursor is from generated file.
bool isFromGeneratedFile(Pointer<clang_types.CXCursor> cursor) {
final s = cursor.sourceFileName();
if (s == null || s.isEmpty) {
return false;
} else {
return p.basename(s) == _generatedFileBaseName;
}
}
/// Base name of generated file.
String _generatedFileBaseName;
/// Generated macro variable names.
///
/// Used to determine if macro should be included in bindings or not.
Set<String> _macroVarNames = {};
/// Creates a temporary file for parsing macros in current directory.
File createFileForMacros() {
final fileNameBase = 'temp_for_macros';
final fileExt = 'hpp';
// Find a filename which doesn't already exist.
var file = File('$fileNameBase.$fileExt');
var i = 0;
while (file.existsSync()) {
i++;
file = File('${fileNameBase.split('.')[0]}_$i.$fileExt');
}
// Create file.
file.createSync();
// Save generted name.
_generatedFileBaseName = p.basename(file.path);
// Write file contents.
final sb = StringBuffer();
for (final h in config.headers.entryPoints) {
sb.writeln('#include "$h"');
}
_macroVarNames = {};
for (final prefixedMacroName in _savedMacros.keys) {
// Write macro.
final macroVarName = MacroVariableString.encode(prefixedMacroName);
sb.writeln('auto ${macroVarName} = ${_savedMacros[prefixedMacroName]};');
// Add to _macroVarNames.
_macroVarNames.add(macroVarName);
}
final macroFileContent = sb.toString();
// Log this generated file for debugging purpose.
// We use the finest log because this file may be very big.
_logger.finest('=====FILE FOR MACROS====');
_logger.finest(macroFileContent);
_logger.finest('========================');
file.writeAsStringSync(macroFileContent);
return file;
}
/// Deals with encoding/decoding name of the variable generated for a Macro.
class MacroVariableString {
static String encode(String s) {
return '_${s.length}_${s}_generated_macro_variable';
}
static String decode(String s) {
// Remove underscore.
s = s.substring(1);
final intReg = RegExp('[0-9]+');
final lengthEnd = intReg.matchAsPrefix(s).end;
final len = int.parse(s.substring(0, lengthEnd));
// Name starts after an unerscore.
final nameStart = lengthEnd + 1;
return s.substring(nameStart, nameStart + len);
}
}