import { parser } from '../../vendor/lezer-cpp/lezer-cpp.mjs'
/**
* C++ parser — walks the Lezer concrete syntax tree to extract:
* - #include "local.h" → imports (quoted, not <angle-bracket>)
* - class / struct definitions → classes with methods, inheritance
* - free functions → functions
* - namespaces → namespaces array
* - enums → enums array
* - typedef / using aliases → constants
* - #define macros → constants
*/
export function parseFile (source, filePath) {
let tree
try { tree = parser.parse(source) }
catch (err) { return { error: err.message, filePath } }
const result = {
filePath,
imports: [],
exports: [],
classes: [],
functions: [],
constants: [],
namespaces: [],
enums: []
}
// Out-of-line definitions (Class::method) may appear before their class; track them
// and merge into the class if we encounter it.
const outOfLineMethods = {} // className -> [method,...]
const cursor = tree.cursor()
if (cursor.firstChild()) {
do {
visitTopLevel(cursor, source, result, outOfLineMethods)
} while (cursor.nextSibling())
}
// Attach any out-of-line methods to their classes (class may have been visited earlier)
for (const cls of result.classes) {
const extra = outOfLineMethods[cls.name]
if (extra) {
for (const m of extra) cls.methods.push(m)
}
}
return result
}
function visitTopLevel (cursor, source, result, outOfLineMethods) {
switch (cursor.name) {
case 'PreprocDirective':
handlePreproc(cursor, source, result)
return
case 'NamespaceDefinition':
handleNamespace(cursor, source, result, outOfLineMethods)
return
case 'ClassSpecifier':
case 'StructSpecifier': {
const cls = extractClass(cursor, source, cursor.name === 'StructSpecifier' ? 'struct' : 'class')
if (cls) {
result.classes.push(cls)
result.exports.push({ name: cls.name, type: cls.kind })
}
return
}
case 'FunctionDefinition':
handleFunctionDefinition(cursor, source, result, outOfLineMethods)
return
case 'EnumSpecifier':
handleEnum(cursor, source, result)
return
case 'TypeDefinition':
case 'AliasDeclaration': {
const name = findTypeIdentifier(cursor, source)
if (name) result.constants.push({ name, kind: 'typedef' })
return
}
case 'Declaration':
// GDCLASS(Name, Base); lives here. We can ignore most declarations but
// preserve top-level function declarations.
handleDeclaration(cursor, source, result)
return
}
}
// --- #include handling ---
function handlePreproc (cursor, source, result) {
// #include followed by either SystemLibString (<...>) or String ("...")
if (!cursor.firstChild()) return
let isInclude = false
try {
do {
if (cursor.name === '#include') isInclude = true
else if (isInclude && cursor.name === 'String') {
// Quoted include — local dependency
const text = source.slice(cursor.from, cursor.to).replace(/^["<]|[">]$/g, '')
result.imports.push({ specifier: text, names: [] })
}
// SystemLibString (<...>) is skipped — system headers don't participate in the local graph
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
}
// --- Namespace ---
function handleNamespace (cursor, source, result, outOfLineMethods) {
if (!cursor.firstChild()) return
let nsName = null
try {
do {
if (cursor.name === 'Identifier' && !nsName) {
const text = source.slice(cursor.from, cursor.to)
if (text !== 'namespace') nsName = text
} else if (cursor.name === 'CompoundStatement') {
// Walk the body as if it were top-level
if (cursor.firstChild()) {
do { visitTopLevel(cursor, source, result, outOfLineMethods) } while (cursor.nextSibling())
cursor.parent()
}
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
if (nsName && !result.namespaces.includes(nsName)) result.namespaces.push(nsName)
}
// --- Class / struct ---
function extractClass (cursor, source, kind) {
const cls = {
name: null,
kind,
extends: null,
line: getLine(source, cursor.from),
methods: [],
getters: [],
staticMethods: [],
constructor: null
}
if (!cursor.firstChild()) return null
try {
do {
if (cursor.name === 'TypeIdentifier' && !cls.name) {
cls.name = source.slice(cursor.from, cursor.to)
} else if (cursor.name === 'BaseClassClause') {
cls.extends = extractBaseClass(cursor, source)
} else if (cursor.name === 'FieldDeclarationList') {
extractFieldDeclarations(cursor, source, cls)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
return cls.name ? cls : null
}
function extractBaseClass (cursor, source) {
// BaseClassClause contains Access + TypeIdentifier (or just TypeIdentifier)
if (!cursor.firstChild()) return null
try {
do {
if (cursor.name === 'TypeIdentifier') {
return source.slice(cursor.from, cursor.to)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
return null
}
function extractFieldDeclarations (cursor, source, cls) {
if (!cursor.firstChild()) return
let currentAccess = 'private' // default for class, public for struct — not enforced here
try {
do {
if (cursor.name === 'AccessSpecifier') {
const text = source.slice(cursor.from, cursor.to)
if (text.startsWith('public')) currentAccess = 'public'
else if (text.startsWith('protected')) currentAccess = 'protected'
else currentAccess = 'private'
} else if (cursor.name === 'FieldDeclaration' || cursor.name === 'FunctionDefinition') {
const isStatic = hasChildWithText(cursor, source, 'static')
const decl = extractFieldOrMethod(cursor, source, cls.name)
if (decl) {
decl.access = currentAccess
if (decl.isMethod) {
const m = { name: decl.name, params: decl.params, line: decl.line }
if (isStatic) {
m.static = true
cls.staticMethods.push(m)
} else if (decl.name === cls.name) {
cls.constructor = m
} else {
cls.methods.push(m)
}
}
// Data fields: not separately tracked in the common interface; could add to state later
}
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
}
function hasChildWithText (cursor, source, text) {
if (!cursor.firstChild()) return false
try {
do {
const t = source.slice(cursor.from, cursor.to)
if (t === text) return true
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
return false
}
function extractFieldOrMethod (cursor, source, className) {
// FieldDeclaration may contain a FunctionDeclarator (method) or just an identifier (data field).
// FunctionDefinition contains a FunctionDeclarator + CompoundStatement (inline method body).
// Return types like `Foo*` wrap the FunctionDeclarator in a PointerDeclarator — recurse through those.
let name = null
let params = []
let isMethod = false
const line = getLine(source, cursor.from)
function visit () {
if (!cursor.firstChild()) return
try {
do {
if (cursor.name === 'FunctionDeclarator') {
isMethod = true
const fd = extractFunctionDeclarator(cursor, source)
if (fd) { name = fd.name; params = fd.params }
} else if (cursor.name === 'PointerDeclarator' || cursor.name === 'ReferenceDeclarator') {
// Recurse: `Foo* create()` or `Foo& create()`
visit()
} else if ((cursor.name === 'FieldIdentifier' || cursor.name === 'Identifier') && !name) {
name = source.slice(cursor.from, cursor.to)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
}
visit()
if (!name) return null
return { name, params, isMethod, line }
}
function extractFunctionDeclarator (cursor, source) {
let name = null
const params = []
let classPart = null // for scoped names like ClassName::method
if (!cursor.firstChild()) return null
try {
do {
if (cursor.name === 'Identifier' && !name) {
name = source.slice(cursor.from, cursor.to)
} else if (cursor.name === 'FieldIdentifier' && !name) {
name = source.slice(cursor.from, cursor.to)
} else if (cursor.name === 'ScopedIdentifier') {
// ClassName::method — extract both halves
const parts = extractScoped(cursor, source)
if (parts) { classPart = parts.ns; name = parts.name }
} else if (cursor.name === 'ParameterList') {
extractParameterList(cursor, source, params)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
if (!name) return null
return { name, params, classPart }
}
function extractScoped (cursor, source) {
// ScopedIdentifier: NamespaceIdentifier :: Identifier
let ns = null
let name = null
if (!cursor.firstChild()) return null
try {
do {
if (cursor.name === 'NamespaceIdentifier' && !ns) {
ns = source.slice(cursor.from, cursor.to)
} else if (cursor.name === 'Identifier') {
name = source.slice(cursor.from, cursor.to)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
return (ns && name) ? { ns, name } : null
}
function extractParameterList (cursor, source, params) {
if (!cursor.firstChild()) return
try {
do {
if (cursor.name === 'ParameterDeclaration') {
// Get last Identifier as param name (after the type)
const pName = lastIdentifier(cursor, source)
if (pName) params.push(pName)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
}
function lastIdentifier (cursor, source) {
let name = null
if (!cursor.firstChild()) return null
try {
do {
if (cursor.name === 'Identifier') {
name = source.slice(cursor.from, cursor.to)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
return name
}
// --- Free function / out-of-line method definitions ---
function handleFunctionDefinition (cursor, source, result, outOfLineMethods) {
// FunctionDefinition = return-type + FunctionDeclarator + CompoundStatement
if (!cursor.firstChild()) return
let fd = null
const line = getLine(source, cursor.from)
try {
do {
if (cursor.name === 'FunctionDeclarator') {
fd = extractFunctionDeclarator(cursor, source)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
if (!fd) return
if (fd.classPart) {
// Out-of-line method: buffer until we process its class
if (!outOfLineMethods[fd.classPart]) outOfLineMethods[fd.classPart] = []
outOfLineMethods[fd.classPart].push({ name: fd.name, params: fd.params, line })
} else {
result.functions.push({ name: fd.name, params: fd.params, line })
}
}
// --- Enum ---
function handleEnum (cursor, source, result) {
let name = null
const values = []
if (!cursor.firstChild()) return
try {
do {
if (cursor.name === 'TypeIdentifier' && !name) {
name = source.slice(cursor.from, cursor.to)
} else if (cursor.name === 'EnumeratorList') {
if (cursor.firstChild()) {
do {
if (cursor.name === 'Enumerator') {
const v = firstIdentifier(cursor, source)
if (v) values.push(v)
}
} while (cursor.nextSibling())
cursor.parent()
}
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
if (name) result.enums.push({ name, values })
}
// --- Declarations (catches #define macros via TypeDefinition, GDCLASS, etc.) ---
function handleDeclaration (cursor, source, result) {
// Most non-function declarations are global variables or forward declarations.
// We care about free function declarations (not definitions) at top level.
if (!cursor.firstChild()) return
let fd = null
const line = getLine(source, cursor.from)
try {
do {
if (cursor.name === 'FunctionDeclarator') {
fd = extractFunctionDeclarator(cursor, source)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
if (fd && !fd.classPart) {
// Only add if not already defined (avoid double-counting with definitions)
if (!result.functions.find(f => f.name === fd.name)) {
result.functions.push({ name: fd.name, params: fd.params, line })
}
}
}
// --- Helpers ---
function findTypeIdentifier (cursor, source) {
if (!cursor.firstChild()) return null
try {
do {
if (cursor.name === 'TypeIdentifier') {
return source.slice(cursor.from, cursor.to)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
return null
}
function firstIdentifier (cursor, source) {
if (!cursor.firstChild()) return null
try {
do {
if (cursor.name === 'Identifier') {
return source.slice(cursor.from, cursor.to)
}
} while (cursor.nextSibling())
} finally {
cursor.parent()
}
return null
}
function getLine (source, offset) {
return source.slice(0, offset).split('\n').length
}