Finch/src/cimport.fch
Claude 0bf66740df
Compiler and docs on every system: macOS Intel in CI, releases made by CI, docs for vectors and --cpu
- CI: macOS on Apple Silicon and Intel (macos-15, macos-15-intel); a tag v* attaches every package
  (Linux x64/ARM64, macOS ARM64/x64, Windows x64, the VS Code extension) to its release
- vectors: fma is llvm.fmuladd (llvm.fma was a libm call per lane without FMA hardware: 3.5x slower)
- examples/simd.fch: a dot product eight at a time, timed against the plain loop
- README and the English guides: installing on Linux, macOS and Windows; vectors (SIMD); --cpu;
  -g is DWARF on Windows too (lldb from llvm-mingw); no more mention of the C++ compiler
- comments left from the C++ compiler (src/target.cpp, src/codegen.cpp, "never frees") brought up to date

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Psc42moPLwP43Cmb9oZqSR
2026-11-01 13:57:28 +00:00

792 lines
25 KiB
Text

// C headers, read through libclang, written in Finch.
//
// What `import "stdio.h"` teaches the compiler: the C functions, the constants (enum values and
// plain #define numbers), the extern variables (stdout) and the structs. It calls libclang's C API
// through Finch's own C import -- so to compile this file, the compiler reads libclang's headers
// with this very code.
//
// C structs by value appear in types as the named type `module "C"`.
import ast
import host
import "clang-c/Index.h"
link "clang"
struct CFunc {
str name
str header // the import it came from
ast.Type ret
[]ast.Type params
bool variadic
bool inline // static inline in the header: called through a pointer a small C file keeps (finch_inl_name)
str unsupported // non-empty: why Finch can't call it (yet)
}
struct CConst { // enum values and #define constants
bool isFloat
int i
float f
str text // a float as written, so no digits are lost: 3.14159265358979323846
bool isStr // #define NAME "text"
str s
str structName // #define RAYWHITE (Color){ 245, 245, 245, 255 }: the struct, and its fields in order
[]str fields // each as a Finch literal: 245, 0.5
[]bool fieldFloat
}
// what visitMacroVar looks for inside (Color){ ... }
struct InitFind {
bool found
CXCursor list
bool ok
[]str fields
[]bool floats
}
struct CField {
str name
ast.Type ty
int offset // bytes
int size
int align
bool hidden // unions, bit fields, unmappable types: kept for layout only
}
struct CStruct {
str name
[]CField fields
int size
int align
str unsupported // why it can't be used by value (pointers to it still work)
bool hasHidden
bool isUnion // every field starts at byte 0
}
struct CImports {
map[str]CFunc fns
map[str]CConst consts
map[str]ast.Type globals
map[str]CStruct structs
[]str structOrder // the order they were defined in
}
// A header to read: the path as written, and where it was written, for error messages.
struct Header {
str path
str where
}
// Where a C type is used decides how `char *` looks in Finch: in function signatures it is a
// `str`; inside structs, globals and behind pointers it stays a raw `ptr[char]`.
struct Mapped {
ast.Type ty
str why
bool ok
}
struct Importer {
CImports out
ptr tu
str header
map[str]str recordNames // record USR -> Finch name (a typedef name wins)
map[str]bool defining // USR -> true while its fields are being mapped
int anon
[]str exprMacros // #defines that are expressions: clang works out their values
}
fn text(CXString s) -> str {
out := clang_getCString(s)
clang_disposeString(s)
return out
}
fn ok(ast.Type t) -> Mapped {
return Mapped(ty: t, ok: true)
}
fn no(str why) -> Mapped {
return Mapped(why: why, ok: false)
}
fn fixed(ast.Type elem, int count) -> ast.Type {
t := ast.Type(kind: "fixed", count: count)
t.elem.push(elem)
return t
}
// ---------- the system's header folders ----------
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
fn systemIncludeArgs(str cc) -> []str {
[]str dirs
tmp := host.tempDir() + "/finch_cc_dirs_" + str(host.pid()) + ".txt"
empty := host.tempDir() + "/finch_empty_" + str(host.pid()) + ".c"
write_file(empty, "")
host.run(cc + " -E -v -x c " + host.quote(empty) + " 2> " + host.quote(tmp) + " > " + host.nullDevice())
host.remove(empty)
if !file_exists(tmp) {
return dirs
}
inList := false
for line in read_file(tmp).split("\n") {
l := line.trim()
if l.starts_with("#include <...> search starts here:") {
inList = true
continue
}
if l.starts_with("End of search list.") {
break
}
if !inList || l.len == 0 {
continue
}
// macOS: "/.../Frameworks (framework directory)"
if l.ends_with(" (framework directory)") {
dirs.push("-iframework" + l.sub(0, l.len - 22))
} else {
dirs.push("-isystem" + l)
}
}
host.remove(tmp)
return dirs
}
// ---------- records ----------
// The Finch name of a C record: its typedef name, its tag, or a made-up one.
fn recordName(ptr[Importer] imp, CXType t) -> str {
decl := clang_getTypeDeclaration(t)
usr := text(clang_getCursorUSR(decl))
if imp.recordNames.has(usr) {
return imp.recordNames[usr]
}
name := text(clang_getCursorSpelling(decl))
if name.len == 0 || name.contains(" ") {
imp.anon += 1
name = "anon_struct_" + str(imp.anon)
}
imp.recordNames[usr] = name
return name
}
// Fill out.structs[name] for a complete record; returns its name, or "" if it can't be one.
fn defineRecord(ptr[Importer] imp, CXType type) -> str {
t := clang_getCanonicalType(type)
decl := clang_getTypeDeclaration(t)
usr := text(clang_getCursorUSR(decl))
name := recordName(imp, t)
if imp.out.structs.has(name) || imp.defining.has(usr) {
return name
}
size := int(clang_Type_getSizeOf(t))
if size < 0 {
return "" // incomplete (opaque) struct
}
imp.defining[usr] = true
s := CStruct(name: name, size: size, align: int(clang_Type_getAlignOf(t)))
if clang_getCursorKind(decl) == CXCursor_UnionDecl {
s.unsupported = "is a C union"
s.isUnion = true
}
// the fields are collected by a callback that gets the importer and the struct through one pointer
ctx := FieldCtx(imp: imp, s: addr(s))
clang_Type_visitFields(t, addr(visitField), addr(ctx))
imp.defining.remove(usr)
imp.out.structs[name] = s
imp.out.structOrder.push(name)
return name
}
struct FieldCtx {
ptr[Importer] imp
ptr[CStruct] s
}
fn visitField(CXCursor field, ptr data) -> i32 {
ptr[FieldCtx] c = data
f := CField(name: text(clang_getCursorSpelling(field)))
ft := clang_getCanonicalType(clang_getCursorType(field))
f.offset = int(clang_Cursor_getOffsetOfField(field)) / 8
f.size = int(clang_Type_getSizeOf(ft))
f.align = int(clang_Type_getAlignOf(ft))
if clang_Cursor_isBitField(field) != 0 {
c.s.unsupported = "has bit fields"
f.hidden = true
} else {
if ft.kind == CXType_Record && clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl {
c.s.unsupported = "has a union inside" // its fields can be used, but not passed by value
}
m := mapType(c.imp, ft, false, false)
if m.ok {
f.ty = m.ty
} else {
f.hidden = true
}
}
if f.hidden {
c.s.hasHidden = true
}
if f.name.len == 0 {
f.hidden = true
}
c.s.fields.push(f)
return CXVisit_Continue
}
// ---------- types ----------
fn sized(int size, bool signed) -> Mapped {
if size == 1 {
if signed {
return ok(ast.simple("i8"))
}
return ok(ast.simple("u8"))
}
if size == 2 {
if signed {
return ok(ast.simple("i16"))
}
return ok(ast.simple("u16"))
}
if size == 4 {
if signed {
return ok(ast.simple("i32"))
}
return ok(ast.simple("u32"))
}
if size == 8 {
if signed {
return ok(ast.simple("int"))
}
return ok(ast.simple("u64"))
}
return no("uses a " + str(size * 8) + "-bit number")
}
// C type -> Finch type. Not ok (with a reason) if Finch can't express it.
// `signature`: a char * is a str (a function's parameters and result); anywhere else it stays a ptr[char].
// A parameter that is a plain `char *` (not const) is a buffer C writes into, and stays a ptr[char];
// only `const char *` is text going in. A result that is a char * is always text coming out.
fn mapType(ptr[Importer] imp, CXType type, bool signature, bool param) -> Mapped {
t := clang_getCanonicalType(type)
size := int(clang_Type_getSizeOf(t))
k := t.kind
if k == CXType_Void {
return ok(ast.simple("void"))
}
if k == CXType_Bool {
return ok(ast.simple("bool"))
}
if k == CXType_Char_S || k == CXType_Char_U {
return ok(ast.simple("char"))
}
if k == CXType_SChar || k == CXType_Short || k == CXType_Int || k == CXType_Long || k == CXType_LongLong {
return sized(size, true)
}
if k == CXType_UChar || k == CXType_UShort || k == CXType_UInt || k == CXType_ULong || k == CXType_ULongLong {
return sized(size, false)
}
if k == CXType_Enum {
return sized(size, true)
}
if k == CXType_Float {
return ok(ast.simple("f32"))
}
if k == CXType_Double {
return ok(ast.simple("float"))
}
if k == CXType_Pointer {
pointee := clang_getCanonicalType(clang_getPointeeType(t))
if (pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) && signature {
if !param || clang_isConstQualifiedType(pointee) != 0 {
return ok(ast.simple("str")) // a C string
}
}
if pointee.kind == CXType_Void || pointee.kind == CXType_FunctionProto || pointee.kind == CXType_FunctionNoProto {
return ok(ast.simple("ptr")) // an opaque `ptr` you can pass around
}
inner := mapType(imp, pointee, false, false)
if !inner.ok || inner.ty.kind == "void" || inner.ty.kind == "fixed" {
return ok(ast.simple("ptr"))
}
return ok(ast.ptrTo(inner.ty))
}
if k == CXType_Record {
name := defineRecord(imp, t)
if name.len == 0 {
return no("uses an incomplete C struct by value")
}
return ok(ast.Type(kind: "named", name: name, module: "C"))
}
if k == CXType_ConstantArray {
inner := mapType(imp, clang_getArrayElementType(t), false, false)
if !inner.ok {
return inner
}
return ok(fixed(inner.ty, int(clang_getArraySize(t))))
}
if k == CXType_LongDouble {
return no("uses long double")
}
return no("uses the C type '" + text(clang_getTypeSpelling(t)) + "'")
}
// ---------- #define numbers ----------
struct Number {
CConst c
bool ok
}
// "0x1F", "010", "10UL" -> the value, and whether the text was a whole number
fn parseInt(str lit) -> Number {
s := lit
base := 10
if s.starts_with("0x") || s.starts_with("0X") {
base = 16
s = s.sub(2, s.len)
} else if s.len > 1 && s[0] == '0' {
base = 8
s = s.sub(1, s.len)
}
n := 0
seen := false
i := 0
while i < s.len {
c := s[i]
d := 99
if c >= '0' && c <= '9' {
d = int(c) - int('0')
} else if c >= 'a' && c <= 'f' {
d = int(c) - int('a') + 10
} else if c >= 'A' && c <= 'F' {
d = int(c) - int('A') + 10
}
if d >= base {
break
}
n = n * base + d
seen = true
i += 1
}
// what is left may only be suffixes: 10UL
while i < s.len {
if !"uUlL".contains(str(s[i])) {
return Number()
}
i += 1
}
if !seen && base != 8 {
return Number()
}
return Number(c: CConst(i: n), ok: true)
}
// The tokens after a #define's name; `safe` is false if any of them could not be part of a
// constant expression (braces, semicolons, statements), so it is never handed to clang.
struct MacroTokens {
[]str parts
bool safe
}
fn macroTokens(ptr[Importer] imp, CXCursor c) -> MacroTokens {
ptr[CXToken] tokens = null
u32 n = 0
clang_tokenize(imp.tu, clang_getCursorExtent(c), addr(tokens), addr(n))
out := MacroTokens(safe: true)
allowed := ["(", ")", "+", "-", "*", "/", "%", "<<", ">>", "|", "&", "^", "~", "!", "<", ">", "<=", ">=", "==", "!=", "&&", "||", "?", ":", ",", "{", "}"]
words := ["sizeof", "unsigned", "signed", "int", "long", "short", "char", "float", "double", "const", "_Bool"]
depth := 0
for k in 1..int(n) { // token 0 is the macro's name
kind := clang_getTokenKind(tokens[k])
if kind == CXToken_Comment {
continue
}
t := text(clang_getTokenSpelling(imp.tu, tokens[k]))
out.parts.push(t)
if kind == CXToken_Punctuation {
if !allowed.contains(t) {
out.safe = false
}
if t == "(" {
depth += 1
} else if t == ")" {
depth -= 1
}
} else if kind == CXToken_Keyword && !words.contains(t) {
out.safe = false
}
}
clang_disposeTokens(imp.tu, tokens, n)
if depth != 0 {
out.safe = false
}
return out
}
// "text" as C writes it -> the text: \n \t \" \\ \0 and \xNN; "a" "b" are joined
fn cString([]str parts) -> Number {
out := ""
for p in parts {
if p.len < 2 || p[0] != '"' || p[p.len - 1] != '"' {
return Number()
}
i := 1
while i < p.len - 1 {
c := p[i]
if c == '\\' && i + 1 < p.len - 1 {
i += 1
e := p[i]
if e == 'n' {
c = '\n'
} else if e == 't' {
c = '\t'
} else if e == 'r' {
c = '\r'
} else if e == '0' {
c = char(0)
} else if e == 'x' {
v := 0
while i + 1 < p.len - 1 && "0123456789abcdefABCDEF".contains(str(p[i + 1])) {
i += 1
v = v * 16 + parseInt("0x" + str(p[i])).c.i
}
c = char(v & 255)
} else {
c = e
}
}
out = out + str(c)
i += 1
}
}
return Number(c: CConst(isStr: true, s: out), ok: true)
}
// Simple #defines: a single number, maybe negative, maybe in parentheses; or a "text".
fn macroValue(ptr[Importer] imp, CXCursor c) -> Number {
parts := macroTokens(imp, c).parts
if parts.len > 0 && parts[0].starts_with("\"") {
return cString(parts)
}
while parts.len >= 2 && parts[0] == "(" && parts[parts.len - 1] == ")" {
parts.remove(0)
parts.pop()
}
neg := false
if parts.len == 2 && parts[0] == "-" {
neg = true
parts.remove(0)
}
if parts.len != 1 {
return Number()
}
lit := parts[0]
if lit.len == 0 || !((lit[0] >= '0' && lit[0] <= '9') || lit[0] == '.') {
return Number()
}
isFloat := (lit.contains(".") || lit.contains("e") || lit.contains("E")) && !lit.starts_with("0x")
if isFloat {
body := lit
while body.len > 0 && "fFlL".contains(str(body[body.len - 1])) {
body = body.sub(0, body.len - 1)
}
v := float(body) or {
return Number()
}
if neg {
v = 0.0 - v
}
return Number(c: CConst(isFloat: true, f: v, text: body), ok: true)
}
r := parseInt(lit)
if r.ok && neg {
r.c.i = 0 - r.c.i
}
return r
}
// ---------- the top level ----------
fn visitTop(CXCursor c, CXCursor parent, ptr data) -> i32 {
ptr[Importer] imp = data
visit(imp, c)
return CXChildVisit_Continue
}
fn visit(ptr[Importer] imp, CXCursor c) {
name := text(clang_getCursorSpelling(c))
kind := clang_getCursorKind(c)
if kind == CXCursor_TypedefDecl {
// typedef struct {...} Name; -> the struct is called Name in Finch
u := clang_getCanonicalType(clang_getTypedefDeclUnderlyingType(c))
if u.kind == CXType_Record {
usr := text(clang_getCursorUSR(clang_getTypeDeclaration(u)))
// prefer the typedef name, unless the struct is already known under its tag
used := imp.recordNames.has(usr) && imp.out.structs.has(imp.recordNames[usr])
if !used && !imp.out.structs.has(name) {
imp.recordNames[usr] = name
}
defineRecord(imp, u) // usable even if no function mentions it: Motion m
}
} else if kind == CXCursor_FunctionDecl {
if imp.out.fns.has(name) {
return
}
f := CFunc(name: name, header: imp.header)
if clang_Cursor_getStorageClass(c) == CX_SC_Static {
f.inline = true // mingw's printf and time, header-only helpers
}
ft := clang_getCanonicalType(clang_getCursorType(c))
if ft.kind == CXType_FunctionNoProto {
f.unsupported = "has no parameter list in its C declaration"
imp.out.fns[name] = f
return
}
r := mapType(imp, clang_getResultType(ft), true, false)
if r.ok {
f.ret = r.ty
} else {
f.unsupported = r.why
}
n := int(clang_getNumArgTypes(ft))
for k in 0..n {
if f.unsupported.len > 0 {
break
}
p := mapType(imp, clang_getArgType(ft, u32(k)), true, true)
if p.ok {
f.params.push(p.ty)
} else {
f.unsupported = p.why
}
}
f.variadic = clang_isFunctionTypeVariadic(ft) != 0
imp.out.fns[name] = f
} else if kind == CXCursor_VarDecl {
if clang_Cursor_getStorageClass(c) != CX_SC_Extern || imp.out.globals.has(name) {
return
}
g := mapType(imp, clang_getCursorType(c), false, false)
if g.ok {
imp.out.globals[name] = g.ty
}
} else if kind == CXCursor_StructDecl || kind == CXCursor_UnionDecl {
if clang_isCursorDefinition(c) != 0 && name.len > 0 && !name.contains(" ") {
usr := text(clang_getCursorUSR(c))
if !imp.recordNames.has(usr) {
imp.recordNames[usr] = name
}
}
} else if kind == CXCursor_EnumDecl {
clang_visitChildren(c, addr(visitTop), imp)
} else if kind == CXCursor_EnumConstantDecl {
if !imp.out.consts.has(name) {
imp.out.consts[name] = CConst(i: int(clang_getEnumConstantDeclValue(c)))
}
} else if kind == CXCursor_MacroDefinition {
if clang_Cursor_isMacroBuiltin(c) != 0 || clang_Cursor_isMacroFunctionLike(c) != 0 {
return
}
if imp.out.consts.has(name) {
return
}
v := macroValue(imp, c)
if v.ok {
imp.out.consts[name] = v.c
return
}
mt := macroTokens(imp, c)
if mt.safe && mt.parts.len > 0 {
imp.exprMacros.push(name) // (1 << 3), (FLAG_A | FLAG_B), ((Uint64)1): asked of clang later
}
}
}
// Read every header in `headers`; `dirs` are searched first for "local.h" headers.
// `target` is the LLVM triple the program is built for ("" = this system): sizes and headers depend on it.
fn importHeaders([]Header headers, []str dirs, str target, bool windows, str ccFor) -> CImports! {
imp := Importer()
if headers.len == 0 {
return imp.out
}
[]str args = ["-x", "c", "-std=gnu11"]
if target.len > 0 {
args.push("--target=" + target)
}
if windows {
args.push("-D_USE_MATH_DEFINES") // M_PI and friends, as on Linux
}
for d in dirs {
args.push("-I" + d)
}
for d in systemIncludeArgs(ccFor) {
args.push(d)
}
[]ptr[char] argv
for i in 0..args.len {
argv.push(args[i].ptr)
}
index := clang_createIndex(0, 0)
for h in headers {
src := "#include \"" + h.path + "\"\n"
file := host.unsavedFile("finch_import.c", src)
ptr tu = null
flags := CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies
err := clang_parseTranslationUnit2(index, "finch_import.c", argv.ptr, i32(argv.len), addr(file), 1, u32(flags), addr(tu))
if err != CXError_Success || tu == null {
return error(h.where + ": error: couldn't read C header '" + h.path + "'")
}
nd := int(clang_getNumDiagnostics(tu))
for k in 0..nd {
d := clang_getDiagnostic(tu, u32(k))
if clang_getDiagnosticSeverity(d) >= CXDiagnostic_Error {
msg := text(clang_getDiagnosticSpelling(d))
clang_disposeDiagnostic(d)
if msg.contains("file not found") {
// "'GL/gl.h' file not found": it may be a header that this one includes
missing := msg.sub(1, msg.sub(1, msg.len).find("'") + 1)
if missing == h.path {
return error(h.where + ": error: can't find the C header '" + h.path + "'")
}
return error(h.where + ": error: can't find the C header '" + missing + "', which '" + h.path + "' includes (is its library's development package installed?)")
}
return error(h.where + ": error: C header '" + h.path + "' has an error: " + msg)
}
clang_disposeDiagnostic(d)
}
imp.tu = tu
imp.header = h.path
clang_visitChildren(clang_getTranslationUnitCursor(tu), addr(visitTop), addr(imp))
clang_disposeTranslationUnit(tu)
evalMacros(addr(imp), index, h, argv)
}
clang_disposeIndex(index)
return imp.out
}
// #defines that are expressions: each becomes `static const __auto_type __finch_m_NAME = (NAME);`
// in a second look at the header, and clang works out the value. One that isn't a constant
// (a type, a function call) just gives clang an error there, and is left out.
fn evalMacros(ptr[Importer] imp, ptr index, Header h, []ptr[char] argv) {
if imp.exprMacros.len == 0 {
return
}
src := "#include \"" + h.path + "\"\n"
for m in imp.exprMacros {
src = src + "static const __auto_type __finch_m_" + m + " = (" + m + ");\n"
}
[]str none
imp.exprMacros = none
file := host.unsavedFile("finch_macros.c", src)
ptr tu = null
err := clang_parseTranslationUnit2(index, "finch_macros.c", argv.ptr, i32(argv.len), addr(file), 1, u32(CXTranslationUnit_SkipFunctionBodies), addr(tu))
if err != CXError_Success || tu == null {
return
}
clang_visitChildren(clang_getTranslationUnitCursor(tu), addr(visitMacroVar), imp)
clang_disposeTranslationUnit(tu)
}
// a float as a Finch literal: 2.0, not 2
fn floatText(float v) -> str {
t := str(v)
if !t.contains(".") && !t.contains("e") {
t = t + ".0"
}
return t
}
// (Color){ 245, 245, 245, 255 }: a struct whose fields are all numbers, in order
fn structMacro(ptr[Importer] imp, str name, CXCursor c, CXType t) {
sname := recordName(imp, t)
if !imp.out.structs.has(sname) {
return
}
find := InitFind(ok: true)
clang_visitChildren(c, addr(visitFindInit), addr(find))
if !find.found {
return
}
clang_visitChildren(find.list, addr(visitInitField), addr(find))
if !find.ok || find.fields.len == 0 || find.fields.len > imp.out.structs[sname].fields.len {
return
}
imp.out.consts[name] = CConst(structName: sname, fields: find.fields, fieldFloat: find.floats)
}
fn visitFindInit(CXCursor c, CXCursor parent, ptr data) -> i32 {
ptr[InitFind] f = data
if clang_getCursorKind(c) == CXCursor_InitListExpr {
f.found = true
f.list = c
return CXChildVisit_Break
}
return CXChildVisit_Recurse
}
fn visitInitField(CXCursor c, CXCursor parent, ptr data) -> i32 {
ptr[InitFind] f = data
r := clang_Cursor_Evaluate(c)
if r == null {
f.ok = false
return CXChildVisit_Break
}
k := clang_EvalResult_getKind(r)
if k == CXEval_Int {
f.fields.push(str(int(clang_EvalResult_getAsLongLong(r))))
f.floats.push(false)
} else if k == CXEval_Float {
v := clang_EvalResult_getAsDouble(r)
if v == v && v - v == 0.0 {
f.fields.push(floatText(v))
f.floats.push(true)
} else {
f.ok = false
}
} else {
f.ok = false // a struct in the struct, a pointer, a text: not a simple constant
}
clang_EvalResult_dispose(r)
if !f.ok {
return CXChildVisit_Break
}
return CXChildVisit_Continue
}
fn visitMacroVar(CXCursor c, CXCursor parent, ptr data) -> i32 {
ptr[Importer] imp = data
if clang_getCursorKind(c) != CXCursor_VarDecl {
return CXChildVisit_Continue
}
name := text(clang_getCursorSpelling(c))
if !name.starts_with("__finch_m_") {
return CXChildVisit_Continue
}
name = name.sub(10, name.len)
if imp.out.consts.has(name) {
return CXChildVisit_Continue
}
t := clang_getCanonicalType(clang_getCursorType(c))
if t.kind == CXType_Record {
structMacro(imp, name, c, t)
return CXChildVisit_Continue
}
r := clang_Cursor_Evaluate(c)
if r == null {
return CXChildVisit_Continue
}
k := clang_EvalResult_getKind(r)
if k == CXEval_Int {
imp.out.consts[name] = CConst(i: int(clang_EvalResult_getAsLongLong(r)))
} else if k == CXEval_Float {
v := clang_EvalResult_getAsDouble(r)
if v == v && v - v == 0.0 { // not nan or inf
imp.out.consts[name] = CConst(isFloat: true, f: v, text: floatText(v))
}
} else if k == CXEval_StrLiteral {
imp.out.consts[name] = CConst(isStr: true, s: clang_EvalResult_getAsStr(r))
}
clang_EvalResult_dispose(r)
return CXChildVisit_Continue
}