skills/compiler-internals/compiler-frontend/SKILL.md
Compiler frontend skill for lexing, parsing, and type checking. Use when building a lexer/parser, designing AST nodes, implementing symbol tables, type checking, error recovery, or emitting LLVM IR. Activates on queries about lexer, Pratt parser, recursive descent, AST, symbol table, Hindley-Milner, or llvm-sys.
npx skillsauth add mohitmishra786/low-level-dev-skills compiler-frontendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Guide agents through building a compiler frontend: lexers (hand-written DFA vs flex), Pratt parsing for expressions, recursive-descent for statements, AST design, symbol tables with scoped hash maps, type checking basics, error recovery strategies, and LLVM IR generation via the C API or llvm-sys.
Source → Lexer (tokens) → Parser (AST) → Type checker → IR generator → LLVM IR
typedef enum {
TOK_EOF, TOK_INT, TOK_IDENT, TOK_PLUS, TOK_MINUS,
TOK_LPAREN, TOK_RPAREN, TOK_SEMI, TOK_EQ,
} TokenKind;
typedef struct {
TokenKind kind;
const char *start;
int length;
int64_t int_val;
} Token;
typedef struct {
const char *src;
int pos;
int line;
} Lexer;
static void skip_whitespace(Lexer *l) {
while (l->src[l->pos] == ' ' || l->src[l->pos] == '\n') l->pos++;
}
Token lexer_next(Lexer *l) {
skip_whitespace(l);
const char *start = &l->src[l->pos];
if (isdigit(l->src[l->pos])) {
int64_t val = 0;
while (isdigit(l->src[l->pos]))
val = val * 10 + (l->src[l->pos++] - '0');
return (Token){ TOK_INT, start, l->pos - (start - l->src), val };
}
if (isalpha(l->src[l->pos])) {
while (isalnum(l->src[l->pos])) l->pos++;
return (Token){ TOK_IDENT, start, l->pos - (start - l->src), 0 };
}
char c = l->src[l->pos++];
switch (c) {
case '+': return (Token){ TOK_PLUS, start, 1, 0 };
case '(': return (Token){ TOK_LPAREN, start, 1, 0 };
case ')': return (Token){ TOK_RPAREN, start, 1, 0 };
case ';': return (Token){ TOK_SEMI, start, 1, 0 };
default: return (Token){ TOK_EOF, start, 0, 0 };
}
}
# flex alternative
flex lexer.l && gcc -o lexer lexer.tab.c -lfl
typedef enum { AST_INT, AST_BINOP, AST_VAR } AstKind;
typedef struct AstNode {
AstKind kind;
union {
int64_t int_val;
struct { int op; struct AstNode *lhs, *rhs; } binop;
char *name;
};
} AstNode;
// Binding powers: higher = tighter precedence
enum { BP_NONE = 0, BP_SUM = 10, BP_PRODUCT = 20 };
AstNode *parse_expression(Parser *p, int min_bp) {
AstNode *left = parse_prefix(p);
for (;;) {
int lbp, rbp;
if (!infix_binding_power(p->cur.kind, &lbp, &rbp) || lbp < min_bp)
break;
advance(p);
AstNode *right = parse_expression(p, rbp);
left = make_binop(p->cur.kind, left, right);
}
return left;
}
Pratt handles operator precedence cleanly without massive grammar tables.
AstNode *parse_statement(Parser *p) {
if (match(p, TOK_IDENT) && peek(p) == TOK_EQ) {
char *name = p->prev.text;
advance(p); // =
AstNode *expr = parse_expression(p, BP_NONE);
expect(p, TOK_SEMI);
return make_assign(name, expr);
}
if (match(p, TOK_RETURN)) {
AstNode *expr = parse_expression(p, BP_NONE);
expect(p, TOK_SEMI);
return make_return(expr);
}
return parse_expression_statement(p);
}
typedef struct Symbol {
char *name;
Type *type;
LLVMValueRef llvm_val; // after codegen
struct Symbol *next;
} Symbol;
typedef struct Scope {
Symbol *symbols; // hash map bucket chain
struct Scope *parent;
} Scope;
Symbol *scope_lookup(Scope *s, const char *name) {
for (Scope *cur = s; cur; cur = cur->parent) {
for (Symbol *sym = cur->symbols; sym; sym = sym->next)
if (strcmp(sym->name, name) == 0)
return sym;
}
return NULL;
}
void scope_define(Scope *s, const char *name, Type *type) {
Symbol *sym = malloc(sizeof(Symbol));
sym->name = strdup(name);
sym->type = type;
sym->next = s->symbols;
s->symbols = sym;
}
typedef enum { TY_INT, TY_BOOL, TY_FUNC, TY_VOID } TypeKind;
Type *check_expr(Scope *s, AstNode *node) {
switch (node->kind) {
case AST_INT: return type_int();
case AST_VAR: {
Symbol *sym = scope_lookup(s, node->name);
if (!sym) error("undefined variable %s", node->name);
return sym->type;
}
case AST_BINOP: {
Type *lt = check_expr(s, node->binop.lhs);
Type *rt = check_expr(s, node->binop.rhs);
if (!type_equal(lt, rt))
error("type mismatch in binary op");
return lt;
}
}
return type_void();
}
Hindley-Milner (full inference): assign type variables, unify on constraints — use for ML-like languages.
| Strategy | When |
|----------|------|
| Panic mode | Skip tokens until synchronizing token (;, }) |
| Synchronization sets | Define recovery tokens per nonterminal |
| Error productions | Grammar rules for common mistakes |
| Single-token insertion/deletion | IDE-friendly recovery |
void synchronize(Parser *p) {
advance(p);
while (p->cur.kind != TOK_EOF) {
if (p->prev.kind == TOK_SEMI) return;
if (p->cur.kind == TOK_RETURN || p->cur.kind == TOK_IDENT) return;
advance(p);
}
}
#include <llvm-c/Core.h>
#include <llvm-c/ExecutionEngine.h>
LLVMModuleRef module;
LLVMBuilderRef builder;
LLVMValueRef codegen_expr(Scope *s, AstNode *node) {
switch (node->kind) {
case AST_INT:
return LLVMConstInt(LLVMInt32Type(), node->int_val, 1);
case AST_BINOP: {
LLVMValueRef l = codegen_expr(s, node->binop.lhs);
LLVMValueRef r = codegen_expr(s, node->binop.rhs);
if (node->binop.op == TOK_PLUS)
return LLVMBuildAdd(builder, l, r, "add");
break;
}
case AST_VAR: {
Symbol *sym = scope_lookup(s, node->name);
return LLVMBuildLoad(builder, sym->llvm_val, node->name);
}
}
return NULL;
}
# Dump IR
LLVMDumpModule(module);
# Verify
char *err = NULL;
LLVMVerifyModule(module, LLVMReturnStatusAction, &err);
Rust: use inkwell or llvm-sys crates for safer bindings.
| Symptom | Cause | Fix | |---------|-------|-----| | Parser infinite loop | Missing advance on error | Audit all branches advance token | | Wrong operator precedence | Incorrect binding powers | Table-test expressions | | Duplicate symbol in scope | No shadowing check | Lookup parent only for read; define in current | | LLVM verify fails | Type mismatch in IR | Check signedness, pointer levels | | flex token clash | Overlapping patterns | Longest match; order rules | | Error cascade | No synchronization | Implement panic mode recovery |
skills/compiler-internals/llvm-passes — optimize generated IRskills/compiler-internals/jit-compilation — execute generated IRskills/compiler-internals/mlir — higher-level IR for DSLsskills/compilers/llvm — LLVM as a user (opt, llc)skills/low-level-programming/interpreters — bytecode VM alternativeskills/languages/carbon-lang — modern language frontend exampledevelopment
QEMU/KVM skill for virtualization and kernel development. Use when running qemu-system-x86_64 with KVM, configuring virtio devices, VFIO passthrough, QMP monitor, libvirt, or booting custom kernels. Activates on queries about QEMU, KVM, virtio, VFIO, virsh, virt-install, or -kernel -append.
development
Hardware virtualization internals skill for Intel VT-x and AMD-V. Use when studying VMCS/VMCB, EPT/NPT page tables, VMEXIT handling, APIC virtualization, or building minimal hypervisors. Activates on queries about VMX, SVM, VMCS, EPT, NPT, VMEXIT, or type-1 hypervisor.
testing
Linux containers internals skill for namespaces, cgroups, and OCI. Use when understanding clone/unshare namespaces, cgroups v2 limits, overlayfs, runc, seccomp profiles, capabilities, or escape mitigations. Activates on queries about namespaces, cgroups, overlayfs, runc, seccomp-bpf, OCI spec, or container escape.
tools
Reverse engineering skill for binary analysis. Use when decompiling with Ghidra, analyzing with radare2, scripting RE tools, triaging with strings/file/xxd, or diffing binaries. Activates on queries about Ghidra, radare2, r2, decompiler, Binary Ninja, Diaphora, or stripped binary analysis.