diff --git a/cl/_testpp/macro/in.h b/cl/_testpp/macro/in.h index b8d5d304..ec093ca1 100644 --- a/cl/_testpp/macro/in.h +++ b/cl/_testpp/macro/in.h @@ -1,4 +1,13 @@ -#define TRUE 1 +#define f() 1 +#define g(x) x + +#define TRUE 1 #define FALSE 0 +#define THREE (TRUE + 2) +#define FVAL (TRUE * 3.14) + +#define MASK (~FALSE) +#define TWO (THREE * TRUE + -TRUE) + typedef char BOOL, *PBOOL; diff --git a/cl/_testpp/macro/out.go b/cl/_testpp/macro/out.go index a1ec0d6d..31e55838 100644 --- a/cl/_testpp/macro/out.go +++ b/cl/_testpp/macro/out.go @@ -2,5 +2,12 @@ package foo import "github.com/goplus/lib/c" +const TRUE = 1 +const FALSE = 0 +const THREE = 3 +const FVAL = 3.14 +const MASK = -1 +const TWO = 2 + type BOOL = c.Char type PBOOL = *c.Char diff --git a/cl/compile.go b/cl/compile.go index f763dbe5..1426b4b9 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -91,11 +91,6 @@ type Config struct { // specified, llcppg uses a default lookup function that returns an empty archivePath // and true (it means any mangling name is considered found). NameLookup func(manglingName string) (archivePath string, ok bool) - - // PresumedFiles specifies the list of files that are presumed to be included in the - // compilation. This is used to determine which files are considered part of the - // package being compiled (optional). - PresumedFiles []string } // ----------------------------------------------------------------------------- @@ -139,9 +134,10 @@ func NewPackage(pkgPath, pkgName string, conf *Config, tu clang.TranslationUnit, nameLookup = defaultNameLookup } ctx := &pkgCtx{ - pkg: pkg, cb: pkg.CB(), llgo: llgo, fset: pkg.Fset, tu: tu, c: c, - lang: conf.Language, cflags: conf.CFlags, wrapFileHeader: conf.WrapFileHeader, - nameLookup: nameLookup, methods: make(map[string]*classMethod), + pkg: pkg, cb: pkg.CB(), llgo: llgo, fset: pkg.Fset, tu: tu, + c: c, lang: conf.Language, cflags: conf.CFlags, + wrapFileHeader: conf.WrapFileHeader, nameLookup: nameLookup, + methods: make(map[string]*classMethod), macroVals: make(map[string]any), } ctx.initFiles(files) loadFiles(ctx) @@ -162,6 +158,13 @@ func loadFiles(ctx *pkgCtx) { overloads: make(map[string]*overloads), } clang.VisitChildren(ctx.tu.Cursor(), func(decl, parent clang.Cursor) clang.ChildVisitResult { + presumedFiles := ctx.presumedFiles + if presumedFiles != nil { + at := clang.PresumedFile(decl.Location()) + if _, ok := presumedFiles[at]; !ok { + return clang.Continue + } + } loadDecl(ctx, scope, decl) return clang.Continue }) @@ -183,17 +186,45 @@ func loadDecl(ctx *pkgCtx, scope *scopeCtx, decl clang.Cursor) { loadClass(ctx, decl, defaultInPublic) case lc.CursorCXXMethod, lc.CursorConstructor, lc.CursorDestructor: loadOutsideMethod(ctx, decl) - case lc.CursorVarDecl: - // compileVarDecl(ctx, decl, global) case lc.CursorTypedefDecl: loadTypedef(ctx, decl) case lc.CursorEnumDecl: // compileEnum(ctx, decl, global) + case lc.CursorMacroDefinition: + loadMacro(ctx, decl) + case lc.CursorVarDecl: + // compileVarDecl(ctx, decl, global) default: log.Panicln("compileDecl: unknown kind =", decl.Kind) } } +func loadMacro(ctx *pkgCtx, decl clang.Cursor) { + if decl.IsMacroFunctionLike() != 0 { + return + } + ctx.compiles = append(ctx.compiles, func(ctx *pkgCtx) { + origName := clang.String(decl) + tokens, dispose := ctx.tu.Tokenize(decl.Extent()) + defer dispose() + if debugCompileDecl { + log.Println("macro", origName, "-", len(tokens), "tokens") + } + if len(tokens) > 1 { + if v, ok := evalConstExpr(ctx, tokens[1:]); ok { + pkg := ctx.pkg + pkgTypes := pkg.Types + ctx.macroVals[origName] = v + name, _ := ctx.getPubName(origName, -1) + pkg.NewConstDefs(pkgTypes.Scope()).New(func(cb *gogen.CodeBuilder) int { + cb.Val(v) + return 1 + }, 0, token.NoPos, nil, name) + } + } + }) +} + func loadTypedef(ctx *pkgCtx, decl clang.Cursor) { ctx.compiles = append(ctx.compiles, func(ctx *pkgCtx) { origName := clang.String(decl) diff --git a/cl/compile_test.go b/cl/compile_test.go index 891f3193..a3e4c213 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -61,7 +61,8 @@ func testFromDir(t *testing.T, sel, relDir string, lang cl.Language) { defer idx.Dispose() filename := pkgDir + "/in.h" - u := idx.ParseTranslationUnit(0, filename, "-x", cltest.LanguageOf(lang)) + u := idx.ParseTranslationUnit( + clang.DetailedPreprocessingRecord, filename, "-x", cltest.LanguageOf(lang)) defer u.Dispose() conf, _ := cltest.LoadConf(pkgDir + "/in.cfg") diff --git a/cl/const_expr.go b/cl/const_expr.go new file mode 100644 index 00000000..b2367eac --- /dev/null +++ b/cl/const_expr.go @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cl + +import ( + "go/token" + "strconv" + "strings" + + "github.com/goplus/llcppg/clang" + lc "github.com/goplus/llcppg/lib/clang" +) + +// ----------------------------------------------------------------------------- + +func evalConstExpr(ctx *pkgCtx, tokens []lc.Token) (v any, ok bool) { + v, _, ok = parseExpr(ctx, ctx.tu, tokens, false) + return +} + +// ----------------------------------------------------------------------------- + +type operand struct { + val any + tok token.Token + prec int +} + +var opPrecs = map[token.Token]int{ + token.REM: 12, + token.MUL: 12, + token.QUO: 12, + + token.ADD: 10, + token.SUB: 10, + + token.SHL: 8, + token.SHR: 8, + + token.AND: 6, + token.XOR: 4, + token.OR: 2, +} + +func parseExpr(ctx *pkgCtx, tu clang.TranslationUnit, tokens []lc.Token, needRParen bool) (v any, left []lc.Token, ok bool) { + v, left, ok = parseOperand(ctx, tu, tokens) + if !ok { + return + } + var tok token.Token + var n, prec int + var ops = []operand{{val: v, tok: token.ILLEGAL, prec: -1}} + for len(left) > 0 { + tok, _, left, ok = scanToken(tu, left) + if !ok { + return + } + prec, ok = opPrecs[tok] + if !ok { + if tok == token.RPAREN && needRParen { + _, ok = calc(ops, n, 0) + v = ops[0].val + } + return + } + n, ok = calc(ops, n, prec) + if !ok { + return + } + v, left, ok = parseOperand(ctx, tu, left) + if !ok { + return + } + n++ + ops = append(ops[:n], operand{val: v, tok: tok, prec: prec}) + } + if ok = !needRParen; ok { + _, ok = calc(ops, n, 0) + v = ops[0].val + } + return +} + +func calc(ops []operand, nlast, prec int) (n int, ok bool) { + n, ok = nlast, true + for ops[n].prec >= prec { + switch op := ops[n].tok; op { + case token.ADD, token.SUB, token.MUL, token.QUO: + ops[n-1].val, ok = mathOp(op, ops[n-1].val, ops[n].val) + if !ok { + return + } + default: + a, ok1 := ops[n-1].val.(int) + b, ok2 := ops[n].val.(int) + if ok = ok1 && ok2; !ok { + return + } + switch op { + case token.SHL: + a <<= b + case token.SHR: + a >>= b + case token.AND: + a &= b + case token.OR: + a |= b + case token.XOR: + a ^= b + case token.REM: + a %= b + default: + panic("parseExpr: unknown op") + } + ops[n-1].val = a + } + n-- + } + return +} + +func mathOp(op token.Token, a, b any) (any, bool) { + switch a := a.(type) { + case int: + switch b := b.(type) { + case int: + switch op { + case token.ADD: + return a + b, true + case token.SUB: + return a - b, true + case token.MUL: + return a * b, true + case token.QUO: + if b == 0 { + // TODO(xsw): + panic("integer divide by zero") + } + return a / b, true + } + case float64: + return floatMathOp(op, float64(a), b), true + } + case float64: + switch b := b.(type) { + case int: + return floatMathOp(op, a, float64(b)), true + case float64: + return floatMathOp(op, a, b), true + } + } + return nil, false +} + +func floatMathOp(op token.Token, a, b float64) float64 { + switch op { + case token.ADD: + return a + b + case token.SUB: + return a - b + case token.MUL: + return a * b + case token.QUO: + return a / b + } + panic("floatMathOp: unknown op") +} + +func parseOperand(ctx *pkgCtx, tu clang.TranslationUnit, tokens []lc.Token) (v any, left []lc.Token, ok bool) { + tok, lit, left, ok := scanToken(tu, tokens) + if !ok { + return + } + switch tok { + case token.FLOAT: + if strings.IndexByte(lit, '.') >= 0 { + val, e := strconv.ParseFloat(lit, 64) + v, ok = val, e == nil + } else { + val, e := strconv.ParseInt(lit, 0, 64) + v, ok = int(val), e == nil + } + case token.LPAREN: + return parseExpr(ctx, tu, left, true) + case token.IDENT: + v, ok = ctx.macroVals[lit] + case token.CHAR, token.STRING: + ok = false // not supported + case token.SUB: // - + v, left, ok = parseOperand(ctx, tu, left) + if !ok { + return + } + switch x := v.(type) { + case int: + v = -x + case float64: + v = -x + default: + ok = false + } + case token.XOR: // ~ + v, left, ok = parseOperand(ctx, tu, left) + if !ok { + return + } + switch x := v.(type) { + case int: + v = ^x + default: + ok = false + } + } + return +} + +// ----------------------------------------------------------------------------- + +var c2goOps = map[string]token.Token{ + "(": token.LPAREN, + ")": token.RPAREN, + + "%": token.REM, + "*": token.MUL, + "/": token.QUO, + + "+": token.ADD, + "-": token.SUB, + + "<<": token.SHL, + ">>": token.SHR, + + "&": token.AND, + "~": token.XOR, + "|": token.OR, +} + +func scanToken(tu clang.TranslationUnit, tokens []lc.Token) (ret token.Token, lit string, left []lc.Token, ok bool) { + for len(tokens) > 0 { + tok := tokens[0] + kind := tok.Kind() + switch kind { + case lc.Punctuation: + op := tu.Token(tok) + left = tokens[1:] + ret, ok = c2goOps[op] + case lc.Literal: + lit, ok = tu.Token(tok), true + switch lit[0] { + case '"': + ret = token.STRING + case '\'': + ret = token.CHAR + default: + ret = token.FLOAT + } + left = tokens[1:] + case lc.Identifier: + ret = token.IDENT + lit, ok = tu.Token(tok), true + left = tokens[1:] + case lc.Comment: + tokens = tokens[1:] + continue + } + break + } + return +} + +// ----------------------------------------------------------------------------- diff --git a/cl/ctx.go b/cl/ctx.go index 20f42443..99b9c78d 100644 --- a/cl/ctx.go +++ b/cl/ctx.go @@ -86,6 +86,8 @@ func (p *nodeInterp) LoadExpr(v ast.Node) string { type compileFunc = func(ctx *pkgCtx) +type none struct{} + type pkgCtx struct { pkg *gogen.Package cb *gogen.CodeBuilder @@ -102,9 +104,12 @@ type pkgCtx struct { nameLookup func(manglingName string) (archivePath string, ok bool) - fileBases map[clang.File]int // clang.File => base + presumedFiles map[string]none // presumedFile set + fileBases map[clang.File]int // clang.File => base + + macroVals map[string]any // macroName => value + methods map[string]*classMethod // manglingName => class - methods map[string]*classMethod // manglingName => class compiles []compileFunc unsafeImported bool @@ -118,8 +123,12 @@ func (p *pkgCtx) forceImportUnsafe() { } func (p *pkgCtx) initFiles(files []string) { + if len(files) == 0 { + return + } fset := p.fset tu := p.tu + presumedFiles := make(map[string]none) fileBases := make(map[clang.File]int) for _, filename := range files { f := tu.File(filename) @@ -131,8 +140,10 @@ func (p *pkgCtx) initFiles(files []string) { tf := fset.AddFile(filename, -1, len(src)) tf.SetLinesForContent(src) fileBases[f] = tf.Base() + presumedFiles[filename] = none{} } p.fileBases = fileBases + p.presumedFiles = presumedFiles } func (p *pkgCtx) compile() { diff --git a/clang/clang.go b/clang/clang.go index 3725c029..9b872193 100644 --- a/clang/clang.go +++ b/clang/clang.go @@ -88,6 +88,27 @@ func (i Index) Dispose() { i.impl.Dispose() } +/** + * Flags that control the creation of translation units. + * + * The enumerators in this enumeration type are meant to be bitwise + * ORed together to specify which options should be used when + * constructing the translation unit. + */ +const ( + /** + * Used to indicate that the parser should construct a "detailed" + * preprocessing record, including all macro definitions and instantiations. + * + * Constructing a detailed preprocessing record requires more memory + * and time to parse, since the information contained in the record + * is usually not retained. However, it can be useful for + * applications that require more detailed information about the + * behavior of the preprocessor. + */ + DetailedPreprocessingRecord = clang.DetailedPreprocessingRecord +) + // ParseTranslationUnit parses the given source file and returns the translation unit corresponding // to that file. func (i Index) ParseTranslationUnit(options uint, filename string, args ...string) TranslationUnit { @@ -141,6 +162,32 @@ func (u TranslationUnit) FileContents(file File) []byte { return unsafe.Slice((*byte)(unsafe.Pointer(data)), int(size)) } +// Tokenize tokenizes the source code described by the given source range +// into raw lexical tokens. Call dispose() to free the memory allocated for +// the tokens after use. +func (u TranslationUnit) Tokenize(extent clang.SourceRange) (ret []clang.Token, dispose func()) { + var tokens *clang.Token + var numTokens c.Uint + u.impl.Tokenize(extent, &tokens, &numTokens) + ret = unsafe.Slice(tokens, int(numTokens)) + dispose = func() { + u.impl.DisposeTokens(tokens, numTokens) + } + return +} + +/** + * Determine the spelling of the given token. + * + * The spelling of a token is the textual representation of that token, e.g., + * the text of an identifier or keyword. + */ +func (u TranslationUnit) Token(tok clang.Token) string { + ret := u.impl.Token(tok) + defer ret.Dispose() + return c.GoString(ret.CStr()) +} + /** * Retrieve the cursor that represents the given translation unit. * @@ -188,9 +235,11 @@ type Cursor = clang.Cursor type SourceLocation = clang.SourceLocation // PresumedFile returns the presumed file name for the given source location. -func PresumedFile(loc SourceLocation) (filename clang.String) { +func PresumedFile(loc SourceLocation) string { + var filename clang.String loc.PresumedLocation(&filename, nil, nil) - return + defer filename.Dispose() + return c.GoString(filename.CStr()) } /** diff --git a/cmd/llcppdump/cppdump.go b/cmd/llcppdump/cppdump.go index f7c0bb22..be44812c 100644 --- a/cmd/llcppdump/cppdump.go +++ b/cmd/llcppdump/cppdump.go @@ -27,14 +27,10 @@ import ( lc "github.com/goplus/llcppg/lib/clang" ) -func dump(node clang.Cursor, ns string, presumedFile *c.Char) { +func dump(node clang.Cursor, ns, presumedFile string) { clang.VisitChildren(node, func(cur, parent clang.Cursor) clang.ChildVisitResult { - if presumedFile != nil { - loc := cur.Location() - at := clang.PresumedFile(loc) - cmpf := c.Strcmp(at.CStr(), presumedFile) - at.Dispose() - if cmpf != 0 { + if presumedFile != "" { + if clang.PresumedFile(cur.Location()) != presumedFile { return clang.Continue } } @@ -68,7 +64,7 @@ func main() { if len(os.Args) > 2 { lang = strings.ToLower(os.Args[2]) } - u := idx.ParseTranslationUnit(0, filename, "-x", lang) + u := idx.ParseTranslationUnit(clang.DetailedPreprocessingRecord, filename, "-x", lang) defer u.Dispose() usys := u.Underlying() @@ -79,9 +75,8 @@ func main() { file := usys.File(spelling.CStr()) loc := usys.GetLocationForOffset(file, 2) presumedFile := clang.PresumedFile(loc) - defer presumedFile.Dispose() - log.Println("==> PresumedFile", c.GoString(presumedFile.CStr())) + log.Println("==> PresumedFile", presumedFile) root := u.Cursor() - dump(root, "", presumedFile.CStr()) + dump(root, "", presumedFile) }